Kaynağa Gözat

Support for class definitions with static member functions. (#3305)

This includes being able to define a class that was previously
forward-declared, and being able to define a member function out-of-line
that was previously declared inside a class.

No support for fields or methods yet, and a class definition doesn't yet
cause the class to be treated as a complete type.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 2 yıl önce
ebeveyn
işleme
b01dfb3f93

+ 14 - 6
toolchain/check/context.cpp

@@ -89,6 +89,13 @@ auto Context::DiagnoseNameNotFound(Parse::Node parse_node,
   emitter_->Emit(parse_node, NameNotFound, semantics_ir_->GetString(name_id));
 }
 
+auto Context::NoteIncompleteClass(SemIR::ClassDeclaration class_decl,
+                                  DiagnosticBuilder& builder) -> void {
+  CARBON_DIAGNOSTIC(ClassForwardDeclaredHere, Note,
+                    "Class was forward declared here.");
+  builder.Note(class_decl.parse_node, ClassForwardDeclaredHere);
+}
+
 auto Context::AddNameToLookup(Parse::Node name_node, SemIR::StringId name_id,
                               SemIR::NodeId target_id) -> void {
   if (current_scope().names.insert(name_id).second) {
@@ -128,7 +135,9 @@ auto Context::LookupName(Parse::Node parse_node, SemIR::StringId name_id,
   }
 }
 
-auto Context::PushScope() -> void { scope_stack_.push_back({}); }
+auto Context::PushScope(SemIR::NameScopeId scope_id) -> void {
+  scope_stack_.push_back({.scope_id = scope_id});
+}
 
 auto Context::PopScope() -> void {
   auto scope = scope_stack_.pop_back_val();
@@ -394,11 +403,10 @@ class TypeCompleter {
       case SemIR::ClassDeclaration::Kind:
         // TODO: Support class definitions and complete class types.
         if (diagnoser_) {
-          CARBON_DIAGNOSTIC(ClassForwardDeclaredHere, Note,
-                            "Class was forward declared here.");
-          (*diagnoser_)()
-              .Note(type_node.parse_node(), ClassForwardDeclaredHere)
-              .Emit();
+          auto builder = (*diagnoser_)();
+          context_.NoteIncompleteClass(type_node.As<SemIR::ClassDeclaration>(),
+                                       builder);
+          builder.Emit();
         }
         return false;
 

+ 13 - 1
toolchain/check/context.h

@@ -66,12 +66,21 @@ class Context {
   auto DiagnoseNameNotFound(Parse::Node parse_node, SemIR::StringId name_id)
       -> void;
 
+  // Adds a note to a diagnostic explaining that a class is incomplete.
+  auto NoteIncompleteClass(SemIR::ClassDeclaration class_decl,
+                           DiagnosticBuilder& builder) -> void;
+
   // Pushes a new scope onto scope_stack_.
-  auto PushScope() -> void;
+  auto PushScope(SemIR::NameScopeId scope_id = SemIR::NameScopeId::Invalid)
+      -> void;
 
   // Pops the top scope from scope_stack_, cleaning up names from name_lookup_.
   auto PopScope() -> void;
 
+  auto current_scope_id() const -> SemIR::NameScopeId {
+    return scope_stack_.back().scope_id;
+  }
+
   // Follows NameReference nodes to find the value named by a given node.
   auto FollowNameReferences(SemIR::NodeId node_id) -> SemIR::NodeId;
 
@@ -232,6 +241,9 @@ class Context {
 
   // An entry in scope_stack_.
   struct ScopeStackEntry {
+    // The name scope associated with this entry, if any.
+    SemIR::NameScopeId scope_id;
+
     // Names which are registered with name_lookup_, and will need to be
     // deregistered when the scope ends.
     llvm::DenseSet<SemIR::StringId> names;

+ 54 - 19
toolchain/check/declaration_name_stack.cpp

@@ -11,7 +11,7 @@ namespace Carbon::Check {
 auto DeclarationNameStack::Push() -> void {
   declaration_name_stack_.push_back(
       {.state = NameContext::State::New,
-       .target_scope_id = SemIR::NameScopeId::Invalid,
+       .target_scope_id = context_->current_scope_id(),
        .resolved_node_id = SemIR::NodeId::Invalid});
 }
 
@@ -33,29 +33,29 @@ auto DeclarationNameStack::Pop() -> NameContext {
   return declaration_name_stack_.pop_back_val();
 }
 
-auto DeclarationNameStack::AddNameToLookup(NameContext name_context,
-                                           SemIR::NodeId target_id) -> void {
+auto DeclarationNameStack::LookupOrAddName(NameContext name_context,
+                                           SemIR::NodeId target_id)
+    -> SemIR::NodeId {
   switch (name_context.state) {
     case NameContext::State::Error:
       // The name is invalid and a diagnostic has already been emitted.
-      return;
+      return SemIR::NodeId::Invalid;
 
     case NameContext::State::New:
       CARBON_FATAL() << "Name is missing, not expected to call AddNameToLookup "
                         "(but that may change based on error handling).";
 
     case NameContext::State::Resolved:
-    case NameContext::State::ResolvedNonScope: {
-      context_->DiagnoseDuplicateName(name_context.parse_node,
-                                      name_context.resolved_node_id);
-      return;
-    }
+    case NameContext::State::ResolvedNonScope:
+      return name_context.resolved_node_id;
 
     case NameContext::State::Unresolved:
       if (name_context.target_scope_id == SemIR::NameScopeId::Invalid) {
         context_->AddNameToLookup(name_context.parse_node,
                                   name_context.unresolved_name_id, target_id);
       } else {
+        // TODO: Reject unless the scope is a namespace scope or the name is
+        // unqualified.
         bool success = context_->semantics_ir().AddNameScopeEntry(
             name_context.target_scope_id, name_context.unresolved_name_id,
             target_id);
@@ -64,7 +64,15 @@ auto DeclarationNameStack::AddNameToLookup(NameContext name_context,
             << name_context.unresolved_name_id << " in "
             << name_context.target_scope_id;
       }
-      return;
+      return SemIR::NodeId::Invalid;
+  }
+}
+
+auto DeclarationNameStack::AddNameToLookup(NameContext name_context,
+                                           SemIR::NodeId target_id) -> void {
+  auto existing_node_id = LookupOrAddName(name_context, target_id);
+  if (existing_node_id.is_valid()) {
+    context_->DiagnoseDuplicateName(name_context.parse_node, existing_node_id);
   }
 }
 
@@ -118,6 +126,18 @@ auto DeclarationNameStack::UpdateScopeIfNeeded(NameContext& name_context)
   auto resolved_node =
       context_->semantics_ir().GetNode(name_context.resolved_node_id);
   switch (resolved_node.kind()) {
+    case SemIR::ClassDeclaration::Kind: {
+      auto& class_info = context_->semantics_ir().GetClass(
+          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()) {
+        name_context.state = NameContext::State::Resolved;
+        name_context.target_scope_id = class_info.scope_id;
+      } else {
+        name_context.state = NameContext::State::ResolvedNonScope;
+      }
+      break;
+    }
     case SemIR::Namespace::Kind:
       name_context.state = NameContext::State::Resolved;
       name_context.target_scope_id =
@@ -147,16 +167,31 @@ auto DeclarationNameStack::CanResolveQualifier(NameContext& name_context,
     case NameContext::State::ResolvedNonScope: {
       // Because more qualifiers were found, we diagnose that the earlier
       // qualifier didn't resolve to a scoped entity.
+      if (auto class_decl = context_->semantics_ir()
+                                .GetNode(name_context.resolved_node_id)
+                                .TryAs<SemIR::ClassDeclaration>()) {
+        CARBON_DIAGNOSTIC(QualifiedDeclarationInIncompleteClassScope, Error,
+                          "Cannot declare a member of incomplete class `{0}`.",
+                          std::string);
+        auto builder = context_->emitter().Build(
+            name_context.parse_node, QualifiedDeclarationInIncompleteClassScope,
+            context_->semantics_ir().StringifyTypeExpression(
+                name_context.resolved_node_id, true));
+        context_->NoteIncompleteClass(*class_decl, builder);
+        builder.Emit();
+      } else {
+        CARBON_DIAGNOSTIC(
+            QualifiedDeclarationInNonScope, Error,
+            "Declaration qualifiers are only allowed for entities "
+            "that provide a scope.");
+        CARBON_DIAGNOSTIC(QualifiedDeclarationNonScopeEntity, Note,
+                          "Non-scope entity referenced here.");
+        context_->emitter()
+            .Build(parse_node, QualifiedDeclarationInNonScope)
+            .Note(name_context.parse_node, QualifiedDeclarationNonScopeEntity)
+            .Emit();
+      }
       name_context.state = NameContext::State::Error;
-      CARBON_DIAGNOSTIC(QualifiedDeclarationInNonScope, Error,
-                        "Declaration qualifiers are only allowed for entities "
-                        "that provide a scope.");
-      CARBON_DIAGNOSTIC(QualifiedDeclarationNonScopeEntity, Note,
-                        "Non-scope entity referenced here.");
-      context_->emitter()
-          .Build(parse_node, QualifiedDeclarationInNonScope)
-          .Note(name_context.parse_node, QualifiedDeclarationNonScopeEntity)
-          .Emit();
       return false;
     }
 

+ 8 - 2
toolchain/check/declaration_name_stack.h

@@ -75,8 +75,9 @@ class DeclarationNameStack {
 
     State state = State::New;
 
-    // The scope which qualified names are added to. For unqualified names,
-    // this will be Invalid to indicate the current scope should be used.
+    // The scope which qualified names are added to. For unqualified names in
+    // an unnamed scope, this will be Invalid to indicate the current scope
+    // should be used.
     SemIR::NameScopeId target_scope_id = SemIR::NameScopeId::Invalid;
 
     // The last parse node used.
@@ -117,6 +118,11 @@ class DeclarationNameStack {
   auto AddNameToLookup(NameContext name_context, SemIR::NodeId target_id)
       -> void;
 
+  // Adds a name to name lookup, or returns the existing node if this name has
+  // already been declared in this scope.
+  auto LookupOrAddName(NameContext name_context, SemIR::NodeId target_id)
+      -> SemIR::NodeId;
+
  private:
   // Returns true if the context is in a state where it can resolve qualifiers.
   // Updates name_context as needed.

+ 84 - 17
toolchain/check/handle_class.cpp

@@ -17,24 +17,51 @@ auto HandleClassIntroducer(Context& context, Parse::Node parse_node) -> bool {
   return true;
 }
 
-static auto BuildClassDeclaration(Context& context) -> void {
+static auto BuildClassDeclaration(Context& context)
+    -> std::tuple<SemIR::ClassId, SemIR::NodeId> {
   auto name_context = context.declaration_name_stack().Pop();
-
   auto class_keyword =
       context.node_stack()
           .PopForSoloParseNode<Parse::NodeKind::ClassIntroducer>();
+  auto decl_block_id = context.node_block_stack().Pop();
 
-  // TODO: Track this somewhere.
-  context.node_block_stack().Pop();
+  // Add the class declaration.
+  auto class_decl =
+      SemIR::ClassDeclaration(class_keyword, SemIR::TypeId::TypeType,
+                              SemIR::ClassId::Invalid, decl_block_id);
+  auto class_decl_id = context.AddNode(class_decl);
+
+  // Check whether this is a redeclaration.
+  auto existing_id = context.declaration_name_stack().LookupOrAddName(
+      name_context, class_decl_id);
+  if (existing_id.is_valid()) {
+    if (auto existing_class_decl = context.semantics_ir()
+                                       .GetNode(existing_id)
+                                       .TryAs<SemIR::ClassDeclaration>()) {
+      // This is a redeclaration of an existing class.
+      class_decl.class_id = existing_class_decl->class_id;
+    } else {
+      // This is a redeclaration of something other than a class.
+      context.DiagnoseDuplicateName(name_context.parse_node, existing_id);
+    }
+  }
+
+  // Create a new class if this isn't a valid redeclaration.
+  if (!class_decl.class_id.is_valid()) {
+    // TODO: If this is an invalid redeclaration of a non-class entity or there
+    // was an error in the qualifier, we will have lost track of the class name
+    // here. We should keep track of it even if the name is invalid.
+    class_decl.class_id = context.semantics_ir().AddClass(
+        {.name_id = name_context.state ==
+                            DeclarationNameStack::NameContext::State::Unresolved
+                        ? name_context.unresolved_name_id
+                        : SemIR::StringId(SemIR::StringId::InvalidIndex)});
+  }
 
-  auto class_id = context.semantics_ir().AddClass(
-      {.name_id = name_context.state ==
-                          DeclarationNameStack::NameContext::State::Unresolved
-                      ? name_context.unresolved_name_id
-                      : SemIR::StringId(SemIR::StringId::InvalidIndex)});
-  auto class_decl_id = context.AddNode(SemIR::ClassDeclaration(
-      class_keyword, SemIR::TypeId::TypeType, class_id));
-  context.declaration_name_stack().AddNameToLookup(name_context, class_decl_id);
+  // Write the class ID into the ClassDeclaration.
+  context.semantics_ir().ReplaceNode(class_decl_id, class_decl);
+
+  return {class_decl.class_id, class_decl_id};
 }
 
 auto HandleClassDeclaration(Context& context, Parse::Node /*parse_node*/)
@@ -45,13 +72,53 @@ auto HandleClassDeclaration(Context& context, Parse::Node /*parse_node*/)
 
 auto HandleClassDefinitionStart(Context& context, Parse::Node parse_node)
     -> bool {
-  BuildClassDeclaration(context);
-  // TODO: Introduce `Self`.
-  return context.TODO(parse_node, "HandleClassDefinitionStart");
+  auto [class_id, class_decl_id] = BuildClassDeclaration(context);
+  auto& class_info = context.semantics_ir().GetClass(class_id);
+
+  // Track that this declaration is the definition.
+  if (class_info.definition_id.is_valid()) {
+    CARBON_DIAGNOSTIC(ClassRedefinition, Error, "Redefinition of class {0}.",
+                      llvm::StringRef);
+    CARBON_DIAGNOSTIC(ClassPreviousDefinition, Note,
+                      "Previous definition was here.");
+    context.emitter()
+        .Build(parse_node, ClassRedefinition,
+               context.semantics_ir().GetString(class_info.name_id))
+        .Note(context.semantics_ir()
+                  .GetNode(class_info.definition_id)
+                  .parse_node(),
+              ClassPreviousDefinition)
+        .Emit();
+  } else {
+    class_info.definition_id = class_decl_id;
+    class_info.scope_id = context.semantics_ir().AddNameScope();
+
+    // TODO: Introduce `Self`.
+  }
+
+  // Enter the class scope.
+  context.PushScope(class_info.scope_id);
+  context.node_block_stack().Push();
+
+  // TODO: Handle the case where there's control flow in the class body. For
+  // example:
+  //
+  //   class C {
+  //     var v: if true then i32 else f64;
+  //   }
+  //
+  // We may need to track a list of node blocks here, as we do for a function.
+  class_info.body_block_id = context.node_block_stack().PeekOrAdd();
+  return true;
 }
 
-auto HandleClassDefinition(Context& context, Parse::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandleClassDefinition");
+auto HandleClassDefinition(Context& context, Parse::Node /*parse_node*/)
+    -> bool {
+  context.node_block_stack().Pop();
+  context.PopScope();
+
+  // TODO: Mark the class as a complete type.
+  return true;
 }
 
 }  // namespace Carbon::Check

+ 71 - 22
toolchain/check/handle_function.cpp

@@ -11,7 +11,7 @@ namespace Carbon::Check {
 // Build a FunctionDeclaration describing the signature of a function. This
 // handles the common logic shared by function declaration syntax and function
 // definition syntax.
-static auto BuildFunctionDeclaration(Context& context)
+static auto BuildFunctionDeclaration(Context& context, bool is_definition)
     -> std::pair<SemIR::FunctionId, SemIR::NodeId> {
   // TODO: This contains the IR block for the parameters and return type. At
   // present, it's just loose, but it's not strictly required for parameter
@@ -57,25 +57,56 @@ static auto BuildFunctionDeclaration(Context& context)
       context.node_stack()
           .PopForSoloParseNode<Parse::NodeKind::FunctionIntroducer>();
 
-  // TODO: Support out-of-line definitions, which will have a resolved
-  // name_context. Right now, those become errors in AddNameToLookup.
-
-  // Add the callable.
-  auto function_id = context.semantics_ir().AddFunction(
-      {.name_id = name_context.state ==
-                          DeclarationNameStack::NameContext::State::Unresolved
-                      ? name_context.unresolved_name_id
-                      : SemIR::StringId(SemIR::StringId::InvalidIndex),
-       .param_refs_id = param_refs_id,
-       .return_type_id = return_type_id,
-       .return_slot_id = return_slot_id,
-       .body_block_ids = {}});
-  auto decl_id = context.AddNode(SemIR::FunctionDeclaration(
+  // Add the function declaration.
+  auto function_decl = SemIR::FunctionDeclaration(
       fn_node, context.GetBuiltinType(SemIR::BuiltinKind::FunctionType),
-      function_id));
-  context.declaration_name_stack().AddNameToLookup(name_context, decl_id);
+      SemIR::FunctionId::Invalid);
+  auto function_decl_id = context.AddNode(function_decl);
+
+  // Check whether this is a redeclaration.
+  auto existing_id = context.declaration_name_stack().LookupOrAddName(
+      name_context, function_decl_id);
+  if (existing_id.is_valid()) {
+    if (auto existing_function_decl =
+            context.semantics_ir()
+                .GetNode(existing_id)
+                .TryAs<SemIR::FunctionDeclaration>()) {
+      // This is a redeclaration of an existing function.
+      function_decl.function_id = existing_function_decl->function_id;
+
+      // TODO: Check that the signature matches!
+
+      // Track the signature from the definition, so that IDs in the body match
+      // IDs in the signature.
+      if (is_definition) {
+        auto& function_info =
+            context.semantics_ir().GetFunction(function_decl.function_id);
+        function_info.param_refs_id = param_refs_id;
+        function_info.return_type_id = return_type_id;
+        function_info.return_slot_id = return_slot_id;
+      }
+    } else {
+      // This is a redeclaration of something other than a function.
+      context.DiagnoseDuplicateName(name_context.parse_node, existing_id);
+    }
+  }
 
-  if (SemIR::IsEntryPoint(context.semantics_ir(), function_id)) {
+  // Create a new function if this isn't a valid redeclaration.
+  if (!function_decl.function_id.is_valid()) {
+    function_decl.function_id = context.semantics_ir().AddFunction(
+        {.name_id = name_context.state ==
+                            DeclarationNameStack::NameContext::State::Unresolved
+                        ? name_context.unresolved_name_id
+                        : SemIR::StringId(SemIR::StringId::InvalidIndex),
+         .param_refs_id = param_refs_id,
+         .return_type_id = return_type_id,
+         .return_slot_id = return_slot_id});
+  }
+
+  // Write the function ID into the FunctionDeclaration.
+  context.semantics_ir().ReplaceNode(function_decl_id, function_decl);
+
+  if (SemIR::IsEntryPoint(context.semantics_ir(), function_decl.function_id)) {
     // TODO: Update this once valid signatures for the entry point are decided.
     if (!context.semantics_ir().GetNodeBlock(param_refs_id).empty() ||
         (return_slot_id.is_valid() &&
@@ -89,12 +120,12 @@ static auto BuildFunctionDeclaration(Context& context)
     }
   }
 
-  return {function_id, decl_id};
+  return {function_decl.function_id, function_decl_id};
 }
 
 auto HandleFunctionDeclaration(Context& context, Parse::Node /*parse_node*/)
     -> bool {
-  BuildFunctionDeclaration(context);
+  BuildFunctionDeclaration(context, /*is_definition=*/false);
   return true;
 }
 
@@ -127,8 +158,26 @@ auto HandleFunctionDefinition(Context& context, Parse::Node parse_node)
 auto HandleFunctionDefinitionStart(Context& context, Parse::Node parse_node)
     -> bool {
   // Process the declaration portion of the function.
-  auto [function_id, decl_id] = BuildFunctionDeclaration(context);
-  const auto& function = context.semantics_ir().GetFunction(function_id);
+  auto [function_id, decl_id] =
+      BuildFunctionDeclaration(context, /*is_definition=*/true);
+  auto& function = context.semantics_ir().GetFunction(function_id);
+
+  // Track that this declaration is the definition.
+  if (function.definition_id.is_valid()) {
+    CARBON_DIAGNOSTIC(FunctionRedefinition, Error,
+                      "Redefinition of function {0}.", llvm::StringRef);
+    CARBON_DIAGNOSTIC(FunctionPreviousDefinition, Note,
+                      "Previous definition was here.");
+    context.emitter()
+        .Build(parse_node, FunctionRedefinition,
+               context.semantics_ir().GetString(function.name_id))
+        .Note(
+            context.semantics_ir().GetNode(function.definition_id).parse_node(),
+            FunctionPreviousDefinition)
+        .Emit();
+  } else {
+    function.definition_id = decl_id;
+  }
 
   // Create the function scope and the entry block.
   context.return_scope_stack().push_back(decl_id);

+ 34 - 8
toolchain/check/handle_name.cpp

@@ -9,19 +9,45 @@
 
 namespace Carbon::Check {
 
+// Returns the name scope corresponding to base_id, or nullopt if not a scope.
+// On invalid scopes, prints a diagnostic and still returns the scope.
+static auto GetAsNameScope(Context& context, SemIR::NodeId base_id)
+    -> std::optional<SemIR::NameScopeId> {
+  auto base =
+      context.semantics_ir().GetNode(context.FollowNameReferences(base_id));
+  if (auto base_as_namespace = base.TryAs<SemIR::Namespace>()) {
+    return base_as_namespace->name_scope_id;
+  }
+  if (auto base_as_class = base.TryAs<SemIR::ClassDeclaration>()) {
+    auto& class_info = context.semantics_ir().GetClass(base_as_class->class_id);
+    if (!class_info.scope_id.is_valid()) {
+      CARBON_DIAGNOSTIC(QualifiedExpressionInIncompleteClassScope, Error,
+                        "Member access into incomplete class `{0}`.",
+                        std::string);
+      auto builder = context.emitter().Build(
+          context.semantics_ir().GetNode(base_id).parse_node(),
+          QualifiedExpressionInIncompleteClassScope,
+          context.semantics_ir().StringifyTypeExpression(base_id, true));
+      context.NoteIncompleteClass(*base_as_class, builder);
+      builder.Emit();
+    }
+    return class_info.scope_id;
+  }
+  return std::nullopt;
+}
+
 auto HandleMemberAccessExpression(Context& context, Parse::Node parse_node)
     -> bool {
   SemIR::StringId name_id = context.node_stack().Pop<Parse::NodeKind::Name>();
-
   auto base_id = context.node_stack().PopExpression();
 
-  if (auto base_namespace = context.semantics_ir()
-                                .GetNode(context.FollowNameReferences(base_id))
-                                .TryAs<SemIR::Namespace>()) {
-    // For a namespace, just resolve the name.
-    auto node_id =
-        context.LookupName(parse_node, name_id, base_namespace->name_scope_id,
-                           /*print_diagnostics=*/true);
+  // If the base is a name scope, such as a class or namespace, perform lookup
+  // into that scope.
+  if (auto name_scope_id = GetAsNameScope(context, base_id)) {
+    auto node_id = name_scope_id->is_valid()
+                       ? context.LookupName(parse_node, name_id, *name_scope_id,
+                                            /*print_diagnostics=*/true)
+                       : SemIR::NodeId::BuiltinError;
     auto node = context.semantics_ir().GetNode(node_id);
     // TODO: Track that this node was named within `base_id`.
     context.AddNodeAndPush(

+ 1 - 1
toolchain/check/testdata/array/fail_incomplete_element.carbon

@@ -20,7 +20,7 @@ var a: [Incomplete; 1];
 var p: Incomplete* = &a[0];
 
 // CHECK:STDOUT: file "fail_incomplete_element.carbon" {
-// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete
+// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete, ()
 // CHECK:STDOUT:   %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete
 // CHECK:STDOUT:   %.loc15_21: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc15_22: type = array_type %.loc15_21, Incomplete

+ 1 - 1
toolchain/check/testdata/basics/verbose.carbon

@@ -8,7 +8,7 @@
 // NOAUTOUPDATE
 // SET-CHECK-SUBSET
 // CHECK:STDERR: Node Push 0: FunctionIntroducer -> <none>
-// CHECK:STDERR: AddNode: {kind: FunctionDeclaration, arg0: function{{[0-9]+}}, type: type{{[0-9]+}}}
+// CHECK:STDERR: AddNode: {kind: FunctionDeclaration, arg0: {{.*}}, type: type{{[0-9]+}}}
 // CHECK:STDERR: node_block_stack_ Push 1
 // CHECK:STDERR: AddNode: {kind: Return}
 // CHECK:STDERR: node_block_stack_ Pop 1: block{{[0-9]+}}

+ 63 - 0
toolchain/check/testdata/class/basic.carbon

@@ -0,0 +1,63 @@
+// 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 Class {
+  fn F(n: i32) -> i32 {
+    return n;
+  }
+
+  fn G(n: i32) -> i32;
+
+  var k: i32;
+}
+
+fn Class.G(n: i32) -> i32 {
+  return n;
+}
+
+fn Run() -> i32 {
+  return Class.F(4);
+}
+
+// CHECK:STDOUT: file "basic.carbon" {
+// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT:   %G: <function> = fn_decl @G
+// CHECK:STDOUT:   %Run: <function> = fn_decl @Run
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT:   %G: <function> = fn_decl @G
+// CHECK:STDOUT:   %k: ref i32 = var "k"
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .G = %G
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%n: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %n.ref: i32 = name_reference "n", %n
+// CHECK:STDOUT:   return %n.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%n: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %n.ref: i32 = name_reference "n", %n
+// CHECK:STDOUT:   return %n.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Run() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Class.ref: type = name_reference "Class", package.%Class
+// CHECK:STDOUT:   %F.ref: <function> = name_reference "F", @Class.%F
+// CHECK:STDOUT:   %.loc22_18: i32 = int_literal 4
+// CHECK:STDOUT:   %.loc22_17.1: init i32 = call %F.ref(%.loc22_18)
+// CHECK:STDOUT:   %.loc22_17.2: ref i32 = temporary_storage
+// CHECK:STDOUT:   %.loc22_17.3: ref i32 = temporary %.loc22_17.2, %.loc22_17.1
+// CHECK:STDOUT:   %.loc22_17.4: i32 = bind_value %.loc22_17.3
+// CHECK:STDOUT:   return %.loc22_17.4
+// CHECK:STDOUT: }

+ 55 - 20
toolchain/check/testdata/class/fail_incomplete.carbon

@@ -6,10 +6,31 @@
 
 class Class;
 
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:4: ERROR: Cannot declare a member of incomplete class `Class`.
+// CHECK:STDERR: fn Class.Function() {}
+// CHECK:STDERR:    ^
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-5]]:1: Class was forward declared here.
+// CHECK:STDERR: class Class;
+// CHECK:STDERR: ^
+fn Class.Function() {}
+
+fn CallClassFunction() {
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:3: ERROR: Member access into incomplete class `Class`.
+  // CHECK:STDERR:   Class.Function();
+  // CHECK:STDERR:   ^
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-14]]:1: Class was forward declared here.
+  // CHECK:STDERR: class Class;
+  // CHECK:STDERR: ^
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+3]]:18: ERROR: Semantics TODO: `Not a callable name`.
+  // CHECK:STDERR:   Class.Function();
+  // CHECK:STDERR:                  ^
+  Class.Function();
+}
+
 // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:17: ERROR: Variable has incomplete type `Class`.
 // CHECK:STDERR: var global_var: Class;
 // CHECK:STDERR:                 ^
-// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-5]]:1: Class was forward declared here.
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-26]]:1: Class was forward declared here.
 // CHECK:STDERR: class Class;
 // CHECK:STDERR: ^
 var global_var: Class;
@@ -17,7 +38,7 @@ var global_var: Class;
 // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:24: ERROR: Function returns incomplete type `Class`.
 // CHECK:STDERR: fn ConvertFromStruct() -> Class { return {}; }
 // CHECK:STDERR:                        ^
-// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-13]]:1: Class was forward declared here.
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-34]]:1: Class was forward declared here.
 // CHECK:STDERR: class Class;
 // CHECK:STDERR: ^
 fn ConvertFromStruct() -> Class { return {}; }
@@ -31,7 +52,7 @@ fn MemberAccess(p: Class*) -> i32 {
   // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:11: ERROR: Invalid use of incomplete type `Class`.
   // CHECK:STDERR:   return (*p).n;
   // CHECK:STDERR:           ^
-  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-27]]:1: Class was forward declared here.
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-48]]:1: Class was forward declared here.
   // CHECK:STDERR: class Class;
   // CHECK:STDERR: ^
   return (*p).n;
@@ -40,7 +61,7 @@ fn MemberAccess(p: Class*) -> i32 {
 // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:20: ERROR: Function returns incomplete type `Class`.
 // CHECK:STDERR: fn Copy(p: Class*) -> Class {
 // CHECK:STDERR:                    ^
-// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-36]]:1: Class was forward declared here.
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-57]]:1: Class was forward declared here.
 // CHECK:STDERR: class Class;
 // CHECK:STDERR: ^
 fn Copy(p: Class*) -> Class {
@@ -51,7 +72,7 @@ fn Let(p: Class*) {
   // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:10: ERROR: `let` binding has incomplete type `Class`.
   // CHECK:STDERR:   let c: Class = *p;
   // CHECK:STDERR:          ^
-  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-47]]:1: Class was forward declared here.
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-68]]:1: Class was forward declared here.
   // CHECK:STDERR: class Class;
   // CHECK:STDERR: ^
   let c: Class = *p;
@@ -64,7 +85,7 @@ fn TakeIncomplete(c: Class);
 // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:23: ERROR: Function returns incomplete type `Class`.
 // CHECK:STDERR: fn ReturnIncomplete() -> Class;
 // CHECK:STDERR:                       ^
-// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-60]]:1: Class was forward declared here.
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-81]]:1: Class was forward declared here.
 // CHECK:STDERR: class Class;
 // CHECK:STDERR: ^
 fn ReturnIncomplete() -> Class;
@@ -73,7 +94,7 @@ fn CallTakeIncomplete(p: Class*) {
   // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:17: ERROR: Forming value of incomplete type `Class`.
   // CHECK:STDERR:   TakeIncomplete(*p);
   // CHECK:STDERR:                 ^
-  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-69]]:1: Class was forward declared here.
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-90]]:1: Class was forward declared here.
   // CHECK:STDERR: class Class;
   // CHECK:STDERR: ^
   // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-19]]:1: Initializing parameter 1 of function declared here.
@@ -84,7 +105,7 @@ fn CallTakeIncomplete(p: Class*) {
   // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:17: ERROR: Forming value of incomplete type `Class`.
   // CHECK:STDERR:   TakeIncomplete({});
   // CHECK:STDERR:                 ^
-  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-80]]:1: Class was forward declared here.
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-101]]:1: Class was forward declared here.
   // CHECK:STDERR: class Class;
   // CHECK:STDERR: ^
   // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-30]]:1: Initializing parameter 1 of function declared here.
