From 620408b999e25403caea66f2c9307585e9162c8e Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 25 Oct 2023 13:44:24 -0700 Subject: [PATCH] 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. --- toolchain/check/context.cpp | 43 +++++++++++++------ toolchain/check/declaration_name_stack.cpp | 3 +- toolchain/check/handle_class.cpp | 13 ++++-- toolchain/check/handle_name.cpp | 2 +- toolchain/check/handle_pattern_binding.cpp | 4 ++ toolchain/check/testdata/class/basic.carbon | 1 + .../testdata/class/fail_member_of_let.carbon | 1 + .../testdata/class/fail_redefinition.carbon | 1 + .../check/testdata/class/fail_reorder.carbon | 11 ++++- .../check/testdata/class/fail_scope.carbon | 1 + .../testdata/class/fail_unbound_field.carbon | 1 + .../check/testdata/class/redeclaration.carbon | 1 + toolchain/check/testdata/class/scope.carbon | 1 + toolchain/diagnostics/diagnostic_kind.def | 1 + toolchain/lower/file_context.cpp | 11 +++++ toolchain/lower/handle.cpp | 2 + toolchain/lower/testdata/class/basic.carbon | 34 +++++++++++++++ toolchain/sem_ir/file.h | 19 +++++++- 18 files changed, 127 insertions(+), 23 deletions(-) create mode 100644 toolchain/lower/testdata/class/basic.carbon diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 8994231026f4..85769ef465ac 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -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().class_id, builder); - builder.Emit(); + case SemIR::ClassType::Kind: { + auto class_type = type_node.As(); + 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().inner_id); @@ -659,8 +672,14 @@ class TypeCompleter { node.As()); 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().class_id) + .object_representation_id); case SemIR::Builtin::Kind: CARBON_FATAL() << "Builtins should be named as cross-references"; diff --git a/toolchain/check/declaration_name_stack.cpp b/toolchain/check/declaration_name_stack.cpp index ea1e0f932c66..b96946852f94 100644 --- a/toolchain/check/declaration_name_stack.cpp +++ b/toolchain/check/declaration_name_stack.cpp @@ -126,8 +126,7 @@ auto DeclarationNameStack::UpdateScopeIfNeeded(NameContext& name_context) case SemIR::ClassDeclaration::Kind: { const auto& class_info = context_->classes().Get( resolved_node.As().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 { diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 86cff7c39c70..e2b8fe31e239 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -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(); +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(); 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; } diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index c3708dfa4c51..5b6719e40d42 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -19,7 +19,7 @@ static auto GetAsNameScope(Context& context, SemIR::NodeId base_id) } if (auto base_as_class = base.TryAs()) { 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); diff --git a/toolchain/check/handle_pattern_binding.cpp b/toolchain/check/handle_pattern_binding.cpp index 3cd69072c20f..2464d2b78a44 100644 --- a/toolchain/check/handle_pattern_binding.cpp +++ b/toolchain/check/handle_pattern_binding.cpp @@ -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}); diff --git a/toolchain/check/testdata/class/basic.carbon b/toolchain/check/testdata/class/basic.carbon index 91909ae60388..104e0a2f9585 100644 --- a/toolchain/check/testdata/class/basic.carbon +++ b/toolchain/check/testdata/class/basic.carbon @@ -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: = fn_decl @G // CHECK:STDOUT: %Run: = fn_decl @Run // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_member_of_let.carbon b/toolchain/check/testdata/class/fail_member_of_let.carbon index 43358a8aed72..f3079881d19d 100644 --- a/toolchain/check/testdata/class/fail_member_of_let.carbon +++ b/toolchain/check/testdata/class/fail_member_of_let.carbon @@ -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: = fn_decl @.1 diff --git a/toolchain/check/testdata/class/fail_redefinition.carbon b/toolchain/check/testdata/class/fail_redefinition.carbon index fd0a289358d2..a6738b390086 100644 --- a/toolchain/check/testdata/class/fail_redefinition.carbon +++ b/toolchain/check/testdata/class/fail_redefinition.carbon @@ -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: = fn_decl @F // CHECK:STDOUT: %G: = fn_decl @G diff --git a/toolchain/check/testdata/class/fail_reorder.carbon b/toolchain/check/testdata/class/fail_reorder.carbon index 04c1559ccf72..0ca5514b3b9c 100644 --- a/toolchain/check/testdata/class/fail_reorder.carbon +++ b/toolchain/check/testdata/class/fail_reorder.carbon @@ -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: } diff --git a/toolchain/check/testdata/class/fail_scope.carbon b/toolchain/check/testdata/class/fail_scope.carbon index c5bb6346719c..fa039bb869b5 100644 --- a/toolchain/check/testdata/class/fail_scope.carbon +++ b/toolchain/check/testdata/class/fail_scope.carbon @@ -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 { diff --git a/toolchain/check/testdata/class/fail_unbound_field.carbon b/toolchain/check/testdata/class/fail_unbound_field.carbon index 60a076dc141e..0ae30c13c1e4 100644 --- a/toolchain/check/testdata/class/fail_unbound_field.carbon +++ b/toolchain/check/testdata/class/fail_unbound_field.carbon @@ -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: = fn_decl @G // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/redeclaration.carbon b/toolchain/check/testdata/class/redeclaration.carbon index 53ac64e8199b..4245092e8e41 100644 --- a/toolchain/check/testdata/class/redeclaration.carbon +++ b/toolchain/check/testdata/class/redeclaration.carbon @@ -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: = fn_decl @F // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/scope.carbon b/toolchain/check/testdata/class/scope.carbon index 1dbb45367ddd..eeb9ba80e1e7 100644 --- a/toolchain/check/testdata/class/scope.carbon +++ b/toolchain/check/testdata/class/scope.carbon @@ -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: = fn_decl @F.2 // CHECK:STDOUT: %Run: = fn_decl @Run // CHECK:STDOUT: } diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 363706fa7062..b3225af80ea4 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -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) diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index cdd5c8fdb697..01f59020259b 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -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().class_id) + .object_representation_id; + return GetType(object_representation_id); + } case SemIR::ConstType::Kind: return GetType(node.As().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; } diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index 810275864118..bfc85abda369 100644 --- a/toolchain/lower/handle.cpp +++ b/toolchain/lower/handle.cpp @@ -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()) { context.SetLocal(node_id, context.GetFunction(function_decl->function_id)); + } else if (auto class_type = target.TryAs()) { + 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)); diff --git a/toolchain/lower/testdata/class/basic.carbon b/toolchain/lower/testdata/class/basic.carbon new file mode 100644 index 000000000000..8b9d5865dbcd --- /dev/null +++ b/toolchain/lower/testdata/class/basic.carbon @@ -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: } diff --git a/toolchain/sem_ir/file.h b/toolchain/sem_ir/file.h index f240dd9b60f3..78eea2b104d7 100644 --- a/toolchain/sem_ir/file.h +++ b/toolchain/sem_ir/file.h @@ -62,20 +62,35 @@ struct Class : public Printable { 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.