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");