Эх сурвалжийг харах

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.
Richard Smith 2 жил өмнө
parent
commit
620408b999

+ 31 - 12
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<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
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<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
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<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
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<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);

+ 4 - 0
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});

+ 1 - 0
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: <function> = fn_decl @G
 // CHECK:STDOUT:   %Run: <function> = fn_decl @Run
 // CHECK:STDOUT: }

+ 1 - 0
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: <function> = fn_decl @.1

+ 1 - 0
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: <function> = fn_decl @F
 // CHECK:STDOUT:   %G: <function> = fn_decl @G

+ 9 - 2
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: }

+ 1 - 0
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 {

+ 1 - 0
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: <function> = fn_decl @G
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
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: <function> = fn_decl @F
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
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: <function> = fn_decl @F.2
 // CHECK:STDOUT:   %Run: <function> = fn_decl @Run
 // CHECK:STDOUT: }

+ 1 - 0
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)

+ 11 - 0
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<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 - 0
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<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 - 0
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: }

+ 17 - 2
toolchain/sem_ir/file.h

@@ -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.