From 7d9340880ea2bcf8ea371e1a35bd9ddd658d3c24 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 23 Oct 2023 18:26:44 -0700 Subject: [PATCH] Separate `ClassType` from `ClassDeclaration`. (#3329) Retain the `ClassDeclaration` node to represent a syntactic declaration of a class (including possibly a declaration of a generic class), but use a separate SemIR node to represent the class type itself. This allows us to give the two separate treatment. The `ClassDeclaration` is still entered into the name lookup table for its enclosing scope, but when it is named in an expression, the class type is produced instead. When the class declaration is named in a declaration name, it can be used to define members of the class, but an expression that resolves to the class type cannot be used to define members of the class. In order to distinguish these cases, use `Name` rather than `NameExpression` for the left-hand side of a `QualifiedName` parse node. This removes the only use of the `Expression` form of a declaration name, so that is also removed. In the future, `ClassType` will also be used to describe types such as `Vector(T)`, for which there is no corresponding `ClassDeclaration`. --- toolchain/check/context.cpp | 20 ++++--- toolchain/check/context.h | 4 +- toolchain/check/declaration_name_stack.cpp | 30 +++------- toolchain/check/declaration_name_stack.h | 12 +--- toolchain/check/handle_class.cpp | 18 ++++-- toolchain/check/handle_name.cpp | 59 +++++++++++-------- .../array/fail_incomplete_element.carbon | 3 +- toolchain/check/testdata/class/basic.carbon | 3 +- .../testdata/class/fail_incomplete.carbon | 3 +- .../testdata/class/fail_member_of_let.carbon | 44 ++++++++++++++ .../testdata/class/fail_redefinition.carbon | 9 +-- .../check/testdata/class/fail_reorder.carbon | 3 +- .../check/testdata/class/fail_scope.carbon | 3 +- .../testdata/class/forward_declared.carbon | 3 +- .../check/testdata/class/redeclaration.carbon | 5 +- toolchain/check/testdata/class/scope.carbon | 3 +- .../check/testdata/namespace/nested.carbon | 1 - .../struct/fail_nested_incomplete.carbon | 3 +- .../tuples/fail_nested_incomplete.carbon | 3 +- toolchain/lower/handle_type.cpp | 5 ++ .../handle_declaration_name_and_params.cpp | 14 +++-- toolchain/parse/node_kind.def | 2 +- .../namespace/fail_incomplete_name.carbon | 2 +- .../parse/testdata/namespace/nested.carbon | 4 +- toolchain/sem_ir/file.cpp | 9 ++- toolchain/sem_ir/file.h | 7 ++- toolchain/sem_ir/formatter.cpp | 4 +- toolchain/sem_ir/node_kind.def | 1 + toolchain/sem_ir/typed_nodes.h | 14 ++++- 29 files changed, 186 insertions(+), 105 deletions(-) create mode 100644 toolchain/check/testdata/class/fail_member_of_let.carbon diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 50ed030a0618..3a09f9b12459 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -89,11 +89,13 @@ auto Context::DiagnoseNameNotFound(Parse::Node parse_node, StringId name_id) semantics_ir_->strings().Get(name_id)); } -auto Context::NoteIncompleteClass(SemIR::ClassDeclaration class_decl, +auto Context::NoteIncompleteClass(SemIR::ClassId class_id, DiagnosticBuilder& builder) -> void { CARBON_DIAGNOSTIC(ClassForwardDeclaredHere, Note, "Class was forward declared here."); - builder.Note(class_decl.parse_node, ClassForwardDeclaredHere); + const auto& class_info = semantics_ir().classes().Get(class_id); + builder.Note(semantics_ir().GetNode(class_info.declaration_id).parse_node(), + ClassForwardDeclaredHere); } auto Context::AddNameToLookup(Parse::Node name_node, StringId name_id, @@ -410,12 +412,12 @@ class TypeCompleter { } break; - case SemIR::ClassDeclaration::Kind: + case SemIR::ClassType::Kind: // TODO: Support class definitions and complete class types. if (diagnoser_) { auto builder = (*diagnoser_)(); - context_.NoteIncompleteClass(type_node.As(), - builder); + context_.NoteIncompleteClass( + type_node.As().class_id, builder); builder.Emit(); } return false; @@ -610,6 +612,7 @@ class TypeCompleter { case SemIR::BranchIf::Kind: case SemIR::BranchWithArg::Kind: case SemIR::Call::Kind: + case SemIR::ClassDeclaration::Kind: case SemIR::Dereference::Kind: case SemIR::FunctionDeclaration::Kind: case SemIR::InitializeFrom::Kind: @@ -659,7 +662,7 @@ class TypeCompleter { return BuildTupleTypeValueRepresentation(type_id, node.As()); - case SemIR::ClassDeclaration::Kind: + case SemIR::ClassType::Kind: // TODO: Support class definitions and complete class types. CARBON_FATAL() << "Class types are currently never complete"; @@ -760,9 +763,8 @@ static auto ProfileType(Context& semantics_context, SemIR::Node node, case SemIR::Builtin::Kind: canonical_id.AddInteger(node.As().builtin_kind.AsInt()); break; - case SemIR::ClassDeclaration::Kind: - canonical_id.AddInteger( - node.As().class_id.index); + case SemIR::ClassType::Kind: + canonical_id.AddInteger(node.As().class_id.index); break; case SemIR::CrossReference::Kind: { // TODO: Cross-references should be canonicalized by looking at their diff --git a/toolchain/check/context.h b/toolchain/check/context.h index de8cf5d27139..fb140a7a0ddc 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -66,8 +66,8 @@ class Context { auto DiagnoseNameNotFound(Parse::Node parse_node, 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; + auto NoteIncompleteClass(SemIR::ClassId class_id, DiagnosticBuilder& builder) + -> void; // Pushes a new scope onto scope_stack_. auto PushScope(SemIR::NameScopeId scope_id = SemIR::NameScopeId::Invalid) diff --git a/toolchain/check/declaration_name_stack.cpp b/toolchain/check/declaration_name_stack.cpp index b44eced9626e..febe44081be9 100644 --- a/toolchain/check/declaration_name_stack.cpp +++ b/toolchain/check/declaration_name_stack.cpp @@ -85,26 +85,6 @@ auto DeclarationNameStack::AddNameToLookup(NameContext name_context, } } -auto DeclarationNameStack::ApplyExpressionQualifier(Parse::Node parse_node, - SemIR::NodeId node_id) - -> void { - auto& name_context = declaration_name_stack_.back(); - if (CanResolveQualifier(name_context, parse_node)) { - if (node_id == SemIR::NodeId::BuiltinError) { - // The input node is an error, so error the context. - name_context.state = NameContext::State::Error; - return; - } - - // For other nodes, we expect a regular resolved node, for example a - // namespace or generic type. Store it and continue for the target scope - // update. - name_context.resolved_node_id = node_id; - - UpdateScopeIfNeeded(name_context); - } -} - auto DeclarationNameStack::ApplyNameQualifier(Parse::Node parse_node, StringId name_id) -> void { ApplyNameQualifierTo(declaration_name_stack_.back(), parse_node, name_id); @@ -193,9 +173,13 @@ auto DeclarationNameStack::CanResolveQualifier(NameContext& name_context, 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); + context_->semantics_ir().StringifyType( + context_->semantics_ir() + .classes() + .Get(class_decl->class_id) + .self_type_id, + true)); + context_->NoteIncompleteClass(class_decl->class_id, builder); builder.Emit(); } else { CARBON_DIAGNOSTIC( diff --git a/toolchain/check/declaration_name_stack.h b/toolchain/check/declaration_name_stack.h index f28f6760cc5e..0968e8db6a65 100644 --- a/toolchain/check/declaration_name_stack.h +++ b/toolchain/check/declaration_name_stack.h @@ -15,10 +15,9 @@ class Context; // Provides support and stacking for qualified declaration name handling. // -// A qualified declaration name will consist of entries which are either -// Identifiers or full expressions. Expressions are expected to resolve to -// types, such as how `fn Vector(i32).Clear() { ... }` uses the expression -// `Vector(i32)` to indicate the type whose member is being declared. +// A qualified declaration name will consist of entries, which are `Name`s +// optionally followed by generic parameter lists, such as `Vector(T:! type)` +// in `fn Vector(T:! type).Clear();`, but parameter lists aren't supported yet. // Identifiers such as `Clear` will be resolved to a name if possible, for // example when declaring things that are in a non-generic type or namespace, // and are otherwise marked as an unresolved identifier. @@ -111,11 +110,6 @@ class DeclarationNameStack { auto MakeUnqualifiedName(Parse::Node parse_node, StringId name_id) -> NameContext; - // Applies an expression from the node stack to the top of the declaration - // name stack. - auto ApplyExpressionQualifier(Parse::Node parse_node, SemIR::NodeId node_id) - -> void; - // Applies a Name from the node stack to the top of the declaration name // stack. auto ApplyNameQualifier(Parse::Node parse_node, StringId name_id) -> void; diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 4cd1cf6cda88..ebc0b94bcd95 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -26,9 +26,8 @@ static auto BuildClassDeclaration(Context& context) auto decl_block_id = 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 = SemIR::ClassDeclaration{ + class_keyword, SemIR::ClassId::Invalid, decl_block_id}; auto class_decl_id = context.AddNode(class_decl); // Check whether this is a redeclaration. @@ -55,7 +54,18 @@ static auto BuildClassDeclaration(Context& context) {.name_id = name_context.state == DeclarationNameStack::NameContext::State::Unresolved ? name_context.unresolved_name_id - : StringId::Invalid}); + : StringId::Invalid, + // `.self_type_id` depends on `class_id`, so is set below. + .self_type_id = SemIR::TypeId::Invalid, + .declaration_id = class_decl_id}); + + // Build the `Self` type. + auto& class_info = + context.semantics_ir().classes().Get(class_decl.class_id); + class_info.self_type_id = + context.CanonicalizeType(context.AddNode(SemIR::ClassType{ + class_keyword, context.GetBuiltinType(SemIR::BuiltinKind::TypeType), + class_decl.class_id})); } // Write the class ID into the ClassDeclaration. diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index 48508ad6fb50..f074a8164fef 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -18,7 +18,7 @@ static auto GetAsNameScope(Context& context, SemIR::NodeId base_id) if (auto base_as_namespace = base.TryAs()) { return base_as_namespace->name_scope_id; } - if (auto base_as_class = base.TryAs()) { + if (auto base_as_class = base.TryAs()) { auto& class_info = context.semantics_ir().classes().Get(base_as_class->class_id); if (!class_info.scope_id.is_valid()) { @@ -29,7 +29,7 @@ static auto GetAsNameScope(Context& context, SemIR::NodeId base_id) context.semantics_ir().GetNode(base_id).parse_node(), QualifiedExpressionInIncompleteClassScope, context.semantics_ir().StringifyTypeExpression(base_id, true)); - context.NoteIncompleteClass(*base_as_class, builder); + context.NoteIncompleteClass(base_as_class->class_id, builder); builder.Emit(); } return class_info.scope_id; @@ -125,7 +125,17 @@ auto HandleNameExpression(Context& context, Parse::Node parse_node) -> bool { context.LookupName(parse_node, name_id, SemIR::NameScopeId::Invalid, /*print_diagnostics=*/true); auto value = context.semantics_ir().GetNode(value_id); - // This is a reference to a name binding that has a value and a type. + + // If lookup finds a class declaration, the value is its `Self` type. + if (auto class_decl = value.TryAs()) { + value_id = context.semantics_ir().GetTypeAllowBuiltinTypes( + context.semantics_ir() + .classes() + .Get(class_decl->class_id) + .self_type_id); + value = context.semantics_ir().GetNode(value_id); + } + CARBON_CHECK(value.kind().value_kind() == SemIR::NodeValueKind::Typed); context.AddNodeAndPush( parse_node, @@ -135,34 +145,33 @@ auto HandleNameExpression(Context& context, Parse::Node parse_node) -> bool { auto HandleQualifiedDeclaration(Context& context, Parse::Node parse_node) -> bool { - auto pop_and_apply_first_child = [&]() { - if (context.parse_tree().node_kind(context.node_stack().PeekParseNode()) != - Parse::NodeKind::QualifiedDeclaration) { - // First QualifiedDeclaration in a chain. - auto [parse_node1, node_id1] = - context.node_stack().PopExpressionWithParseNode(); - context.declaration_name_stack().ApplyExpressionQualifier( - parse_node1, context.FollowNameReferences(node_id1)); + auto [parse_node2, name_id2] = + context.node_stack().PopWithParseNode(); + + Parse::Node parse_node1 = context.node_stack().PeekParseNode(); + switch (context.parse_tree().node_kind(parse_node1)) { + case Parse::NodeKind::QualifiedDeclaration: + // This is the second or subsequent QualifiedDeclaration in a chain. + // Nothing to do: the first QualifiedDeclaration remains as a + // bracketing node for later QualifiedDeclarations. + break; + + case Parse::NodeKind::Name: { + // This is the first QualifiedDeclaration in a chain, and starts with a + // name. + auto name_id = context.node_stack().Pop(); + context.declaration_name_stack().ApplyNameQualifier(parse_node1, name_id); // Add the QualifiedDeclaration so that it can be used for bracketing. context.node_stack().Push(parse_node); - } else { - // Nothing to do: the QualifiedDeclaration remains as a bracketing node - // for later QualifiedDeclarations. + break; } - }; - Parse::Node parse_node2 = context.node_stack().PeekParseNode(); - if (context.parse_tree().node_kind(parse_node2) == Parse::NodeKind::Name) { - StringId name_id2 = context.node_stack().Pop(); - pop_and_apply_first_child(); - context.declaration_name_stack().ApplyNameQualifier(parse_node2, name_id2); - } else { - SemIR::NodeId node_id2 = context.node_stack().PopExpression(); - pop_and_apply_first_child(); - context.declaration_name_stack().ApplyExpressionQualifier(parse_node2, - node_id2); + default: + CARBON_FATAL() << "Unexpected node kind on left side of qualified " + "declaration name"; } + context.declaration_name_stack().ApplyNameQualifier(parse_node2, name_id2); return true; } diff --git a/toolchain/check/testdata/array/fail_incomplete_element.carbon b/toolchain/check/testdata/array/fail_incomplete_element.carbon index 595932741177..02e4733e9012 100644 --- a/toolchain/check/testdata/array/fail_incomplete_element.carbon +++ b/toolchain/check/testdata/array/fail_incomplete_element.carbon @@ -20,7 +20,8 @@ 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: class_declaration @Incomplete, () +// CHECK:STDOUT: %Incomplete: type = class_type @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 diff --git a/toolchain/check/testdata/class/basic.carbon b/toolchain/check/testdata/class/basic.carbon index bc9ab297d035..769533622a14 100644 --- a/toolchain/check/testdata/class/basic.carbon +++ b/toolchain/check/testdata/class/basic.carbon @@ -23,7 +23,8 @@ fn Run() -> i32 { } // CHECK:STDOUT: file "basic.carbon" { -// CHECK:STDOUT: %Class: type = class_declaration @Class, () +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class // CHECK:STDOUT: %G: = fn_decl @G // CHECK:STDOUT: %Run: = fn_decl @Run // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_incomplete.carbon b/toolchain/check/testdata/class/fail_incomplete.carbon index 2c1f6c64720d..53e4f64be904 100644 --- a/toolchain/check/testdata/class/fail_incomplete.carbon +++ b/toolchain/check/testdata/class/fail_incomplete.carbon @@ -119,7 +119,8 @@ fn CallReturnIncomplete() { } // CHECK:STDOUT: file "fail_incomplete.carbon" { -// CHECK:STDOUT: %Class: type = class_declaration @Class, () +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class // CHECK:STDOUT: %.loc15: = fn_decl @.1 // CHECK:STDOUT: %CallClassFunction: = fn_decl @CallClassFunction // CHECK:STDOUT: %Class.ref: type = name_reference "Class", %Class diff --git a/toolchain/check/testdata/class/fail_member_of_let.carbon b/toolchain/check/testdata/class/fail_member_of_let.carbon new file mode 100644 index 000000000000..43358a8aed72 --- /dev/null +++ b/toolchain/check/testdata/class/fail_member_of_let.carbon @@ -0,0 +1,44 @@ +// 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; +} + +// TODO: Use `:!` here once it is available. +let T: type = Class; + +// The class name is required to be written in the same way as in the class +// declaration. An expression that evaluates to the class name is not accepted. +// CHECK:STDERR: fail_member_of_let.carbon:[[@LINE+6]]:6: ERROR: Declaration qualifiers are only allowed for entities that provide a scope. +// CHECK:STDERR: fn T.F() {} +// CHECK:STDERR: ^ +// CHECK:STDERR: fail_member_of_let.carbon:[[@LINE+3]]:4: Non-scope entity referenced here. +// CHECK:STDERR: fn T.F() {} +// CHECK:STDERR: ^ +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: %Class.ref: type = name_reference "Class", %Class +// CHECK:STDOUT: %T: type = bind_name "T", %Class.ref +// CHECK:STDOUT: %.loc22: = fn_decl @.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @.1() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_redefinition.carbon b/toolchain/check/testdata/class/fail_redefinition.carbon index d8e8670e9182..fd0a289358d2 100644 --- a/toolchain/check/testdata/class/fail_redefinition.carbon +++ b/toolchain/check/testdata/class/fail_redefinition.carbon @@ -25,8 +25,9 @@ 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: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: class_declaration @Class, () // CHECK:STDOUT: %F: = fn_decl @F // CHECK:STDOUT: %G: = fn_decl @G // CHECK:STDOUT: %H: = fn_decl @H @@ -37,8 +38,8 @@ fn Class.H() {} // CHECK:STDOUT: %H: = fn_decl @H // CHECK:STDOUT: // CHECK:STDOUT: !members: -// CHECK:STDOUT: .F = -// CHECK:STDOUT: .H = +// CHECK:STDOUT: .F = +// CHECK:STDOUT: .H = // CHECK:STDOUT: .G = %G // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_reorder.carbon b/toolchain/check/testdata/class/fail_reorder.carbon index fdd1d62c636d..e323f8b58200 100644 --- a/toolchain/check/testdata/class/fail_reorder.carbon +++ b/toolchain/check/testdata/class/fail_reorder.carbon @@ -23,7 +23,8 @@ class Class { } // CHECK:STDOUT: file "fail_reorder.carbon" { -// CHECK:STDOUT: %Class: type = class_declaration @Class, () +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Class { diff --git a/toolchain/check/testdata/class/fail_scope.carbon b/toolchain/check/testdata/class/fail_scope.carbon index cfcb9695b7be..c5bb6346719c 100644 --- a/toolchain/check/testdata/class/fail_scope.carbon +++ b/toolchain/check/testdata/class/fail_scope.carbon @@ -22,7 +22,8 @@ class Class { } // CHECK:STDOUT: file "fail_scope.carbon" { -// CHECK:STDOUT: %Class: type = class_declaration @Class, () +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Class { diff --git a/toolchain/check/testdata/class/forward_declared.carbon b/toolchain/check/testdata/class/forward_declared.carbon index 36a8660dec0a..acdb2d9d0925 100644 --- a/toolchain/check/testdata/class/forward_declared.carbon +++ b/toolchain/check/testdata/class/forward_declared.carbon @@ -9,7 +9,8 @@ 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_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class // CHECK:STDOUT: %F: = fn_decl @F // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/redeclaration.carbon b/toolchain/check/testdata/class/redeclaration.carbon index b627f0a4e067..53ac64e8199b 100644 --- a/toolchain/check/testdata/class/redeclaration.carbon +++ b/toolchain/check/testdata/class/redeclaration.carbon @@ -13,8 +13,9 @@ class Class { 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: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: class_declaration @Class, () // 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 1ff3e94b5c61..8cc73a243626 100644 --- a/toolchain/check/testdata/class/scope.carbon +++ b/toolchain/check/testdata/class/scope.carbon @@ -19,7 +19,8 @@ fn Run() -> i32 { } // CHECK:STDOUT: file "scope.carbon" { -// CHECK:STDOUT: %Class: type = class_declaration @Class, () +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class // CHECK:STDOUT: %F: = fn_decl @F.2 // CHECK:STDOUT: %Run: = fn_decl @Run // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/namespace/nested.carbon b/toolchain/check/testdata/namespace/nested.carbon index 38c949181b4f..553e7e5e63e7 100644 --- a/toolchain/check/testdata/namespace/nested.carbon +++ b/toolchain/check/testdata/namespace/nested.carbon @@ -16,7 +16,6 @@ fn Foo.Bar.Baz() { // CHECK:STDOUT: file "nested.carbon" { // CHECK:STDOUT: %.loc7: = namespace {.Bar = %.loc8} -// CHECK:STDOUT: %Foo.ref: = name_reference "Foo", %.loc7 // CHECK:STDOUT: %.loc8: = namespace {.Wiz = %Wiz, .Baz = %Baz} // CHECK:STDOUT: %Wiz: = fn_decl @Wiz // CHECK:STDOUT: %Baz: = fn_decl @Baz diff --git a/toolchain/check/testdata/struct/fail_nested_incomplete.carbon b/toolchain/check/testdata/struct/fail_nested_incomplete.carbon index c77aa347f3f6..d5684b98b6bf 100644 --- a/toolchain/check/testdata/struct/fail_nested_incomplete.carbon +++ b/toolchain/check/testdata/struct/fail_nested_incomplete.carbon @@ -20,7 +20,8 @@ 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: class_declaration @Incomplete, () +// CHECK:STDOUT: %Incomplete: type = class_type @Incomplete // CHECK:STDOUT: %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete // CHECK:STDOUT: %.loc15: type = struct_type {.a: Incomplete} // CHECK:STDOUT: %s: ref = var "s" diff --git a/toolchain/check/testdata/tuples/fail_nested_incomplete.carbon b/toolchain/check/testdata/tuples/fail_nested_incomplete.carbon index 39fd650bf6b6..745cfc471c37 100644 --- a/toolchain/check/testdata/tuples/fail_nested_incomplete.carbon +++ b/toolchain/check/testdata/tuples/fail_nested_incomplete.carbon @@ -20,7 +20,8 @@ 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: class_declaration @Incomplete, () +// CHECK:STDOUT: %Incomplete: type = class_type @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) diff --git a/toolchain/lower/handle_type.cpp b/toolchain/lower/handle_type.cpp index 4418fe6fe3dc..a5f69d06c972 100644 --- a/toolchain/lower/handle_type.cpp +++ b/toolchain/lower/handle_type.cpp @@ -11,6 +11,11 @@ auto HandleArrayType(FunctionContext& context, SemIR::NodeId node_id, context.SetLocal(node_id, context.GetTypeAsValue()); } +auto HandleClassType(FunctionContext& context, SemIR::NodeId node_id, + SemIR::ClassType /*node*/) -> void { + context.SetLocal(node_id, context.GetTypeAsValue()); +} + auto HandleConstType(FunctionContext& context, SemIR::NodeId node_id, SemIR::ConstType /*node*/) -> void { context.SetLocal(node_id, context.GetTypeAsValue()); diff --git a/toolchain/parse/handle_declaration_name_and_params.cpp b/toolchain/parse/handle_declaration_name_and_params.cpp index 2b220c3c7fed..c3f9cb5aa890 100644 --- a/toolchain/parse/handle_declaration_name_and_params.cpp +++ b/toolchain/parse/handle_declaration_name_and_params.cpp @@ -17,10 +17,7 @@ static auto HandleDeclarationNameAndParams(Context& context, State after_name) context.PushState(state); if (context.PositionIs(Lex::TokenKind::Period)) { - // Because there's a qualifier, we process the first segment as an - // expression for simplicity. This just means semantics has one less thing - // to handle here. - context.AddLeafNode(NodeKind::NameExpression, *identifier); + context.AddLeafNode(NodeKind::Name, *identifier); state.state = State::PeriodAsDeclaration; context.PushState(state); } else { @@ -71,6 +68,15 @@ static auto HandleDeclarationNameAndParamsAfterName(Context& context, return; } + // TODO: We can have a parameter list after a name qualifier, regardless of + // whether the entity itself permits or requires parameters: + // + // fn Class(T:! type).AnotherClass(U:! type).Function(v: T) {} + // + // We should retain a `DeclarationNameAndParams...` state on the stack in all + // cases below to check for a period after a parameter list, which indicates + // that we've not finished parsing the declaration name. + if (params == Params::None) { return; } diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index ccd904aa02c5..ccadf025a9db 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -354,7 +354,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(CallExpression, CallExpressionStart, CARBON_TOKEN(CloseParen)) // A qualified declaration, such as `a.b`: -// _external_: NameExpression or QualifiedDeclaration +// _external_: Name or QualifiedDeclaration // _external_: Name // QualifiedDeclaration // diff --git a/toolchain/parse/testdata/namespace/fail_incomplete_name.carbon b/toolchain/parse/testdata/namespace/fail_incomplete_name.carbon index 0611c683b325..71a00394ccec 100644 --- a/toolchain/parse/testdata/namespace/fail_incomplete_name.carbon +++ b/toolchain/parse/testdata/namespace/fail_incomplete_name.carbon @@ -13,7 +13,7 @@ namespace Foo.; // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, -// CHECK:STDOUT: {kind: 'NameExpression', text: 'Foo'}, +// CHECK:STDOUT: {kind: 'Name', text: 'Foo'}, // CHECK:STDOUT: {kind: 'Name', text: ';', has_error: yes}, // CHECK:STDOUT: {kind: 'QualifiedDeclaration', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 5}, diff --git a/toolchain/parse/testdata/namespace/nested.carbon b/toolchain/parse/testdata/namespace/nested.carbon index 22eb30a83449..c42ac8942a9e 100644 --- a/toolchain/parse/testdata/namespace/nested.carbon +++ b/toolchain/parse/testdata/namespace/nested.carbon @@ -18,12 +18,12 @@ fn Foo.Bar.Baz() { // CHECK:STDOUT: {kind: 'Name', text: 'Foo'}, // CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 3}, // CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, -// CHECK:STDOUT: {kind: 'NameExpression', text: 'Foo'}, +// CHECK:STDOUT: {kind: 'Name', text: 'Foo'}, // CHECK:STDOUT: {kind: 'Name', text: 'Bar'}, // CHECK:STDOUT: {kind: 'QualifiedDeclaration', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 5}, // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'NameExpression', text: 'Foo'}, +// CHECK:STDOUT: {kind: 'Name', text: 'Foo'}, // CHECK:STDOUT: {kind: 'Name', text: 'Bar'}, // CHECK:STDOUT: {kind: 'QualifiedDeclaration', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'Name', text: 'Baz'}, diff --git a/toolchain/sem_ir/file.cpp b/toolchain/sem_ir/file.cpp index 2d1c5be5e5c1..81dcfb74eb55 100644 --- a/toolchain/sem_ir/file.cpp +++ b/toolchain/sem_ir/file.cpp @@ -196,6 +196,7 @@ static auto GetTypePrecedence(NodeKind kind) -> int { case NameReference::Kind: case StructType::Kind: case TupleType::Kind: + case ClassType::Kind: return 0; case ConstType::Kind: return -1; @@ -304,9 +305,9 @@ auto File::StringifyTypeExpression(NodeId outer_node_id, } break; } - case ClassDeclaration::Kind: { + case ClassType::Kind: { auto class_name_id = - classes().Get(node.As().class_id).name_id; + classes().Get(node.As().class_id).name_id; out << strings().Get(class_name_id); break; } @@ -405,6 +406,7 @@ auto File::StringifyTypeExpression(NodeId outer_node_id, case BranchWithArg::Kind: case Builtin::Kind: case Call::Kind: + case ClassDeclaration::Kind: case CrossReference::Kind: case Dereference::Kind: case FunctionDeclaration::Kind: @@ -466,6 +468,7 @@ auto GetExpressionCategory(const File& file, NodeId node_id) case Branch::Kind: case BranchIf::Kind: case BranchWithArg::Kind: + case ClassDeclaration::Kind: case FunctionDeclaration::Kind: case Namespace::Kind: case NoOp::Kind: @@ -492,7 +495,7 @@ auto GetExpressionCategory(const File& file, NodeId node_id) case BindValue::Kind: case BlockArg::Kind: case BoolLiteral::Kind: - case ClassDeclaration::Kind: + case ClassType::Kind: case ConstType::Kind: case IntegerLiteral::Kind: case Parameter::Kind: diff --git a/toolchain/sem_ir/file.h b/toolchain/sem_ir/file.h index 84a660a2d06e..3758d0968e5e 100644 --- a/toolchain/sem_ir/file.h +++ b/toolchain/sem_ir/file.h @@ -64,14 +64,15 @@ struct Class : public Printable { // 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. 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; diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index d49f44b59b64..50495f815dfa 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -424,9 +424,9 @@ class NodeNamer { .name_id); continue; } - case ClassDeclaration::Kind: { + case ClassType::Kind: { add_node_name_id(semantics_ir_.classes() - .Get(node.As().class_id) + .Get(node.As().class_id) .name_id); continue; } diff --git a/toolchain/sem_ir/node_kind.def b/toolchain/sem_ir/node_kind.def index fa7f8ce1d98d..699671445f91 100644 --- a/toolchain/sem_ir/node_kind.def +++ b/toolchain/sem_ir/node_kind.def @@ -33,6 +33,7 @@ CARBON_SEM_IR_NODE_KIND(BranchWithArg) CARBON_SEM_IR_NODE_KIND(Builtin) CARBON_SEM_IR_NODE_KIND(Call) CARBON_SEM_IR_NODE_KIND(ClassDeclaration) +CARBON_SEM_IR_NODE_KIND(ClassType) CARBON_SEM_IR_NODE_KIND(ConstType) CARBON_SEM_IR_NODE_KIND(CrossReference) CARBON_SEM_IR_NODE_KIND(Dereference) diff --git a/toolchain/sem_ir/typed_nodes.h b/toolchain/sem_ir/typed_nodes.h index 83218f1e20b1..031c6d1fc082 100644 --- a/toolchain/sem_ir/typed_nodes.h +++ b/toolchain/sem_ir/typed_nodes.h @@ -180,13 +180,25 @@ struct ClassDeclaration { NodeKind::ClassDeclaration.Define("class_declaration"); Parse::Node parse_node; - TypeId type_id; + // No type: a class declaration is not itself a value. The name of a class + // declaration becomes a class type value. + // TODO: For a generic class declaration, the name of the class declaration + // should become a parameterized entity name value. ClassId class_id; // The declaration block, containing the class name's qualifiers and the // class's generic parameters. NodeBlockId decl_block_id; }; +struct ClassType { + static constexpr auto Kind = NodeKind::ClassType.Define("class_type"); + + Parse::Node parse_node; + TypeId type_id; + ClassId class_id; + // TODO: Once we support generic classes, include the class's arguments here. +}; + struct ConstType { static constexpr auto Kind = NodeKind::ConstType.Define("const_type");