@@ -98,7 +119,9 @@ fn CallReturnIncomplete() {
 }
 
 // CHECK:STDOUT: file "fail_incomplete.carbon" {
-// CHECK:STDOUT:   %Class: type = class_declaration @Class
+// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT:   %.loc15: <function> = fn_decl @.1
+// CHECK:STDOUT:   %CallClassFunction: <function> = fn_decl @CallClassFunction
 // CHECK:STDOUT:   %Class.ref: type = name_reference "Class", %Class
 // CHECK:STDOUT:   %global_var: ref <error> = var "global_var"
 // CHECK:STDOUT:   %ConvertFromStruct: <function> = fn_decl @ConvertFromStruct
@@ -113,24 +136,36 @@ fn CallReturnIncomplete() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Class;
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @CallClassFunction() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Class.ref: type = name_reference "Class", package.%Class
+// CHECK:STDOUT:   %Function.ref: <error> = name_reference "Function", <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: fn @ConvertFromStruct() -> <error> {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc23_43.1: type = struct_type {}
-// CHECK:STDOUT:   %.loc23_43.2: {} = struct_literal ()
+// CHECK:STDOUT:   %.loc44_43.1: type = struct_type {}
+// CHECK:STDOUT:   %.loc44_43.2: {} = struct_literal ()
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @MemberAccess(%p: Class*) -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %p.ref: Class* = name_reference "p", %p
-// CHECK:STDOUT:   %.loc37: ref Class = dereference %p.ref
+// CHECK:STDOUT:   %.loc58: ref Class = dereference %p.ref
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Copy(%p: Class*) -> <error> {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %p.ref: Class* = name_reference "p", %p
-// CHECK:STDOUT:   %.loc47: ref Class = dereference %p.ref
+// CHECK:STDOUT:   %.loc68: ref Class = dereference %p.ref
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -138,7 +173,7 @@ fn CallReturnIncomplete() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Class.ref: type = name_reference "Class", package.%Class
 // CHECK:STDOUT:   %p.ref: Class* = name_reference "p", %p
-// CHECK:STDOUT:   %.loc57: ref Class = dereference %p.ref
+// CHECK:STDOUT:   %.loc78: ref Class = dereference %p.ref
 // CHECK:STDOUT:   %c: <error> = bind_name "c", <error>
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
@@ -149,18 +184,18 @@ fn CallReturnIncomplete() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @CallTakeIncomplete(%p: Class*) {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %TakeIncomplete.ref.loc82: <function> = name_reference "TakeIncomplete", package.%TakeIncomplete
+// CHECK:STDOUT:   %TakeIncomplete.ref.loc103: <function> = name_reference "TakeIncomplete", package.%TakeIncomplete
 // CHECK:STDOUT:   %p.ref: Class* = name_reference "p", %p
-// CHECK:STDOUT:   %.loc82_18: ref Class = dereference %p.ref
-// CHECK:STDOUT:   %.loc82_17: type = tuple_type ()
-// CHECK:STDOUT:   %TakeIncomplete.ref.loc93: <function> = name_reference "TakeIncomplete", package.%TakeIncomplete
-// CHECK:STDOUT:   %.loc93: {} = struct_literal ()
+// CHECK:STDOUT:   %.loc103_18: ref Class = dereference %p.ref
+// CHECK:STDOUT:   %.loc103_17: type = tuple_type ()
+// CHECK:STDOUT:   %TakeIncomplete.ref.loc114: <function> = name_reference "TakeIncomplete", package.%TakeIncomplete
+// CHECK:STDOUT:   %.loc114: {} = struct_literal ()
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @CallReturnIncomplete() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %ReturnIncomplete.ref: <function> = name_reference "ReturnIncomplete", package.%ReturnIncomplete
-// CHECK:STDOUT:   %.loc97: init <error> = call %ReturnIncomplete.ref()
+// CHECK:STDOUT:   %.loc118: init <error> = call %ReturnIncomplete.ref()
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 58 - 0
toolchain/check/testdata/class/fail_redefinition.carbon

@@ -0,0 +1,58 @@
+// 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 Class {
+  fn F();
+  fn H();
+}
+
+// CHECK:STDERR: fail_redefinition.carbon:[[@LINE+6]]:13: ERROR: Redefinition of class Class.
+// CHECK:STDERR: class Class {
+// CHECK:STDERR:             ^
+// CHECK:STDERR: fail_redefinition.carbon:[[@LINE-8]]:1: Previous definition was here.
+// CHECK:STDERR: class Class {
+// CHECK:STDERR: ^
+class Class {
+  fn G();
+  fn H();
+}
+
+fn Class.F() {}
+fn Class.G() {}
+fn Class.H() {}
+
+// CHECK:STDOUT: file "fail_redefinition.carbon" {
+// CHECK:STDOUT:   %Class.loc7: type = class_declaration @Class, ()
+// CHECK:STDOUT:   %Class.loc18: type = class_declaration @Class, ()
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT:   %G: <function> = fn_decl @G
+// CHECK:STDOUT:   %H: <function> = fn_decl @H
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class {
+// CHECK:STDOUT:   %G: <function> = fn_decl @G
+// CHECK:STDOUT:   %H: <function> = fn_decl @H
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = <unexpected noderef 9>
+// CHECK:STDOUT:   .H = <unexpected noderef 10>
+// CHECK:STDOUT:   .G = %G
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @H() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }

+ 49 - 0
toolchain/check/testdata/class/fail_reorder.carbon

@@ -0,0 +1,49 @@
+// 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 Class {
+  fn G() -> i32 {
+    // TODO: This should find the member function `F` even though it's declared
+    // later.
+    // CHECK:STDERR: fail_reorder.carbon:[[@LINE+6]]:17: ERROR: Name `F` not found.
+    // CHECK:STDERR:     return Class.F();
+    // CHECK:STDERR:                 ^
+    // CHECK:STDERR: fail_reorder.carbon:[[@LINE+3]]:20: ERROR: Semantics TODO: `Not a callable name`.
+    // CHECK:STDERR:     return Class.F();
+    // CHECK:STDERR:                    ^
+    return Class.F();
+  }
+
+  fn F() -> i32 {
+    return 1;
+  }
+}
+
+// CHECK:STDOUT: file "fail_reorder.carbon" {
+// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class {
+// CHECK:STDOUT:   %G: <function> = fn_decl @G
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .G = %G
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Class.ref: type = name_reference "Class", package.%Class
+// CHECK:STDOUT:   %F.ref: <error> = name_reference "F", <error>
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc21: i32 = int_literal 1
+// CHECK:STDOUT:   return %.loc21
+// CHECK:STDOUT: }

