Basic lowering support for classes. (#3334)

Track the fields in a class, and generate a corresponding struct type as
the object representation for the class. For now, we always use a
pointer as the value representation for a class.
This commit is contained in:
Richard Smith
2023-10-25 20:44:24 +00:00
committed by GitHub
parent 7f2f4bec4f
commit 620408b999
18 changed files with 127 additions and 23 deletions
+31 -12
View File
@@ -92,9 +92,17 @@ auto Context::NoteIncompleteClass(SemIR::ClassId class_id,
DiagnosticBuilder& builder) -> void {
CARBON_DIAGNOSTIC(ClassForwardDeclaredHere, Note,
"Class was forward declared here.");
CARBON_DIAGNOSTIC(ClassIncompleteWithinDefinition, Note,
"Class is incomplete within its definition.");
const auto& class_info = classes().Get(class_id);
builder.Note(nodes().Get(class_info.declaration_id).parse_node(),
ClassForwardDeclaredHere);
CARBON_CHECK(!class_info.is_defined()) << "Class is not incomplete";
if (class_info.definition_id.is_valid()) {
builder.Note(nodes().Get(class_info.definition_id).parse_node(),
ClassIncompleteWithinDefinition);
} else {
builder.Note(nodes().Get(class_info.declaration_id).parse_node(),
ClassForwardDeclaredHere);
}
}
auto Context::AddNameToLookup(Parse::Node name_node, StringId name_id,
@@ -410,15 +418,20 @@ class TypeCompleter {
}
break;
case SemIR::ClassType::Kind:
// TODO: Support class definitions and complete class types.
if (diagnoser_) {
auto builder = (*diagnoser_)();
context_.NoteIncompleteClass(
type_node.As<SemIR::ClassType>().class_id, builder);
builder.Emit();
case SemIR::ClassType::Kind: {
auto class_type = type_node.As<SemIR::ClassType>();
auto& class_info = context_.classes().Get(class_type.class_id);
if (!class_info.is_defined()) {
if (diagnoser_) {
auto builder = (*diagnoser_)();
context_.NoteIncompleteClass(class_type.class_id, builder);
builder.Emit();
}
return false;
}
return false;
Push(class_info.object_representation_id);
break;
}
case SemIR::ConstType::Kind:
Push(type_node.As<SemIR::ConstType>().inner_id);
@@ -659,8 +672,14 @@ class TypeCompleter {
node.As<SemIR::TupleType>());
case SemIR::ClassType::Kind:
// TODO: Support class definitions and complete class types.
CARBON_FATAL() << "Class types are currently never complete";
// The value representation for a class is a pointer to the object
// representation.
// TODO: Support customized value representations for classes.
// TODO: Pick a better value representation when possible.
return MakePointerRepresentation(
node.parse_node(), context_.classes()
.Get(node.As<SemIR::ClassType>().class_id)
.object_representation_id);
case SemIR::Builtin::Kind:
CARBON_FATAL() << "Builtins should be named as cross-references";
+1 -2
View File
@@ -126,8 +126,7 @@ auto DeclarationNameStack::UpdateScopeIfNeeded(NameContext& name_context)
case SemIR::ClassDeclaration::Kind: {
const auto& class_info = context_->classes().Get(
resolved_node.As<SemIR::ClassDeclaration>().class_id);
// TODO: Check that the class is complete rather than that it has a scope.
if (class_info.scope_id.is_valid()) {
if (class_info.is_defined()) {
name_context.state = NameContext::State::Resolved;
name_context.target_scope_id = class_info.scope_id;
} else {
+9 -4
View File
@@ -106,6 +106,7 @@ auto HandleClassDefinitionStart(Context& context, Parse::Node parse_node)
context.PushScope(class_decl_id, class_info.scope_id);
context.node_block_stack().Push();
context.node_stack().Push(parse_node, class_id);
context.args_type_info_stack().Push();
// TODO: Handle the case where there's control flow in the class body. For
// example:
@@ -119,13 +120,17 @@ auto HandleClassDefinitionStart(Context& context, Parse::Node parse_node)
return true;
}
auto HandleClassDefinition(Context& context, Parse::Node /*parse_node*/)
-> bool {
context.node_stack().Pop<Parse::NodeKind::ClassDefinitionStart>();
auto HandleClassDefinition(Context& context, Parse::Node parse_node) -> bool {
auto fields_id = context.args_type_info_stack().Pop();
auto class_id =
context.node_stack().Pop<Parse::NodeKind::ClassDefinitionStart>();
context.node_block_stack().Pop();
context.PopScope();
// TODO: Mark the class as a complete type.
// The class type is now fully defined.
auto& class_info = context.classes().Get(class_id);
class_info.object_representation_id =
context.CanonicalizeStructType(parse_node, fields_id);
return true;
}
+1 -1
View File
@@ -19,7 +19,7 @@ static auto GetAsNameScope(Context& context, SemIR::NodeId base_id)
}
if (auto base_as_class = base.TryAs<SemIR::ClassType>()) {
auto& class_info = context.classes().Get(base_as_class->class_id);
if (!class_info.scope_id.is_valid()) {
if (!class_info.is_defined()) {
CARBON_DIAGNOSTIC(QualifiedExpressionInIncompleteClassScope, Error,
"Member access into incomplete class `{0}`.",
std::string);
@@ -57,6 +57,10 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool {
value_type_id = context.CanonicalizeType(field_type_node_id);
value_id =
context.AddNode(SemIR::Field{parse_node, value_type_id, name_id});
// Add a corresponding field to the object representation of the class.
context.args_type_info_stack().AddNode(
SemIR::StructTypeField{parse_node, name_id, cast_type_id});
} else {
value_id = context.AddNode(
SemIR::VarStorage{name_node, value_type_id, name_id});
+1
View File
@@ -25,6 +25,7 @@ fn Run() -> i32 {
// CHECK:STDOUT: file "basic.carbon" {
// CHECK:STDOUT: class_declaration @Class, ()
// CHECK:STDOUT: %Class: type = class_type @Class
// CHECK:STDOUT: %.loc15: type = struct_type {.k: i32}
// CHECK:STDOUT: %G: <function> = fn_decl @G
// CHECK:STDOUT: %Run: <function> = fn_decl @Run
// CHECK:STDOUT: }
@@ -24,6 +24,7 @@ fn T.F() {}
// CHECK:STDOUT: file "fail_member_of_let.carbon" {
// CHECK:STDOUT: class_declaration @Class, ()
// CHECK:STDOUT: %Class: type = class_type @Class
// CHECK:STDOUT: %.loc9: type = struct_type {}
// CHECK:STDOUT: %Class.ref: type = name_reference "Class", %Class
// CHECK:STDOUT: %T: type = bind_name "T", %Class.ref
// CHECK:STDOUT: %.loc22: <function> = fn_decl @.1
@@ -27,6 +27,7 @@ fn Class.H() {}
// CHECK:STDOUT: file "fail_redefinition.carbon" {
// CHECK:STDOUT: class_declaration @Class, ()
// CHECK:STDOUT: %Class: type = class_type @Class
// CHECK:STDOUT: %.loc10: type = struct_type {}
// CHECK:STDOUT: class_declaration @Class, ()
// CHECK:STDOUT: %F: <function> = fn_decl @F
// CHECK:STDOUT: %G: <function> = fn_decl @G
+9 -2
View File
@@ -8,6 +8,12 @@ class Class {
fn G() -> i32 {
// TODO: This should find the member function `F` even though it's declared
// later.
// CHECK:STDERR: fail_reorder.carbon:[[@LINE+12]]:12: ERROR: Member access into incomplete class `Class`.
// CHECK:STDERR: return Class.F();
// CHECK:STDERR: ^
// CHECK:STDERR: fail_reorder.carbon:[[@LINE-7]]:1: Class is incomplete within its definition.
// CHECK:STDERR: class Class {
// CHECK:STDERR: ^
// CHECK:STDERR: fail_reorder.carbon:[[@LINE+6]]:17: ERROR: Name `F` not found.
// CHECK:STDERR: return Class.F();
// CHECK:STDERR: ^
@@ -25,6 +31,7 @@ class Class {
// CHECK:STDOUT: file "fail_reorder.carbon" {
// CHECK:STDOUT: class_declaration @Class, ()
// CHECK:STDOUT: %Class: type = class_type @Class
// CHECK:STDOUT: %.loc29: type = struct_type {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @Class {
@@ -45,6 +52,6 @@ class Class {
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() -> i32 {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %.loc21: i32 = int_literal 1
// CHECK:STDOUT: return %.loc21
// CHECK:STDOUT: %.loc27: i32 = int_literal 1
// CHECK:STDOUT: return %.loc27
// CHECK:STDOUT: }
+1
View File
@@ -24,6 +24,7 @@ class Class {
// CHECK:STDOUT: file "fail_scope.carbon" {
// CHECK:STDOUT: class_declaration @Class, ()
// CHECK:STDOUT: %Class: type = class_type @Class
// CHECK:STDOUT: %.loc22: type = struct_type {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @Class {
@@ -25,6 +25,7 @@ fn G() -> i32 {
// CHECK:STDOUT: file "fail_unbound_field.carbon" {
// CHECK:STDOUT: class_declaration @Class, ()
// CHECK:STDOUT: %Class: type = class_type @Class
// CHECK:STDOUT: %.loc16: type = struct_type {.field: i32}
// CHECK:STDOUT: %G: <function> = fn_decl @G
// CHECK:STDOUT: }
// CHECK:STDOUT:
+1
View File
@@ -16,6 +16,7 @@ fn Class.F() {}
// CHECK:STDOUT: class_declaration @Class, ()
// CHECK:STDOUT: %Class: type = class_type @Class
// CHECK:STDOUT: class_declaration @Class, ()
// CHECK:STDOUT: %.loc11: type = struct_type {}
// CHECK:STDOUT: %F: <function> = fn_decl @F
// CHECK:STDOUT: }
// CHECK:STDOUT:
+1
View File
@@ -21,6 +21,7 @@ fn Run() -> i32 {
// CHECK:STDOUT: file "scope.carbon" {
// CHECK:STDOUT: class_declaration @Class, ()
// CHECK:STDOUT: %Class: type = class_type @Class
// CHECK:STDOUT: %.loc11: type = struct_type {}
// CHECK:STDOUT: %F: <function> = fn_decl @F.2
// CHECK:STDOUT: %Run: <function> = fn_decl @Run
// CHECK:STDOUT: }
@@ -118,6 +118,7 @@ CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop)
CARBON_DIAGNOSTIC_KIND(ClassForwardDeclaredHere)
CARBON_DIAGNOSTIC_KIND(ClassPreviousDefinition)
CARBON_DIAGNOSTIC_KIND(ClassRedefinition)
CARBON_DIAGNOSTIC_KIND(ClassIncompleteWithinDefinition)
CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop)
CARBON_DIAGNOSTIC_KIND(DereferenceOfNonPointer)
CARBON_DIAGNOSTIC_KIND(DereferenceOfType)
+11
View File
@@ -225,6 +225,13 @@ auto FileContext::BuildType(SemIR::NodeId node_id) -> llvm::Type* {
GetType(array_type.element_type_id),
sem_ir_->GetArrayBoundValue(array_type.bound_id));
}
case SemIR::ClassType::Kind: {
auto object_representation_id =
sem_ir_->classes()
.Get(node.As<SemIR::ClassType>().class_id)
.object_representation_id;
return GetType(object_representation_id);
}
case SemIR::ConstType::Kind:
return GetType(node.As<SemIR::ConstType>().inner_id);
case SemIR::PointerType::Kind:
@@ -258,6 +265,10 @@ auto FileContext::BuildType(SemIR::NodeId node_id) -> llvm::Type* {
}
return llvm::StructType::get(*llvm_context_, subtypes);
}
case SemIR::UnboundFieldType::Kind: {
// Return an empty struct as a placeholder.
return llvm::StructType::get(*llvm_context_);
}
default: {
CARBON_FATAL() << "Cannot use node as type: " << node_id << " " << node;
}
+2
View File
@@ -210,6 +210,8 @@ auto HandleNameReference(FunctionContext& context, SemIR::NodeId node_id,
auto target = context.sem_ir().nodes().Get(node.value_id);
if (auto function_decl = target.TryAs<SemIR::FunctionDeclaration>()) {
context.SetLocal(node_id, context.GetFunction(function_decl->function_id));
} else if (auto class_type = target.TryAs<SemIR::ClassType>()) {
context.SetLocal(node_id, context.GetTypeAsValue());
} else {
// TODO: Handle other kinds of name references to globals.
context.SetLocal(node_id, context.GetLocal(node.value_id));
+34
View File
@@ -0,0 +1,34 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// AUTOUPDATE
class C {
var a: i32;
var b: C*;
}
fn F(c: C) -> C {
// TODO: We should copy `c` into the return slot here.
return c;
}
fn Run() {
var c: C;
var d: C = F(c);
}
// CHECK:STDOUT: ; ModuleID = 'basic.carbon'
// CHECK:STDOUT: source_filename = "basic.carbon"
// CHECK:STDOUT:
// CHECK:STDOUT: define void @F(ptr sret({ i32, ptr }) %return, ptr %c) {
// CHECK:STDOUT: ret void
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define void @main() {
// CHECK:STDOUT: %c = alloca { i32, ptr }, align 8
// CHECK:STDOUT: %d = alloca { i32, ptr }, align 8
// CHECK:STDOUT: call void @F(ptr %d, ptr %c)
// CHECK:STDOUT: ret void
// CHECK:STDOUT: }
+17 -2
View File
@@ -62,20 +62,35 @@ struct Class : public Printable<Class> {
out << "}";
}
// Determines whether this class has been fully defined. This is false until
// we reach the `}` of the class definition.
bool is_defined() const { return object_representation_id.is_valid(); }
// The following members always have values, and do not change throughout the
// lifetime of the class.
// The class name.
StringId name_id;
// The class type, which is the type of `Self` in the class definition.
TypeId self_type_id;
// The first declaration of the class. This is a ClassDeclaration.
NodeId declaration_id = NodeId::Invalid;
// The definition, if the class has been defined or is currently being
// defined. This is a ClassDeclaration.
// The following members are set at the `{` of the class definition.
// The definition of the class. This is a ClassDeclaration.
NodeId definition_id = NodeId::Invalid;
// The class scope.
NameScopeId scope_id = NameScopeId::Invalid;
// The first block of the class body.
// TODO: Handle control flow in the class body, such as if-expressions.
NodeBlockId body_block_id = NodeBlockId::Invalid;
// The following members are set at the `}` of the class definition.
// The object representation type to use for this class. This is valid once
// the class is defined.
TypeId object_representation_id = TypeId::Invalid;
};
// The value representation to use when passing by value.