diff --git a/toolchain/parser/parse_node_kind.def b/toolchain/parser/parse_node_kind.def index 24128f9f9d6c..1ad77a699aec 100644 --- a/toolchain/parser/parse_node_kind.def +++ b/toolchain/parser/parse_node_kind.def @@ -70,7 +70,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(PackageDirective, PackageIntroducer) // `namespace`: // NamespaceStart -// _external_: Identifier or QualifiedDeclaration +// _external_: Name or QualifiedDeclaration // Namespace CARBON_PARSE_NODE_KIND_CHILD_COUNT(NamespaceStart, 0) CARBON_PARSE_NODE_KIND_CHILD_COUNT(Namespace, 2) @@ -84,7 +84,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(CodeBlock, CodeBlockStart) // `fn`: // FunctionIntroducer -// _external_: Identifier or QualifiedDeclaration +// _external_: Name or QualifiedDeclaration // _external_: ParameterList // _external_: type expression // ReturnType @@ -241,8 +241,8 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(CallExpressionComma, 0) CARBON_PARSE_NODE_KIND_BRACKET(CallExpression, CallExpressionStart) // A qualified declaration, such as `a.b`: -// _external_: Identifier or QualifiedDeclaration -// _external_: Identifier +// _external_: NameExpression or QualifiedDeclaration +// _external_: Name // QualifiedDeclaration // // TODO: This will eventually more general expressions, for example with @@ -329,7 +329,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(StructTypeLiteral, // `class`: // ClassIntroducer -// _external_: Identifier or QualifiedDeclaration +// _external_: Name or QualifiedDeclaration // ClassDefinitionStart // _external_: declarations // ClassDefinition @@ -344,7 +344,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(ClassDeclaration, ClassIntroducer) // `interface`: // InterfaceIntroducer -// _external_: Identifier or QualifiedDeclaration +// _external_: Name or QualifiedDeclaration // InterfaceDefinitionStart // _external_: declarations // InterfaceDefinition @@ -359,7 +359,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(InterfaceDeclaration, InterfaceIntroducer) // `constraint`: // NamedConstraintIntroducer -// _external_: Identifier or QualifiedDeclaration +// _external_: Name or QualifiedDeclaration // NamedConstraintDefinitionStart // _external_: declarations // NamedConstraintDefinition diff --git a/toolchain/parser/parser_handle_declaration_name_and_params.cpp b/toolchain/parser/parser_handle_declaration_name_and_params.cpp index bd790eef711c..1fd7e8ee0440 100644 --- a/toolchain/parser/parser_handle_declaration_name_and_params.cpp +++ b/toolchain/parser/parser_handle_declaration_name_and_params.cpp @@ -18,7 +18,10 @@ static auto ParserHandleDeclarationNameAndParams(ParserContext& context, context.PushState(state); if (context.PositionIs(TokenKind::Period)) { - context.AddLeafNode(ParseNodeKind::Name, *identifier); + // 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(ParseNodeKind::NameExpression, *identifier); state.state = ParserState::PeriodAsDeclaration; context.PushState(state); } else { diff --git a/toolchain/parser/testdata/namespace/fail_incomplete_name.carbon b/toolchain/parser/testdata/namespace/fail_incomplete_name.carbon index 30676cbb369e..2805a27238a0 100644 --- a/toolchain/parser/testdata/namespace/fail_incomplete_name.carbon +++ b/toolchain/parser/testdata/namespace/fail_incomplete_name.carbon @@ -5,7 +5,7 @@ // AUTOUPDATE // CHECK:STDOUT: [ // CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, -// CHECK:STDOUT: {kind: 'Name', text: 'Foo'}, +// CHECK:STDOUT: {kind: 'NameExpression', 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/parser/testdata/namespace/nested.carbon b/toolchain/parser/testdata/namespace/nested.carbon index 1f65d4e3a560..3c710dcbfdd5 100644 --- a/toolchain/parser/testdata/namespace/nested.carbon +++ b/toolchain/parser/testdata/namespace/nested.carbon @@ -8,12 +8,12 @@ // CHECK:STDOUT: {kind: 'Name', text: 'Foo'}, // CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 3}, // CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, -// CHECK:STDOUT: {kind: 'Name', text: 'Foo'}, +// CHECK:STDOUT: {kind: 'NameExpression', 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: 'Name', text: 'Foo'}, +// CHECK:STDOUT: {kind: 'NameExpression', 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/semantics/BUILD b/toolchain/semantics/BUILD index 8e56772bfd71..abcfc43d87bf 100644 --- a/toolchain/semantics/BUILD +++ b/toolchain/semantics/BUILD @@ -59,6 +59,7 @@ cc_library( "semantics_ir.cpp", "semantics_context.cpp", "semantics_node_block_stack.cpp", + "semantics_declaration_name_stack.cpp", ] + # Glob handler files to avoid missing anyway. glob([ @@ -66,6 +67,7 @@ cc_library( ]), hdrs = [ "semantics_context.h", + "semantics_declaration_name_stack.h", "semantics_ir.h", "semantics_node_block_stack.h", ], diff --git a/toolchain/semantics/semantics_context.cpp b/toolchain/semantics/semantics_context.cpp index 95638d13f89d..9fffdd829013 100644 --- a/toolchain/semantics/semantics_context.cpp +++ b/toolchain/semantics/semantics_context.cpp @@ -10,14 +10,13 @@ #include "toolchain/diagnostics/diagnostic_kind.h" #include "toolchain/lexer/tokenized_buffer.h" #include "toolchain/parser/parse_node_kind.h" +#include "toolchain/semantics/semantics_declaration_name_stack.h" #include "toolchain/semantics/semantics_ir.h" #include "toolchain/semantics/semantics_node.h" #include "toolchain/semantics/semantics_node_block_stack.h" namespace Carbon { -CARBON_DIAGNOSTIC(NameNotFound, Error, "Name {0} not found", llvm::StringRef); - SemanticsContext::SemanticsContext(const TokenizedBuffer& tokens, DiagnosticEmitter& emitter, const ParseTree& parse_tree, @@ -31,8 +30,8 @@ SemanticsContext::SemanticsContext(const TokenizedBuffer& tokens, node_stack_(parse_tree, vlog_stream), node_block_stack_("node_block_stack_", semantics_ir, vlog_stream), params_or_args_stack_("params_or_args_stack_", semantics_ir, vlog_stream), - args_type_info_stack_("args_type_info_stack_", semantics_ir, - vlog_stream) { + args_type_info_stack_("args_type_info_stack_", semantics_ir, vlog_stream), + declaration_name_stack_(this) { // Inserts the "Error" and "Type" types as "used types" so that // canonicalization can skip them. We don't emit either for lowering. canonical_types_.insert( @@ -75,10 +74,24 @@ auto SemanticsContext::AddNodeAndPush(ParseTree::Node parse_node, node_stack_.Push(parse_node, node_id); } -CARBON_DIAGNOSTIC(NameDeclarationDuplicate, Error, - "Duplicate name being declared in the same scope."); -CARBON_DIAGNOSTIC(NameDeclarationPrevious, Note, - "Name is previously declared here."); +auto SemanticsContext::DiagnoseDuplicateName(ParseTree::Node parse_node, + SemanticsNodeId prev_def_id) + -> void { + CARBON_DIAGNOSTIC(NameDeclarationDuplicate, Error, + "Duplicate name being declared in the same scope."); + CARBON_DIAGNOSTIC(NameDeclarationPrevious, Note, + "Name is previously declared here."); + auto prev_def = semantics_ir_->GetNode(prev_def_id); + emitter_->Build(parse_node, NameDeclarationDuplicate) + .Note(prev_def.parse_node(), NameDeclarationPrevious) + .Emit(); +} + +auto SemanticsContext::DiagnoseNameNotFound(ParseTree::Node parse_node, + SemanticsStringId name_id) -> void { + CARBON_DIAGNOSTIC(NameNotFound, Error, "Name {0} not found", llvm::StringRef); + emitter_->Emit(parse_node, NameNotFound, semantics_ir_->GetString(name_id)); +} auto SemanticsContext::AddNameToLookup(ParseTree::Node name_node, SemanticsStringId name_id, @@ -86,48 +99,7 @@ auto SemanticsContext::AddNameToLookup(ParseTree::Node name_node, if (current_scope().names.insert(name_id).second) { name_lookup_[name_id].push_back(target_id); } else { - auto prev_def_id = name_lookup_[name_id].back(); - auto prev_def = semantics_ir_->GetNode(prev_def_id); - emitter_->Build(name_node, NameDeclarationDuplicate) - .Note(prev_def.parse_node(), NameDeclarationPrevious) - .Emit(); - } -} - -auto SemanticsContext::AddNameToLookup(DeclarationNameContext name_context, - SemanticsNodeId target_id) -> void { - switch (name_context.state) { - case DeclarationNameContext::State::Error: - // The name is invalid and a diagnostic has already been emitted. - return; - - case DeclarationNameContext::State::New: - CARBON_FATAL() << "Name is missing, not expected to call AddNameToLookup " - "(but that may change based on error handling)."; - - case DeclarationNameContext::State::Resolved: - case DeclarationNameContext::State::ResolvedNonScope: { - auto prev_def = semantics_ir_->GetNode(name_context.resolved_node_id); - emitter_->Build(name_context.parse_node, NameDeclarationDuplicate) - .Note(prev_def.parse_node(), NameDeclarationPrevious) - .Emit(); - return; - } - - case DeclarationNameContext::State::Unresolved: - if (name_context.target_scope_id == SemanticsNameScopeId::Invalid) { - AddNameToLookup(name_context.parse_node, - name_context.unresolved_name_id, target_id); - } else { - bool success = semantics_ir_->AddNameScopeEntry( - name_context.target_scope_id, name_context.unresolved_name_id, - target_id); - CARBON_CHECK(success) - << "Duplicate names should have been resolved previously: " - << name_context.unresolved_name_id << " in " - << name_context.target_scope_id; - } - return; + DiagnoseDuplicateName(name_node, name_lookup_[name_id].back()); } } @@ -139,8 +111,7 @@ auto SemanticsContext::LookupName(ParseTree::Node parse_node, auto it = name_lookup_.find(name_id); if (it == name_lookup_.end()) { if (print_diagnostics) { - emitter_->Emit(parse_node, NameNotFound, - semantics_ir_->GetString(name_id)); + DiagnoseNameNotFound(parse_node, name_id); } return SemanticsNodeId::BuiltinError; } @@ -154,8 +125,7 @@ auto SemanticsContext::LookupName(ParseTree::Node parse_node, auto it = scope.find(name_id); if (it == scope.end()) { if (print_diagnostics) { - emitter_->Emit(parse_node, NameNotFound, - semantics_ir_->GetString(name_id)); + DiagnoseNameNotFound(parse_node, name_id); } return SemanticsNodeId::BuiltinError; } @@ -295,106 +265,6 @@ auto SemanticsContext::is_current_position_reachable() -> bool { } } -auto SemanticsContext::PushDeclarationName() -> void { - declaration_name_stack_.push_back( - {.state = DeclarationNameContext::State::New, - .target_scope_id = SemanticsNameScopeId::Invalid, - .resolved_node_id = SemanticsNodeId::Invalid}); -} - -auto SemanticsContext::PopDeclarationName() -> DeclarationNameContext { - if (parse_tree_->node_kind(node_stack().PeekParseNode()) == - ParseNodeKind::QualifiedDeclaration) { - // Any parts from a QualifiedDeclaration will already have been processed - // into the name. - node_stack_ - .PopAndDiscardSoloParseNode(); - } else { - // The name had no qualifiers, so we need to process the node now. - auto [parse_node, node_or_name_id] = - node_stack_.PopExpressionWithParseNode(); - ApplyDeclarationNameQualifier(parse_node, node_or_name_id); - } - - return declaration_name_stack_.pop_back_val(); -} - -auto SemanticsContext::ApplyDeclarationNameQualifier( - ParseTree::Node parse_node, SemanticsNodeId node_or_name_id) -> void { - auto& name_context = declaration_name_stack_.back(); - switch (name_context.state) { - case DeclarationNameContext::State::Error: - // Already in an error state, so return without examining. - return; - - case DeclarationNameContext::State::Unresolved: - // Because more qualifiers were found, we diagnose that the earlier - // qualifier failed to resolve. - name_context.state = DeclarationNameContext::State::Error; - emitter_->Emit(name_context.parse_node, NameNotFound, - semantics_ir_->GetString(name_context.unresolved_name_id)); - return; - - case DeclarationNameContext::State::ResolvedNonScope: { - // Because more qualifiers were found, we diagnose that the earlier - // qualifier didn't resolve to a scoped entity. - name_context.state = DeclarationNameContext::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."); - emitter_->Build(parse_node, QualifiedDeclarationInNonScope) - .Note(name_context.parse_node, QualifiedDeclarationNonScopeEntity) - .Emit(); - return; - } - - case DeclarationNameContext::State::New: - case DeclarationNameContext::State::Resolved: { - name_context.parse_node = parse_node; - if (parse_tree().node_kind(name_context.parse_node) == - ParseNodeKind::Name) { - // For identifier nodes, we need to perform a lookup on the identifier. - // This means the input node_id is actually a string ID. - SemanticsStringId name_id(node_or_name_id.index); - auto resolved_node_id = LookupName(name_context.parse_node, name_id, - name_context.target_scope_id, - /*print_diagnostics=*/false); - if (resolved_node_id == SemanticsNodeId::BuiltinError) { - // Invalid indicates an unresolved node. Store it and return. - name_context.state = DeclarationNameContext::State::Unresolved; - name_context.unresolved_name_id = name_id; - return; - } else { - // Store the resolved node and continue for the target scope update. - name_context.resolved_node_id = resolved_node_id; - } - } else { - // 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_or_name_id; - } - - // This will only be reached for resolved nodes. We update the target - // scope based on the resolved type. - auto resolved_node = - semantics_ir_->GetNode(name_context.resolved_node_id); - switch (resolved_node.kind()) { - case SemanticsNodeKind::Namespace: - name_context.state = DeclarationNameContext::State::Resolved; - name_context.target_scope_id = resolved_node.GetAsNamespace(); - break; - default: - name_context.state = DeclarationNameContext::State::ResolvedNonScope; - break; - } - return; - } - } -} - auto SemanticsContext::ImplicitAsForArgs( SemanticsNodeBlockId arg_refs_id, ParseTree::Node param_parse_node, SemanticsNodeBlockId param_refs_id, diff --git a/toolchain/semantics/semantics_context.h b/toolchain/semantics/semantics_context.h index ed023ff646c5..de99d46e95f3 100644 --- a/toolchain/semantics/semantics_context.h +++ b/toolchain/semantics/semantics_context.h @@ -10,6 +10,7 @@ #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/SmallVector.h" #include "toolchain/parser/parse_tree.h" +#include "toolchain/semantics/semantics_declaration_name_stack.h" #include "toolchain/semantics/semantics_ir.h" #include "toolchain/semantics/semantics_node.h" #include "toolchain/semantics/semantics_node_block_stack.h" @@ -20,83 +21,6 @@ namespace Carbon { // Context and shared functionality for semantics handlers. class SemanticsContext { public: - // Context for declaration name construction, tracked in - // declaration_name_stack_. - // - // 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. - // 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. - // - // Unresolved identifiers are valid if and only if they are the last step of a - // qualified name; all resolved qualifiers must resolve to an entity with - // members, such as a namespace. Resolved identifiers in the last step will - // occur for both out-of-line definitions and new declarations, depending on - // context. - // - // Example state transitions: - // - // ``` - // // New -> Unresolved, because `MyNamespace` is newly declared. - // namespace MyNamespace; - // - // // New -> Resolved -> Unresolved, because `MyType` is newly declared. - // class MyNamespace.MyType; - // - // // New -> Resolved -> Resolved, because `MyType` was forward declared. - // class MyNamespace.MyType { - // // New -> Unresolved, because `DoSomething` is newly declared. - // fn DoSomething(); - // } - // - // // New -> Resolved -> Resolved -> ResolvedNonScope, because `DoSomething` - // // is forward declared in `MyType`, but is not a scope itself. - // fn MyNamespace.MyType.DoSomething() { ... } - // ``` - struct DeclarationNameContext { - enum class State { - // A new context which has not processed any parts of the qualifier. - New, - - // A node ID has been resolved, whether through an identifier or - // expression. This provided a new scope, such as a type. - Resolved, - - // A node ID has been resolved, whether through an identifier or - // expression. It did not provide a new scope, so must be the final part, - // such as an out-of-line function definition. - ResolvedNonScope, - - // An identifier didn't resolve. - Unresolved, - - // An error has occurred, such as an additional qualifier past an - // unresolved name. No new diagnostics should be emitted. - Error, - }; - - 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. - SemanticsNameScopeId target_scope_id = SemanticsNameScopeId::Invalid; - - // The last parse node used. - ParseTree::Node parse_node = ParseTree::Node::Invalid; - - union { - // The ID of a resolved qualifier, including both identifiers and - // expressions. Invalid indicates resolution failed. - SemanticsNodeId resolved_node_id = SemanticsNodeId::Invalid; - - // The ID of an unresolved identifier. - SemanticsStringId unresolved_name_id; - }; - }; - // Stores references for work. explicit SemanticsContext(const TokenizedBuffer& tokens, DiagnosticEmitter& emitter, @@ -124,16 +48,20 @@ class SemanticsContext { auto AddNameToLookup(ParseTree::Node name_node, SemanticsStringId name_id, SemanticsNodeId target_id) -> void; - // Adds a name to name lookup. Prints a diagnostic for name conflicts. - auto AddNameToLookup(DeclarationNameContext name_context, - SemanticsNodeId target_id) -> void; - // Performs name lookup in a specified scope, returning the referenced node. // If scope_id is invalid, uses the current contextual scope. auto LookupName(ParseTree::Node parse_node, SemanticsStringId name_id, SemanticsNameScopeId scope_id, bool print_diagnostics) -> SemanticsNodeId; + // Prints a diagnostic for a duplicate name. + auto DiagnoseDuplicateName(ParseTree::Node parse_node, + SemanticsNodeId prev_def_id) -> void; + + // Prints a diagnostic for a missing name. + auto DiagnoseNameNotFound(ParseTree::Node parse_node, + SemanticsStringId name_id) -> void; + // Pushes a new scope onto scope_stack_. auto PushScope() -> void; @@ -182,20 +110,6 @@ class SemanticsContext { // Returns whether the current position in the current block is reachable. auto is_current_position_reachable() -> bool; - // Pushes processing of a new declaration name, which will be used - // contextually. - auto PushDeclarationName() -> void; - - // Pops the current declaration name processing, returning the final context - // for adding the name to lookup. This also pops the final name node from the - // node stack, which will be applied to the declaration name if appropriate. - auto PopDeclarationName() -> DeclarationNameContext; - - // Applies an entry from the node stack to the top of the declaration name - // stack. - auto ApplyDeclarationNameQualifier(ParseTree::Node parse_node, - SemanticsNodeId node_or_name_id) -> void; - // Runs ImplicitAsImpl for a set of arguments and parameters. // // This will eventually need to support checking against multiple possible @@ -286,6 +200,10 @@ class SemanticsContext { return return_scope_stack_; } + auto declaration_name_stack() -> SemanticsDeclarationNameStack& { + return declaration_name_stack_; + } + private: // For CanImplicitAs, the detected conversion to apply. enum ImplicitAsKind { @@ -372,9 +290,8 @@ class SemanticsContext { // A stack for scope context. llvm::SmallVector scope_stack_; - // A stack for declaration name context. See DeclarationNameContext for - // details. - llvm::SmallVector declaration_name_stack_; + // The stack used for qualified declaration name construction. + SemanticsDeclarationNameStack declaration_name_stack_; // Maps identifiers to name lookup results. Values are a stack of name lookup // results in the ancestor scopes. This offers constant-time lookup of names, diff --git a/toolchain/semantics/semantics_declaration_name_stack.cpp b/toolchain/semantics/semantics_declaration_name_stack.cpp new file mode 100644 index 000000000000..140fc2d773b1 --- /dev/null +++ b/toolchain/semantics/semantics_declaration_name_stack.cpp @@ -0,0 +1,170 @@ +// 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 + +#include "toolchain/semantics/semantics_declaration_name_stack.h" + +#include "toolchain/semantics/semantics_context.h" + +namespace Carbon { + +auto SemanticsDeclarationNameStack::Push() -> void { + declaration_name_stack_.push_back( + {.state = Context::State::New, + .target_scope_id = SemanticsNameScopeId::Invalid, + .resolved_node_id = SemanticsNodeId::Invalid}); +} + +auto SemanticsDeclarationNameStack::Pop() -> Context { + if (context_->parse_tree().node_kind( + context_->node_stack().PeekParseNode()) == + ParseNodeKind::QualifiedDeclaration) { + // Any parts from a QualifiedDeclaration will already have been processed + // into the name. + context_->node_stack() + .PopAndDiscardSoloParseNode(); + } else { + // The name had no qualifiers, so we need to process the node now. + auto [parse_node, name_id] = + context_->node_stack().PopWithParseNode(); + ApplyNameQualifier(parse_node, name_id); + } + + return declaration_name_stack_.pop_back_val(); +} + +auto SemanticsDeclarationNameStack::AddNameToLookup(Context name_context, + SemanticsNodeId target_id) + -> void { + switch (name_context.state) { + case Context::State::Error: + // The name is invalid and a diagnostic has already been emitted. + return; + + case Context::State::New: + CARBON_FATAL() << "Name is missing, not expected to call AddNameToLookup " + "(but that may change based on error handling)."; + + case Context::State::Resolved: + case Context::State::ResolvedNonScope: { + context_->DiagnoseDuplicateName(name_context.parse_node, + name_context.resolved_node_id); + return; + } + + case Context::State::Unresolved: + if (name_context.target_scope_id == SemanticsNameScopeId::Invalid) { + context_->AddNameToLookup(name_context.parse_node, + name_context.unresolved_name_id, target_id); + } else { + bool success = context_->semantics_ir().AddNameScopeEntry( + name_context.target_scope_id, name_context.unresolved_name_id, + target_id); + CARBON_CHECK(success) + << "Duplicate names should have been resolved previously: " + << name_context.unresolved_name_id << " in " + << name_context.target_scope_id; + } + return; + } +} + +auto SemanticsDeclarationNameStack::ApplyExpressionQualifier( + ParseTree::Node parse_node, SemanticsNodeId node_id) -> void { + auto& name_context = declaration_name_stack_.back(); + if (CanResolveQualifier(name_context, parse_node)) { + if (node_id == SemanticsNodeId::BuiltinError) { + // The input node is an error, so error the context. + name_context.state = Context::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 SemanticsDeclarationNameStack::ApplyNameQualifier( + ParseTree::Node parse_node, SemanticsStringId name_id) -> void { + auto& name_context = declaration_name_stack_.back(); + if (CanResolveQualifier(name_context, parse_node)) { + // For identifier nodes, we need to perform a lookup on the identifier. + // This means the input node_id is actually a string ID. + auto resolved_node_id = context_->LookupName( + name_context.parse_node, name_id, name_context.target_scope_id, + /*print_diagnostics=*/false); + if (resolved_node_id == SemanticsNodeId::BuiltinError) { + // Invalid indicates an unresolved node. Store it and return. + name_context.state = Context::State::Unresolved; + name_context.unresolved_name_id = name_id; + return; + } else { + // Store the resolved node and continue for the target scope update. + name_context.resolved_node_id = resolved_node_id; + } + + UpdateScopeIfNeeded(name_context); + } +} + +auto SemanticsDeclarationNameStack::UpdateScopeIfNeeded(Context& name_context) + -> void { + // This will only be reached for resolved nodes. We update the target + // scope based on the resolved type. + auto resolved_node = + context_->semantics_ir().GetNode(name_context.resolved_node_id); + switch (resolved_node.kind()) { + case SemanticsNodeKind::Namespace: + name_context.state = Context::State::Resolved; + name_context.target_scope_id = resolved_node.GetAsNamespace(); + break; + default: + name_context.state = Context::State::ResolvedNonScope; + break; + } +} + +auto SemanticsDeclarationNameStack::CanResolveQualifier( + Context& name_context, ParseTree::Node parse_node) -> bool { + switch (name_context.state) { + case Context::State::Error: + // Already in an error state, so return without examining. + return false; + + case Context::State::Unresolved: + // Because more qualifiers were found, we diagnose that the earlier + // qualifier failed to resolve. + name_context.state = Context::State::Error; + context_->DiagnoseNameNotFound(name_context.parse_node, + name_context.unresolved_name_id); + return false; + + case Context::State::ResolvedNonScope: { + // Because more qualifiers were found, we diagnose that the earlier + // qualifier didn't resolve to a scoped entity. + name_context.state = Context::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; + } + + case Context::State::New: + case Context::State::Resolved: { + name_context.parse_node = parse_node; + return true; + } + } +} + +} // namespace Carbon diff --git a/toolchain/semantics/semantics_declaration_name_stack.h b/toolchain/semantics/semantics_declaration_name_stack.h new file mode 100644 index 000000000000..cd413009e695 --- /dev/null +++ b/toolchain/semantics/semantics_declaration_name_stack.h @@ -0,0 +1,139 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_DECLARATION_NAME_STACK_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_DECLARATION_NAME_STACK_H_ + +#include "llvm/ADT/SmallVector.h" +#include "toolchain/parser/parse_tree.h" +#include "toolchain/semantics/semantics_node.h" + +namespace Carbon { + +class SemanticsContext; + +// 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. +// 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. +// +// Unresolved identifiers are valid if and only if they are the last step of a +// qualified name; all resolved qualifiers must resolve to an entity with +// members, such as a namespace. Resolved identifiers in the last step will +// occur for both out-of-line definitions and new declarations, depending on +// context. +// +// Example state transitions: +// +// ``` +// // New -> Unresolved, because `MyNamespace` is newly declared. +// namespace MyNamespace; +// +// // New -> Resolved -> Unresolved, because `MyType` is newly declared. +// class MyNamespace.MyType; +// +// // New -> Resolved -> Resolved, because `MyType` was forward declared. +// class MyNamespace.MyType { +// // New -> Unresolved, because `DoSomething` is newly declared. +// fn DoSomething(); +// } +// +// // New -> Resolved -> Resolved -> ResolvedNonScope, because `DoSomething` +// // is forward declared in `MyType`, but is not a scope itself. +// fn MyNamespace.MyType.DoSomething() { ... } +// ``` +class SemanticsDeclarationNameStack { + public: + // Context for declaration name construction. + struct Context { + enum class State { + // A new context which has not processed any parts of the qualifier. + New, + + // A node ID has been resolved, whether through an identifier or + // expression. This provided a new scope, such as a type. + Resolved, + + // A node ID has been resolved, whether through an identifier or + // expression. It did not provide a new scope, so must be the final part, + // such as an out-of-line function definition. + ResolvedNonScope, + + // An identifier didn't resolve. + Unresolved, + + // An error has occurred, such as an additional qualifier past an + // unresolved name. No new diagnostics should be emitted. + Error, + }; + + 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. + SemanticsNameScopeId target_scope_id = SemanticsNameScopeId::Invalid; + + // The last parse node used. + ParseTree::Node parse_node = ParseTree::Node::Invalid; + + union { + // The ID of a resolved qualifier, including both identifiers and + // expressions. Invalid indicates resolution failed. + SemanticsNodeId resolved_node_id = SemanticsNodeId::Invalid; + + // The ID of an unresolved identifier. + SemanticsStringId unresolved_name_id; + }; + }; + + explicit SemanticsDeclarationNameStack(SemanticsContext* context) + : context_(context) {} + + // Pushes processing of a new declaration name, which will be used + // contextually. + auto Push() -> void; + + // Pops the current declaration name processing, returning the final context + // for adding the name to lookup. This also pops the final name node from the + // node stack, which will be applied to the declaration name if appropriate. + auto Pop() -> Context; + + // Applies an expression from the node stack to the top of the declaration + // name stack. + auto ApplyExpressionQualifier(ParseTree::Node parse_node, + SemanticsNodeId node_id) -> void; + + // Applies a Name from the node stack to the top of the declaration name + // stack. + auto ApplyNameQualifier(ParseTree::Node parse_node, SemanticsStringId name_id) + -> void; + + // Adds a name to name lookup. Prints a diagnostic for name conflicts. + auto AddNameToLookup(Context name_context, SemanticsNodeId target_id) -> void; + + private: + // Returns true if the context is in a state where it can resolve qualifiers. + // Updates name_context as needed. + auto CanResolveQualifier(Context& name_context, ParseTree::Node parse_node) + -> bool; + + // Updates the scope on name_context as needed. This is called after + // resolution is complete, whether for Name or expression. + auto UpdateScopeIfNeeded(Context& name_context) -> void; + + // The linked context. + SemanticsContext* context_; + + // Provides nesting for construction. + llvm::SmallVector declaration_name_stack_; +}; + +} // namespace Carbon + +#endif // CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_DECLARATION_NAME_STACK_H_ diff --git a/toolchain/semantics/semantics_handle_function.cpp b/toolchain/semantics/semantics_handle_function.cpp index b776aebdd3ff..656d92a79bd6 100644 --- a/toolchain/semantics/semantics_handle_function.cpp +++ b/toolchain/semantics/semantics_handle_function.cpp @@ -21,7 +21,7 @@ static auto BuildFunctionDeclaration(SemanticsContext& context) } SemanticsNodeBlockId param_refs_id = context.node_stack().Pop(); - auto name_context = context.PopDeclarationName(); + auto name_context = context.declaration_name_stack().Pop(); auto fn_node = context.node_stack() .PopForSoloParseNode(); @@ -36,7 +36,7 @@ static auto BuildFunctionDeclaration(SemanticsContext& context) .body_block_ids = {}}); auto decl_id = context.AddNode( SemanticsNode::FunctionDeclaration::Make(fn_node, function_id)); - context.AddNameToLookup(name_context, decl_id); + context.declaration_name_stack().AddNameToLookup(name_context, decl_id); return {function_id, decl_id}; } @@ -103,7 +103,7 @@ auto SemanticsHandleFunctionIntroducer(SemanticsContext& context, // Push the bracketing node. context.node_stack().Push(parse_node); // A name should always follow. - context.PushDeclarationName(); + context.declaration_name_stack().Push(); return true; } diff --git a/toolchain/semantics/semantics_handle_name.cpp b/toolchain/semantics/semantics_handle_name.cpp index df38af325249..f207ae15e10e 100644 --- a/toolchain/semantics/semantics_handle_name.cpp +++ b/toolchain/semantics/semantics_handle_name.cpp @@ -87,25 +87,34 @@ auto SemanticsHandleNameExpression(SemanticsContext& context, auto SemanticsHandleQualifiedDeclaration(SemanticsContext& context, ParseTree::Node parse_node) -> bool { - // The first two qualifiers in a chain will be a QualifiedDeclaration with two - // Identifier or expression children. Later qualifiers will have a - // QualifiedDeclaration as the first child, and an Identifier or expression as - // the second child. - auto [parse_node2, node_or_name_id2] = - context.node_stack().PopExpressionWithParseNode(); - if (context.parse_tree().node_kind(context.node_stack().PeekParseNode()) != - ParseNodeKind::QualifiedDeclaration) { - // First QualifiedDeclaration in a chain. - auto [parse_node1, node_or_name_id1] = - context.node_stack().PopExpressionWithParseNode(); - context.ApplyDeclarationNameQualifier(parse_node1, node_or_name_id1); - // Add the QualifiedDeclaration so that it can be used for bracketing. - context.node_stack().Push(parse_node); + auto pop_and_apply_first_child = [&]() { + if (context.parse_tree().node_kind(context.node_stack().PeekParseNode()) != + ParseNodeKind::QualifiedDeclaration) { + // First QualifiedDeclaration in a chain. + auto [parse_node1, node_id1] = + context.node_stack().PopExpressionWithParseNode(); + context.declaration_name_stack().ApplyExpressionQualifier(parse_node1, + node_id1); + // 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. + } + }; + + ParseTree::Node parse_node2 = context.node_stack().PeekParseNode(); + if (context.parse_tree().node_kind(parse_node2) == ParseNodeKind::Name) { + SemanticsStringId name_id2 = + context.node_stack().Pop(); + pop_and_apply_first_child(); + context.declaration_name_stack().ApplyNameQualifier(parse_node2, name_id2); } else { - // Nothing to do: the QualifiedDeclaration remains as a bracketing node for - // later QualifiedDeclarations. + SemanticsNodeId node_id2 = context.node_stack().PopExpression(); + pop_and_apply_first_child(); + context.declaration_name_stack().ApplyExpressionQualifier(parse_node2, + node_id2); } - context.ApplyDeclarationNameQualifier(parse_node2, node_or_name_id2); return true; } diff --git a/toolchain/semantics/semantics_handle_namespace.cpp b/toolchain/semantics/semantics_handle_namespace.cpp index 585edd04e73e..b18f784400d4 100644 --- a/toolchain/semantics/semantics_handle_namespace.cpp +++ b/toolchain/semantics/semantics_handle_namespace.cpp @@ -9,16 +9,16 @@ namespace Carbon { auto SemanticsHandleNamespaceStart(SemanticsContext& context, ParseTree::Node /*parse_node*/) -> bool { - context.PushDeclarationName(); + context.declaration_name_stack().Push(); return true; } auto SemanticsHandleNamespace(SemanticsContext& context, ParseTree::Node parse_node) -> bool { - auto name_context = context.PopDeclarationName(); + auto name_context = context.declaration_name_stack().Pop(); auto namespace_id = context.AddNode(SemanticsNode::Namespace::Make( parse_node, context.semantics_ir().AddNameScope())); - context.AddNameToLookup(name_context, namespace_id); + context.declaration_name_stack().AddNameToLookup(name_context, namespace_id); return true; } diff --git a/toolchain/semantics/semantics_node_stack.h b/toolchain/semantics/semantics_node_stack.h index bce4fca2c005..ad2e2b6c2b31 100644 --- a/toolchain/semantics/semantics_node_stack.h +++ b/toolchain/semantics/semantics_node_stack.h @@ -82,7 +82,8 @@ class SemanticsNodeStack { PopForSoloParseNode(); } - // Pops the top of the stack and returns the parse_node and the ID. + // Pops an expression from the top of the stack and returns the parse_node and + // the ID. auto PopExpressionWithParseNode() -> std::pair { return PopWithParseNode(); @@ -325,12 +326,6 @@ class SemanticsNodeStack { // Require a ParseNodeKind be mapped to a particular IdKind. auto RequireIdKind(ParseNodeKind parse_kind, IdKind id_kind) -> void { - // TODO: Name can be popped as a node_id by declaration name handling. Will - // refactor to remove this quirk. - if (parse_kind == ParseNodeKind::Name && - id_kind == IdKind::SemanticsNodeId) { - return; - } CARBON_CHECK(ParseNodeKindToIdKind(parse_kind) == id_kind) << "Unexpected IdKind mapping for " << parse_kind; } diff --git a/toolchain/semantics/testdata/namespace/fail_unresolved_scope.carbon b/toolchain/semantics/testdata/namespace/fail_unresolved_scope.carbon index 82117cc7e463..2c0574e4b3fa 100644 --- a/toolchain/semantics/testdata/namespace/fail_unresolved_scope.carbon +++ b/toolchain/semantics/testdata/namespace/fail_unresolved_scope.carbon @@ -5,7 +5,7 @@ // AUTOUPDATE // CHECK:STDOUT: cross_reference_irs_size: 1 // CHECK:STDOUT: functions: [ -// CHECK:STDOUT: {name: str0, param_refs: block0, body: {block2}}}, +// CHECK:STDOUT: {name: str, param_refs: block0, body: {block2}}}, // CHECK:STDOUT: ] // CHECK:STDOUT: integer_literals: [ // CHECK:STDOUT: ]