+ 47 - 0
toolchain/check/testdata/class/fail_scope.carbon

@@ -0,0 +1,47 @@
+// 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 Class {
+  fn F() -> i32 {
+    return 1;
+  }
+
+  // TODO: This `F()` should find `Class.F`.
+  fn G() -> i32 {
+    // CHECK:STDERR: fail_scope.carbon:[[@LINE+6]]:12: ERROR: Name `F` not found.
+    // CHECK:STDERR:     return F();
+    // CHECK:STDERR:            ^
+    // CHECK:STDERR: fail_scope.carbon:[[@LINE+3]]:14: ERROR: Semantics TODO: `Not a callable name`.
+    // CHECK:STDERR:     return F();
+    // CHECK:STDERR:              ^
+    return F();
+  }
+}
+
+// CHECK:STDOUT: file "fail_scope.carbon" {
+// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT:   %G: <function> = fn_decl @G
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT:   .G = %G
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc9: i32 = int_literal 1
+// CHECK:STDOUT:   return %.loc9
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %F.ref: <error> = name_reference "F", <error>
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }

+ 1 - 1
toolchain/check/testdata/class/forward_declared.carbon

@@ -9,7 +9,7 @@ class Class;
 fn F(p: Class*) -> Class* { return p; }
 
 // CHECK:STDOUT: file "forward_declared.carbon" {
-// CHECK:STDOUT:   %Class: type = class_declaration @Class
+// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 31 - 0
toolchain/check/testdata/class/redeclaration.carbon

@@ -0,0 +1,31 @@
+// 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 Class;
+
+class Class {
+  fn F();
+}
+
+fn Class.F() {}
+
+// CHECK:STDOUT: file "redeclaration.carbon" {
+// CHECK:STDOUT:   %Class.loc7: type = class_declaration @Class, ()
+// CHECK:STDOUT:   %Class.loc9: type = class_declaration @Class, ()
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }

+ 61 - 0
toolchain/check/testdata/class/scope.carbon

@@ -0,0 +1,61 @@
+// 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 Class {
+  fn F() -> i32 {
+    return 1;
+  }
+}
+
+fn F() -> i32 {
+  return 2;
+}
+
+fn Run() -> i32 {
+  return F() + Class.F();
+}
+
+// CHECK:STDOUT: file "scope.carbon" {
+// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2
+// CHECK:STDOUT:   %Run: <function> = fn_decl @Run
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc9: i32 = int_literal 1
+// CHECK:STDOUT:   return %.loc9
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc14: i32 = int_literal 2
+// CHECK:STDOUT:   return %.loc14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Run() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %F.ref.loc18_10: <function> = name_reference "F", package.%F
+// CHECK:STDOUT:   %.loc18_11.1: init i32 = call %F.ref.loc18_10()
+// CHECK:STDOUT:   %Class.ref: type = name_reference "Class", package.%Class
+// CHECK:STDOUT:   %F.ref.loc18_21: <function> = name_reference "F", @Class.%F
+// CHECK:STDOUT:   %.loc18_23.1: init i32 = call %F.ref.loc18_21()
+// CHECK:STDOUT:   %.loc18_11.2: ref i32 = temporary_storage
+// CHECK:STDOUT:   %.loc18_11.3: ref i32 = temporary %.loc18_11.2, %.loc18_11.1
+// CHECK:STDOUT:   %.loc18_11.4: i32 = bind_value %.loc18_11.3
+// CHECK:STDOUT:   %.loc18_23.2: ref i32 = temporary_storage
+// CHECK:STDOUT:   %.loc18_23.3: ref i32 = temporary %.loc18_23.2, %.loc18_23.1
+// CHECK:STDOUT:   %.loc18_23.4: i32 = bind_value %.loc18_23.3
+// CHECK:STDOUT:   %.loc18_14: i32 = add %.loc18_11.4, %.loc18_23.4
+// CHECK:STDOUT:   return %.loc18_14
+// CHECK:STDOUT: }

+ 7 - 9
toolchain/check/testdata/namespace/fail_duplicate.carbon

@@ -9,27 +9,25 @@ namespace Foo;
 fn Foo.Baz() {
 }
 
-// CHECK:STDERR: fail_duplicate.carbon:[[@LINE+6]]:8: ERROR: Duplicate name being declared in the same scope.
+// CHECK:STDERR: fail_duplicate.carbon:[[@LINE+6]]:14: ERROR: Redefinition of function Baz.
 // CHECK:STDERR: fn Foo.Baz() {
-// CHECK:STDERR:        ^
-// CHECK:STDERR: fail_duplicate.carbon:[[@LINE-6]]:1: Name is previously declared here.
+// CHECK:STDERR:              ^
+// CHECK:STDERR: fail_duplicate.carbon:[[@LINE-6]]:1: Previous definition was here.
 // CHECK:STDERR: fn Foo.Baz() {
 // CHECK:STDERR: ^
 fn Foo.Baz() {
 }
 
 // CHECK:STDOUT: file "fail_duplicate.carbon" {
-// CHECK:STDOUT:   %.loc7: <namespace> = namespace {.Baz = %Baz}
-// CHECK:STDOUT:   %Baz: <function> = fn_decl @Baz
-// CHECK:STDOUT:   %.loc18: <function> = fn_decl @.1
+// CHECK:STDOUT:   %.loc7: <namespace> = namespace {.Baz = %Baz.loc9}
+// CHECK:STDOUT:   %Baz.loc9: <function> = fn_decl @Baz
+// CHECK:STDOUT:   %Baz.loc18: <function> = fn_decl @Baz
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Baz() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
-// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1() {
-// CHECK:STDOUT: !entry:
+// CHECK:STDOUT: !.loc19:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 1 - 1
toolchain/check/testdata/struct/fail_nested_incomplete.carbon

@@ -20,7 +20,7 @@ var s: {.a: Incomplete};
 var p: Incomplete* = &s.a;
 
 // CHECK:STDOUT: file "fail_nested_incomplete.carbon" {
-// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete
+// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete, ()
 // CHECK:STDOUT:   %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete
 // CHECK:STDOUT:   %.loc15: type = struct_type {.a: Incomplete}
 // CHECK:STDOUT:   %s: ref <error> = var "s"

+ 1 - 1
toolchain/check/testdata/tuples/fail_nested_incomplete.carbon

@@ -20,7 +20,7 @@ var t: (i32, Incomplete);
 var p: Incomplete* = &t[1];
 
 // CHECK:STDOUT: file "fail_nested_incomplete.carbon" {
-// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete
+// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete, ()
 // CHECK:STDOUT:   %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete
 // CHECK:STDOUT:   %.loc15_24.1: type = tuple_type (type, type)
 // CHECK:STDOUT:   %.loc15_24.2: (type, type) = tuple_literal (i32, %Incomplete.ref.loc15)

+ 6 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -116,9 +116,13 @@ CARBON_DIAGNOSTIC_KIND(ArrayInitFromExpressionArgCountMismatch)
 CARBON_DIAGNOSTIC_KIND(AssignmentToNonAssignable)
 CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop)
 CARBON_DIAGNOSTIC_KIND(ClassForwardDeclaredHere)
+CARBON_DIAGNOSTIC_KIND(ClassPreviousDefinition)
+CARBON_DIAGNOSTIC_KIND(ClassRedefinition)
 CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop)
 CARBON_DIAGNOSTIC_KIND(DereferenceOfNonPointer)
 CARBON_DIAGNOSTIC_KIND(DereferenceOfType)
+CARBON_DIAGNOSTIC_KIND(FunctionPreviousDefinition)
+CARBON_DIAGNOSTIC_KIND(FunctionRedefinition)
 CARBON_DIAGNOSTIC_KIND(NameNotFound)
 CARBON_DIAGNOSTIC_KIND(NameDeclarationDuplicate)
 CARBON_DIAGNOSTIC_KIND(NameDeclarationPrevious)
@@ -146,8 +150,10 @@ CARBON_DIAGNOSTIC_KIND(ReturnStatementDisallowExpression)
 CARBON_DIAGNOSTIC_KIND(ReturnStatementImplicitNote)
 CARBON_DIAGNOSTIC_KIND(ReturnStatementMissingExpression)
 CARBON_DIAGNOSTIC_KIND(ImplicitAsConversionFailure)
+CARBON_DIAGNOSTIC_KIND(QualifiedDeclarationInIncompleteClassScope)
 CARBON_DIAGNOSTIC_KIND(QualifiedDeclarationInNonScope)
 CARBON_DIAGNOSTIC_KIND(QualifiedDeclarationNonScopeEntity)
+CARBON_DIAGNOSTIC_KIND(QualifiedExpressionInIncompleteClassScope)
 CARBON_DIAGNOSTIC_KIND(QualifiedExpressionUnsupported)
 CARBON_DIAGNOSTIC_KIND(QualifiedExpressionNameNotFound)
 CARBON_DIAGNOSTIC_KIND(UseOfNonExpressionAsValue)

+ 11 - 3
toolchain/sem_ir/file.cpp

@@ -207,6 +207,7 @@ static auto GetTypePrecedence(NodeKind kind) -> int {
     case ArrayType::Kind:
     case Builtin::Kind:
     case ClassDeclaration::Kind:
+    case NameReference::Kind:
     case StructType::Kind:
     case TupleType::Kind:
       return 0;
@@ -238,7 +239,6 @@ static auto GetTypePrecedence(NodeKind kind) -> int {
     case FunctionDeclaration::Kind:
     case InitializeFrom::Kind:
     case IntegerLiteral::Kind:
-    case NameReference::Kind:
     case Namespace::Kind:
     case NoOp::Kind:
     case Parameter::Kind:
@@ -268,6 +268,12 @@ static auto GetTypePrecedence(NodeKind kind) -> int {
 
 auto File::StringifyType(TypeId type_id, bool in_type_context) const
     -> std::string {
+  return StringifyTypeExpression(GetTypeAllowBuiltinTypes(type_id),
+                                 in_type_context);
+}
+
+auto File::StringifyTypeExpression(NodeId outer_node_id,
+                                   bool in_type_context) const -> std::string {
   std::string str;
   llvm::raw_string_ostream out(str);
 
@@ -281,7 +287,6 @@ auto File::StringifyType(TypeId type_id, bool in_type_context) const
       return {.node_id = node_id, .index = index + 1};
     }
   };
-  auto outer_node_id = GetTypeAllowBuiltinTypes(type_id);
   llvm::SmallVector<Step> steps = {{.node_id = outer_node_id}};
 
   while (!steps.empty()) {
@@ -338,6 +343,10 @@ auto File::StringifyType(TypeId type_id, bool in_type_context) const
         }
         break;
       }
+      case NameReference::Kind: {
+        out << GetString(node.As<NameReference>().name_id);
+        break;
+      }
       case PointerType::Kind: {
         if (step.index == 0) {
           steps.push_back(step.Next());
@@ -414,7 +423,6 @@ auto File::StringifyType(TypeId type_id, bool in_type_context) const
       case FunctionDeclaration::Kind:
       case InitializeFrom::Kind:
       case IntegerLiteral::Kind:
-      case NameReference::Kind:
       case Namespace::Kind:
       case NoOp::Kind:
       case Parameter::Kind:

+ 21 - 1
toolchain/sem_ir/file.h

@@ -35,6 +35,9 @@ struct Function : public Printable<Function> {
 
   // The function name.
   StringId name_id;
+  // The definition, if the function has been defined or is currently being
+  // defined. This is a FunctionDeclaration.
+  NodeId definition_id = NodeId::Invalid;
   // A block containing a single reference node per parameter.
   NodeBlockId param_refs_id;
   // The return type. This will be invalid if the return type wasn't specified.
@@ -48,7 +51,7 @@ struct Function : public Printable<Function> {
   // A list of the statically reachable code blocks in the body of the
   // function, in lexical order. The first block is the entry block. This will
   // be empty for declarations that don't have a visible definition.
-  llvm::SmallVector<NodeBlockId> body_block_ids;
+  llvm::SmallVector<NodeBlockId> body_block_ids = {};
 };
 
 // A class.
@@ -60,6 +63,17 @@ struct Class : public Printable<Class> {
 
   // The class name.
   StringId name_id;
+
+  // The definition, if the class has been defined or is currently being
+  // defined. 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;
 };
 
 // TODO: Replace this with a Rational type, per the design:
@@ -428,6 +442,12 @@ class File : public Printable<File> {
   auto StringifyType(TypeId type_id, bool in_type_context = false) const
       -> std::string;
 
+  // Same as `StringifyType`, but starting with a node representing a type
+  // expression rather than a canonical type.
+  auto StringifyTypeExpression(NodeId outer_node_id,
+                               bool in_type_context = false) const
+      -> std::string;
+
   auto functions_size() const -> int { return functions_.size(); }
   auto classes_size() const -> int { return classes_.size(); }
   auto nodes_size() const -> int { return nodes_.size(); }

+ 35 - 22
toolchain/sem_ir/formatter.cpp

@@ -89,7 +89,8 @@ class NodeNamer {
           class_info.name_id.is_valid()
               ? semantics_ir.GetString(class_info.name_id).str()
               : "");
-      // TODO: Handle names declared in the class scope.
+      AddBlockLabel(class_scope, class_info.body_block_id, "class", class_loc);
+      CollectNamesInBlock(class_scope, class_info.body_block_id);
     }
   }
 
@@ -509,9 +510,18 @@ class Formatter {
 
     out_ << "\nclass ";
     FormatClassName(id);
-    // TODO: Format class definitions.
-    (void)class_info;
-    out_ << ";\n";
+
+    llvm::SaveAndRestore class_scope(scope_, node_namer_.GetScopeFor(id));
+
+    if (class_info.scope_id.is_valid()) {
+      out_ << " {\n";
+      FormatCodeBlock(class_info.body_block_id);
+      out_ << "\n!members:";
+      FormatNameScope(class_info.scope_id, "", "\n  .");
+      out_ << "\n}\n";
+    } else {
+      out_ << ";\n";
+    }
   }
 
   auto FormatFunction(FunctionId id) -> void {
@@ -572,6 +582,26 @@ class Formatter {
     }
   }
 
+  auto FormatNameScope(NameScopeId id, llvm::StringRef separator,
+                       llvm::StringRef prefix) -> void {
+    // Name scopes aren't kept in any particular order. Sort the entries before
+    // we print them for stability and consistency.
+    llvm::SmallVector<std::pair<NodeId, StringId>> entries;
+    for (auto [name_id, node_id] : semantics_ir_.GetNameScope(id)) {
+      entries.push_back({node_id, name_id});
+    }
+    llvm::sort(entries,
+               [](auto a, auto b) { return a.first.index < b.first.index; });
+
+    llvm::ListSeparator sep(separator);
+    for (auto [node_id, name_id] : entries) {
+      out_ << sep << prefix;
+      FormatString(name_id);
+      out_ << " = ";
+      FormatNodeName(node_id);
+    }
+  }
+
   auto FormatInstruction(NodeId node_id) -> void {
     if (!node_id.is_valid()) {
       Indent();
@@ -788,26 +818,9 @@ class Formatter {
 
   auto FormatArg(MemberIndex index) -> void { out_ << index; }
 
-  // TODO: Should we be printing scopes inline, or should we have a separate
-  // step to print them like we do for functions?
   auto FormatArg(NameScopeId id) -> void {
-    // Name scopes aren't kept in any particular order. Sort the entries before
-    // we print them for stability and consistency.
-    std::vector<std::pair<NodeId, StringId>> entries;
-    for (auto [name_id, node_id] : semantics_ir_.GetNameScope(id)) {
-      entries.push_back({node_id, name_id});
-    }
-    llvm::sort(entries,
-               [](auto a, auto b) { return a.first.index < b.first.index; });
-
     out_ << '{';
-    llvm::ListSeparator sep;
-    for (auto [node_id, name_id] : entries) {
-      out_ << sep << ".";
-      FormatString(name_id);
-      out_ << " = ";
-      FormatNodeName(node_id);
-    }
+    FormatNameScope(id, ", ", ".");
     out_ << '}';
   }
 

+ 13 - 0
toolchain/sem_ir/node.h

@@ -56,6 +56,9 @@ constexpr NodeId NodeId::Invalid = NodeId(NodeId::InvalidIndex);
 
 // The ID of a function.
 struct FunctionId : public IndexBase, public Printable<FunctionId> {
+  // An explicitly invalid function ID.
+  static const FunctionId Invalid;
+
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "function";
@@ -63,8 +66,13 @@ struct FunctionId : public IndexBase, public Printable<FunctionId> {
   }
 };
 
+constexpr FunctionId FunctionId::Invalid = FunctionId(FunctionId::InvalidIndex);
+
 // The ID of a class.
 struct ClassId : public IndexBase, public Printable<ClassId> {
+  // An explicitly invalid class ID.
+  static const ClassId Invalid;
+
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "class";
@@ -72,6 +80,8 @@ struct ClassId : public IndexBase, public Printable<ClassId> {
   }
 };
 
+constexpr ClassId ClassId::Invalid = ClassId(ClassId::InvalidIndex);
+
 // The ID of a cross-referenced IR.
 struct CrossReferenceIRId : public IndexBase,
                             public Printable<CrossReferenceIRId> {
@@ -323,6 +333,9 @@ struct Call {
 
 struct ClassDeclaration {
   ClassId class_id;
+  // The declaration block, containing the class name's qualifiers and the
+  // class's generic parameters.
+  NodeBlockId decl_block_id;
 };
 
 struct ConstType {