From 918c089e03b55200c9986c66434cafb784b4b027 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Thu, 6 Jul 2023 13:43:40 -0700 Subject: [PATCH] Add namespace support. (#2940) This handles namespacing of functions. Parsing and semantics are changed significantly, while lowering works without changes. Variables can't be namespaced yet because they're dealing with patterns, and I didn't dig through that code. Most of the logic is done through the new name declaration stack, which is necessary because semantics isn't quite sure where the declaration name ends. It'd be complex for parsing to send a signal about this, probably involving node variants and rewrites of the tree, and this solution seems to work well. Unfortunately this means a new stack, but that may be inevitable due to the extra information needing to be tracked. Note this doesn't deal with scoped lookups of non-namespace things, which we'll need for generics. That'll probably involve pushing resolved scopes onto a stack (or maybe just setting a singleton value?) to affect contextual name lookup. But, I think the basics are there to make it work when we can test the behavior. This renames "designator expression" to "qualified expression" and adds "qualified declaration" in order to use terminology more consistent with C++. Namespaces will probably need to be considered for name mangling down the line, but this still uses the basic name. --- toolchain/diagnostics/diagnostic_kind.def | 17 +- toolchain/lowering/lowering_handle.cpp | 6 + .../testdata/namespace/function.carbon | 35 ++++ .../lowering/testdata/namespace/nested.carbon | 28 +++ toolchain/parser/parse_node_kind.def | 33 ++- toolchain/parser/parser_context.cpp | 9 +- toolchain/parser/parser_context.h | 12 ++ .../parser/parser_handle_brace_expression.cpp | 2 +- ...ser_handle_declaration_name_and_params.cpp | 76 ++++++- .../parser_handle_declaration_scope_loop.cpp | 4 + toolchain/parser/parser_handle_expression.cpp | 8 +- toolchain/parser/parser_handle_function.cpp | 4 +- toolchain/parser/parser_handle_namespace.cpp | 39 ++++ toolchain/parser/parser_handle_package.cpp | 5 +- ...esignator.cpp => parser_handle_period.cpp} | 27 ++- toolchain/parser/parser_handle_statement.cpp | 6 +- toolchain/parser/parser_handle_type.cpp | 4 +- toolchain/parser/parser_handle_var.cpp | 4 +- toolchain/parser/parser_state.def | 59 +++++- .../basics/fail_invalid_designators.carbon | 6 +- .../basics/fail_paren_match_regression.carbon | 2 +- .../testdata/basics/function_call.carbon | 8 +- .../testdata/class/fn_definitions.carbon | 2 +- .../declaration/fail_missing_name.carbon | 3 +- .../declaration/fail_only_fn_and_semi.carbon | 3 +- .../fail_repeated_fn_and_semi.carbon | 3 +- ...skip_indented_newline_until_outdent.carbon | 3 +- ...ail_skip_indented_newline_with_semi.carbon | 3 +- ..._skip_indented_newline_without_semi.carbon | 3 +- .../fail_skip_to_newline_without_semi.carbon | 3 +- ...hout_name_and_many_tokens_in_params.carbon | 3 +- .../fail_identifier_in_statements.carbon | 2 +- .../interface/fail_missing_name.carbon | 3 +- .../interface/fail_missing_open_curly.carbon | 4 +- .../parser/testdata/namespace/basic.carbon | 29 +++ .../testdata/namespace/fail_args.carbon | 14 ++ .../namespace/fail_incomplete_name.carbon | 16 ++ .../testdata/namespace/fail_no_name.carbon | 14 ++ .../parser/testdata/namespace/nested.carbon | 41 ++++ .../fail_infix_uneven_space_after.carbon | 2 +- .../operators/fail_star_star_no_space.carbon | 2 +- .../testdata/package/fail_no_semi.carbon | 2 +- .../testdata/return/fail_expr_no_semi.carbon | 2 +- .../testdata/return/fail_no_semi.carbon | 2 +- .../parser/testdata/while/fail_no_semi.carbon | 4 +- toolchain/semantics/semantics_context.cpp | 196 ++++++++++++++++-- toolchain/semantics/semantics_context.h | 105 +++++++++- toolchain/semantics/semantics_handle.cpp | 142 ++++++++----- .../semantics_handle_call_expression.cpp | 3 +- .../semantics/semantics_handle_function.cpp | 15 +- .../semantics/semantics_handle_namespace.cpp | 25 +++ toolchain/semantics/semantics_ir.cpp | 1 + toolchain/semantics/semantics_ir.h | 25 +++ toolchain/semantics/semantics_node.h | 18 ++ toolchain/semantics/semantics_node_kind.def | 1 + .../testdata/basics/fail_name_lookup.carbon | 1 + .../fail_qualifier_unsupported.carbon} | 2 +- .../fail_param_name_conflict.carbon | 4 +- .../testdata/namespace/fail_duplicate.carbon | 53 +++++ .../namespace/fail_unresolved_scope.carbon | 38 ++++ .../testdata/namespace/function.carbon | 66 ++++++ .../testdata/namespace/nested.carbon | 59 ++++++ .../testdata/var/fail_duplicate_decl.carbon | 4 +- 63 files changed, 1136 insertions(+), 179 deletions(-) create mode 100644 toolchain/lowering/testdata/namespace/function.carbon create mode 100644 toolchain/lowering/testdata/namespace/nested.carbon create mode 100644 toolchain/parser/parser_handle_namespace.cpp rename toolchain/parser/{parser_handle_designator.cpp => parser_handle_period.cpp} (57%) create mode 100644 toolchain/parser/testdata/namespace/basic.carbon create mode 100644 toolchain/parser/testdata/namespace/fail_args.carbon create mode 100644 toolchain/parser/testdata/namespace/fail_incomplete_name.carbon create mode 100644 toolchain/parser/testdata/namespace/fail_no_name.carbon create mode 100644 toolchain/parser/testdata/namespace/nested.carbon create mode 100644 toolchain/semantics/semantics_handle_namespace.cpp rename toolchain/semantics/testdata/{designators/fail_unsupported.carbon => basics/fail_qualifier_unsupported.carbon} (91%) create mode 100644 toolchain/semantics/testdata/namespace/fail_duplicate.carbon create mode 100644 toolchain/semantics/testdata/namespace/fail_unresolved_scope.carbon create mode 100644 toolchain/semantics/testdata/namespace/function.carbon create mode 100644 toolchain/semantics/testdata/namespace/nested.carbon diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 8fb84b6ef4e9..6e4639a2f449 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -56,9 +56,8 @@ CARBON_DIAGNOSTIC_KIND(ExpectedExpression) CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterDot) CARBON_DIAGNOSTIC_KIND(ExpectedParameterName) CARBON_DIAGNOSTIC_KIND(ExpectedParenAfter) -CARBON_DIAGNOSTIC_KIND(ExpectedSemiAfter) -CARBON_DIAGNOSTIC_KIND(ExpectedSemiAfterExpression) -CARBON_DIAGNOSTIC_KIND(ExpectedSemiAfterVar) +CARBON_DIAGNOSTIC_KIND(ExpectedExpressionSemi) +CARBON_DIAGNOSTIC_KIND(ExpectedStatementSemi) CARBON_DIAGNOSTIC_KIND(ExpectedStructLiteralField) CARBON_DIAGNOSTIC_KIND(ExpectedVariableDeclaration) CARBON_DIAGNOSTIC_KIND(ExpectedVariableName) @@ -73,7 +72,6 @@ CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterPackage) CARBON_DIAGNOSTIC_KIND(ExpectedLibraryName) CARBON_DIAGNOSTIC_KIND(MissingLibraryKeyword) CARBON_DIAGNOSTIC_KIND(ExpectedApiOrImpl) -CARBON_DIAGNOSTIC_KIND(ExpectedSemiToEndPackageDirective) // For-specific diagnostics. CARBON_DIAGNOSTIC_KIND(ExpectedIn) @@ -85,6 +83,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedElseAfterIf) // Declaration diagnostics. CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationName) +CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationSemi) CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationSemiOrDefinition) CARBON_DIAGNOSTIC_KIND(MethodImplNotAllowed) CARBON_DIAGNOSTIC_KIND(ParametersRequiredByIntroducer) @@ -97,8 +96,8 @@ CARBON_DIAGNOSTIC_KIND(ParametersRequiredByDeduced) CARBON_DIAGNOSTIC_KIND(SemanticsTodo) CARBON_DIAGNOSTIC_KIND(NameNotFound) -CARBON_DIAGNOSTIC_KIND(NameRedefined) -CARBON_DIAGNOSTIC_KIND(PreviousDefinition) +CARBON_DIAGNOSTIC_KIND(NameDeclarationDuplicate) +CARBON_DIAGNOSTIC_KIND(NameDeclarationPrevious) CARBON_DIAGNOSTIC_KIND(NoMatchingCall) CARBON_DIAGNOSTIC_KIND(CallArgCountMismatch) CARBON_DIAGNOSTIC_KIND(CallArgTypeMismatch) @@ -107,8 +106,10 @@ CARBON_DIAGNOSTIC_KIND(ReturnStatementDisallowExpression) CARBON_DIAGNOSTIC_KIND(ReturnStatementImplicitNote) CARBON_DIAGNOSTIC_KIND(ReturnStatementMissingExpression) CARBON_DIAGNOSTIC_KIND(ImplicitAsConversionFailure) -CARBON_DIAGNOSTIC_KIND(DesignatorExpressionUnsupported) -CARBON_DIAGNOSTIC_KIND(DesignatorExpressionNameNotFound) +CARBON_DIAGNOSTIC_KIND(QualifiedDeclarationInNonScope) +CARBON_DIAGNOSTIC_KIND(QualifiedDeclarationNonScopeEntity) +CARBON_DIAGNOSTIC_KIND(QualifiedExpressionUnsupported) +CARBON_DIAGNOSTIC_KIND(QualifiedExpressionNameNotFound) // ============================================================================ // Other diagnostics diff --git a/toolchain/lowering/lowering_handle.cpp b/toolchain/lowering/lowering_handle.cpp index 5e9a5a5e413a..54fece4c0e9e 100644 --- a/toolchain/lowering/lowering_handle.cpp +++ b/toolchain/lowering/lowering_handle.cpp @@ -148,6 +148,12 @@ auto LoweringHandleIntegerLiteral(LoweringFunctionContext& context, context.SetLocal(node_id, v); } +auto LoweringHandleNamespace(LoweringFunctionContext& /*context*/, + SemanticsNodeId /*node_id*/, + SemanticsNode /*node*/) -> void { + // No action to take. +} + auto LoweringHandleRealLiteral(LoweringFunctionContext& context, SemanticsNodeId node_id, SemanticsNode node) -> void { diff --git a/toolchain/lowering/testdata/namespace/function.carbon b/toolchain/lowering/testdata/namespace/function.carbon new file mode 100644 index 000000000000..7034b3395f95 --- /dev/null +++ b/toolchain/lowering/testdata/namespace/function.carbon @@ -0,0 +1,35 @@ +// 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 +// CHECK:STDOUT: ; ModuleID = 'function.carbon' +// CHECK:STDOUT: source_filename = "function.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: %EmptyTupleType = type {} +// CHECK:STDOUT: +// CHECK:STDOUT: define %EmptyTupleType @Baz() { +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define %EmptyTupleType @Baz.1() { +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define %EmptyTupleType @Bar() { +// CHECK:STDOUT: %Baz.1 = call %EmptyTupleType @Baz.1() +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } + +namespace Foo; + +// Never called, just here to help catch bugs in name lookup. +fn Baz() { +} + +fn Foo.Baz() { +} + +fn Bar() { + Foo.Baz(); +} diff --git a/toolchain/lowering/testdata/namespace/nested.carbon b/toolchain/lowering/testdata/namespace/nested.carbon new file mode 100644 index 000000000000..a59d788cff62 --- /dev/null +++ b/toolchain/lowering/testdata/namespace/nested.carbon @@ -0,0 +1,28 @@ +// 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 +// CHECK:STDOUT: ; ModuleID = 'nested.carbon' +// CHECK:STDOUT: source_filename = "nested.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: %EmptyTupleType = type {} +// CHECK:STDOUT: +// CHECK:STDOUT: define %EmptyTupleType @Wiz() { +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define %EmptyTupleType @Baz() { +// CHECK:STDOUT: %Wiz = call %EmptyTupleType @Wiz() +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } + +namespace Foo; +namespace Foo.Bar; + +fn Foo.Bar.Wiz() { +} + +fn Foo.Bar.Baz() { + Foo.Bar.Wiz(); +} diff --git a/toolchain/parser/parse_node_kind.def b/toolchain/parser/parse_node_kind.def index c1be13272832..24128f9f9d6c 100644 --- a/toolchain/parser/parse_node_kind.def +++ b/toolchain/parser/parse_node_kind.def @@ -68,6 +68,13 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageImpl, 0) CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageLibrary, 1) CARBON_PARSE_NODE_KIND_BRACKET(PackageDirective, PackageIntroducer) +// `namespace`: +// NamespaceStart +// _external_: Identifier or QualifiedDeclaration +// Namespace +CARBON_PARSE_NODE_KIND_CHILD_COUNT(NamespaceStart, 0) +CARBON_PARSE_NODE_KIND_CHILD_COUNT(Namespace, 2) + // A code block: // CodeBlockStart // _external_: statements @@ -77,7 +84,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(CodeBlock, CodeBlockStart) // `fn`: // FunctionIntroducer -// Name +// _external_: Identifier or QualifiedDeclaration // _external_: ParameterList // _external_: type expression // ReturnType @@ -233,11 +240,21 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(CallExpressionStart, 1) CARBON_PARSE_NODE_KIND_CHILD_COUNT(CallExpressionComma, 0) CARBON_PARSE_NODE_KIND_BRACKET(CallExpression, CallExpressionStart) -// A designator expression, such as `a.b`: +// A qualified declaration, such as `a.b`: +// _external_: Identifier or QualifiedDeclaration +// _external_: Identifier +// QualifiedDeclaration +// +// TODO: This will eventually more general expressions, for example with +// `GenericType(type_args).ChildType(child_type_args).Name`. +CARBON_PARSE_NODE_KIND_CHILD_COUNT(QualifiedDeclaration, 2) + +// A member access expression, such as `a.b` or +// `GetObject().(Interface.member)`: // _external_: lhs expression -// _external_: Name -// DesignatorExpression -CARBON_PARSE_NODE_KIND_CHILD_COUNT(DesignatorExpression, 2) +// _external_: rhs expression +// QualifiedExpression +CARBON_PARSE_NODE_KIND_CHILD_COUNT(MemberAccessExpression, 2) // A literal. CARBON_PARSE_NODE_KIND_CHILD_COUNT(Literal, 0) @@ -312,7 +329,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(StructTypeLiteral, // `class`: // ClassIntroducer -// Name +// _external_: Identifier or QualifiedDeclaration // ClassDefinitionStart // _external_: declarations // ClassDefinition @@ -327,7 +344,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(ClassDeclaration, ClassIntroducer) // `interface`: // InterfaceIntroducer -// Name +// _external_: Identifier or QualifiedDeclaration // InterfaceDefinitionStart // _external_: declarations // InterfaceDefinition @@ -342,7 +359,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(InterfaceDeclaration, InterfaceIntroducer) // `constraint`: // NamedConstraintIntroducer -// Name +// _external_: Identifier or QualifiedDeclaration // NamedConstraintDefinitionStart // _external_: declarations // NamedConstraintDefinition diff --git a/toolchain/parser/parser_context.cpp b/toolchain/parser/parser_context.cpp index 37d3e3bd73ab..d56c3ca04b4d 100644 --- a/toolchain/parser/parser_context.cpp +++ b/toolchain/parser/parser_context.cpp @@ -425,10 +425,17 @@ auto ParserContext::RecoverFromDeclarationError(StateStackEntry state, /*has_error=*/true); } +auto ParserContext::EmitExpectedDeclarationSemi(TokenKind expected_kind) + -> void { + CARBON_DIAGNOSTIC(ExpectedDeclarationSemi, Error, + "`{0}` declarations must end with a `;`.", TokenKind); + emitter().Emit(*position(), ExpectedDeclarationSemi, expected_kind); +} + auto ParserContext::EmitExpectedDeclarationSemiOrDefinition( TokenKind expected_kind) -> void { CARBON_DIAGNOSTIC(ExpectedDeclarationSemiOrDefinition, Error, - "`{0}` should either end with a `;` for a declaration or " + "`{0}` declarations must either end with a `;` or " "have a `{{ ... }` block for a definition.", TokenKind); emitter().Emit(*position(), ExpectedDeclarationSemiOrDefinition, diff --git a/toolchain/parser/parser_context.h b/toolchain/parser/parser_context.h index 77ba887f2a0f..cfbd755ef4dc 100644 --- a/toolchain/parser/parser_context.h +++ b/toolchain/parser/parser_context.h @@ -215,6 +215,14 @@ class ParserContext { *position_, tree_->size())); } + // Pushes a new state with a specific token for context. Used when forming a + // new subtree with a token that isn't the start of the subtree. + auto PushState(ParserState state, TokenizedBuffer::Token token) -> void { + PushState(StateStackEntry(state, PrecedenceGroup::ForTopLevelExpression(), + PrecedenceGroup::ForTopLevelExpression(), token, + tree_->size())); + } + // Pushes a new expression state with specific precedence. auto PushStateForExpression(PrecedenceGroup ambient_precedence) -> void { PushState(StateStackEntry(ParserState::Expression, ambient_precedence, @@ -255,6 +263,10 @@ class ParserContext { ParserState keyword_state, int subtree_start) -> void; + // Emits a diagnostic for a declaration missing a semi. + auto EmitExpectedDeclarationSemi(TokenKind expected_kind) -> void; + + // Emits a diagnostic for a declaration missing a semi or definition. auto EmitExpectedDeclarationSemiOrDefinition(TokenKind expected_kind) -> void; // Handles error recovery in a declaration, particularly before any possible diff --git a/toolchain/parser/parser_handle_brace_expression.cpp b/toolchain/parser/parser_handle_brace_expression.cpp index f3761219ec08..7a3ec17da7f8 100644 --- a/toolchain/parser/parser_handle_brace_expression.cpp +++ b/toolchain/parser/parser_handle_brace_expression.cpp @@ -57,7 +57,7 @@ static auto ParserHandleBraceExpressionParameter( state.state = after_designator_state; context.PushState(state); - context.PushState(ParserState::DesignatorAsStruct); + context.PushState(ParserState::PeriodAsStruct); } auto ParserHandleBraceExpressionParameterAsType(ParserContext& context) diff --git a/toolchain/parser/parser_handle_declaration_name_and_params.cpp b/toolchain/parser/parser_handle_declaration_name_and_params.cpp index 94a6dafe2093..bd790eef711c 100644 --- a/toolchain/parser/parser_handle_declaration_name_and_params.cpp +++ b/toolchain/parser/parser_handle_declaration_name_and_params.cpp @@ -8,17 +8,70 @@ namespace Carbon { // Handles DeclarationNameAndParamsAs(Optional|Required). static auto ParserHandleDeclarationNameAndParams(ParserContext& context, - bool params_required) -> void { + ParserState after_name) + -> void { auto state = context.PopState(); - if (!context.ConsumeAndAddLeafNodeIf(TokenKind::Identifier, - ParseNodeKind::Name)) { + // TODO: Should handle designated names. + if (auto identifier = context.ConsumeIf(TokenKind::Identifier)) { + state.state = after_name; + context.PushState(state); + + if (context.PositionIs(TokenKind::Period)) { + context.AddLeafNode(ParseNodeKind::Name, *identifier); + state.state = ParserState::PeriodAsDeclaration; + context.PushState(state); + } else { + context.AddLeafNode(ParseNodeKind::Name, *identifier); + } + } else { CARBON_DIAGNOSTIC(ExpectedDeclarationName, Error, "`{0}` introducer should be followed by a name.", TokenKind); context.emitter().Emit(*context.position(), ExpectedDeclarationName, context.tokens().GetKind(state.token)); context.ReturnErrorOnState(); + context.AddLeafNode(ParseNodeKind::InvalidParse, *context.position()); + } +} + +auto ParserHandleDeclarationNameAndParamsAsNone(ParserContext& context) + -> void { + ParserHandleDeclarationNameAndParams( + context, ParserState::DeclarationNameAndParamsAfterNameAsNone); +} + +auto ParserHandleDeclarationNameAndParamsAsOptional(ParserContext& context) + -> void { + ParserHandleDeclarationNameAndParams( + context, ParserState::DeclarationNameAndParamsAfterNameAsOptional); +} + +auto ParserHandleDeclarationNameAndParamsAsRequired(ParserContext& context) + -> void { + ParserHandleDeclarationNameAndParams( + context, ParserState::DeclarationNameAndParamsAfterNameAsRequired); +} + +enum class Params { + None, + Optional, + Required, +}; + +static auto ParserHandleDeclarationNameAndParamsAfterName( + ParserContext& context, Params params) -> void { + auto state = context.PopState(); + + if (context.PositionIs(TokenKind::Period)) { + // Continue designator processing. + context.PushState(state); + state.state = ParserState::PeriodAsDeclaration; + context.PushState(state); + return; + } + + if (params == Params::None) { return; } @@ -27,7 +80,7 @@ static auto ParserHandleDeclarationNameAndParams(ParserContext& context, context.PushState(ParserState::ParameterListAsDeduced); } else if (context.PositionIs(TokenKind::OpenParen)) { context.PushState(ParserState::ParameterListAsRegular); - } else if (params_required) { + } else if (params == Params::Required) { CARBON_DIAGNOSTIC(ParametersRequiredByIntroducer, Error, "`{0}` requires a `(` for parameters.", TokenKind); context.emitter().Emit(*context.position(), ParametersRequiredByIntroducer, @@ -36,14 +89,19 @@ static auto ParserHandleDeclarationNameAndParams(ParserContext& context, } } -auto ParserHandleDeclarationNameAndParamsAsOptional(ParserContext& context) +auto ParserHandleDeclarationNameAndParamsAfterNameAsNone(ParserContext& context) -> void { - ParserHandleDeclarationNameAndParams(context, /*params_required=*/false); + ParserHandleDeclarationNameAndParamsAfterName(context, Params::None); } -auto ParserHandleDeclarationNameAndParamsAsRequired(ParserContext& context) - -> void { - ParserHandleDeclarationNameAndParams(context, /*params_required=*/true); +auto ParserHandleDeclarationNameAndParamsAfterNameAsOptional( + ParserContext& context) -> void { + ParserHandleDeclarationNameAndParamsAfterName(context, Params::Optional); +} + +auto ParserHandleDeclarationNameAndParamsAfterNameAsRequired( + ParserContext& context) -> void { + ParserHandleDeclarationNameAndParamsAfterName(context, Params::Required); } auto ParserHandleDeclarationNameAndParamsAfterDeduced(ParserContext& context) diff --git a/toolchain/parser/parser_handle_declaration_scope_loop.cpp b/toolchain/parser/parser_handle_declaration_scope_loop.cpp index 23a91de938b8..122c3549fe2f 100644 --- a/toolchain/parser/parser_handle_declaration_scope_loop.cpp +++ b/toolchain/parser/parser_handle_declaration_scope_loop.cpp @@ -46,6 +46,10 @@ auto ParserHandleDeclarationScopeLoop(ParserContext& context) -> void { context.PushState(ParserState::TypeIntroducerAsInterface); break; } + case TokenKind::Namespace: { + context.PushState(ParserState::Namespace); + break; + } case TokenKind::Semi: { context.AddLeafNode(ParseNodeKind::EmptyDeclaration, context.Consume()); break; diff --git a/toolchain/parser/parser_handle_expression.cpp b/toolchain/parser/parser_handle_expression.cpp index 3b12f8fbac7f..12884097a2c2 100644 --- a/toolchain/parser/parser_handle_expression.cpp +++ b/toolchain/parser/parser_handle_expression.cpp @@ -119,7 +119,7 @@ auto ParserHandleExpressionInPostfixLoop(ParserContext& context) -> void { switch (context.PositionKind()) { case TokenKind::Period: { context.PushState(state); - state.state = ParserState::DesignatorAsExpression; + state.state = ParserState::PeriodAsExpression; context.PushState(state); break; } @@ -298,9 +298,9 @@ auto ParserHandleExpressionStatementFinish(ParserContext& context) -> void { } if (!state.has_error) { - CARBON_DIAGNOSTIC(ExpectedSemiAfterExpression, Error, - "Expected `;` after expression."); - context.emitter().Emit(*context.position(), ExpectedSemiAfterExpression); + CARBON_DIAGNOSTIC(ExpectedExpressionSemi, Error, + "Expected `;` after expression statement."); + context.emitter().Emit(*context.position(), ExpectedExpressionSemi); } if (auto semi_token = context.SkipPastLikelyEnd(state.token)) { diff --git a/toolchain/parser/parser_handle_function.cpp b/toolchain/parser/parser_handle_function.cpp index 56399fc22c3a..0f7dc1b8efe6 100644 --- a/toolchain/parser/parser_handle_function.cpp +++ b/toolchain/parser/parser_handle_function.cpp @@ -13,8 +13,8 @@ auto ParserHandleFunctionIntroducer(ParserContext& context) -> void { state.state = ParserState::FunctionAfterParameters; context.PushState(state); - state.state = ParserState::DeclarationNameAndParamsAsRequired; - context.PushState(state); + context.PushState(ParserState::DeclarationNameAndParamsAsRequired, + state.token); } auto ParserHandleFunctionAfterParameters(ParserContext& context) -> void { diff --git a/toolchain/parser/parser_handle_namespace.cpp b/toolchain/parser/parser_handle_namespace.cpp new file mode 100644 index 000000000000..c4ade164e978 --- /dev/null +++ b/toolchain/parser/parser_handle_namespace.cpp @@ -0,0 +1,39 @@ +// 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/parser/parser_context.h" + +namespace Carbon { + +auto ParserHandleNamespace(ParserContext& context) -> void { + auto state = context.PopState(); + + context.AddLeafNode(ParseNodeKind::NamespaceStart, context.Consume()); + + state.state = ParserState::NamespaceFinish; + context.PushState(state); + + context.PushState(ParserState::DeclarationNameAndParamsAsNone, state.token); +} + +auto ParserHandleNamespaceFinish(ParserContext& context) -> void { + auto state = context.PopState(); + + if (state.has_error) { + context.RecoverFromDeclarationError(state, ParseNodeKind::Namespace, + /*skip_past_likely_end=*/true); + return; + } + + if (auto semi = context.ConsumeIf(TokenKind::Semi)) { + context.AddNode(ParseNodeKind::Namespace, *semi, state.subtree_start, + state.has_error); + } else { + context.EmitExpectedDeclarationSemi(TokenKind::Namespace); + context.RecoverFromDeclarationError(state, ParseNodeKind::Namespace, + /*skip_past_likely_end=*/true); + } +} + +} // namespace Carbon diff --git a/toolchain/parser/parser_handle_package.cpp b/toolchain/parser/parser_handle_package.cpp index 4491d7dfc66e..700239ff4080 100644 --- a/toolchain/parser/parser_handle_package.cpp +++ b/toolchain/parser/parser_handle_package.cpp @@ -77,10 +77,7 @@ auto ParserHandlePackage(ParserContext& context) -> void { } if (!context.PositionIs(TokenKind::Semi)) { - CARBON_DIAGNOSTIC(ExpectedSemiToEndPackageDirective, Error, - "Expected `;` to end package directive."); - context.emitter().Emit(*context.position(), - ExpectedSemiToEndPackageDirective); + context.EmitExpectedDeclarationSemi(TokenKind::Package); exit_on_parse_error(); return; } diff --git a/toolchain/parser/parser_handle_designator.cpp b/toolchain/parser/parser_handle_period.cpp similarity index 57% rename from toolchain/parser/parser_handle_designator.cpp rename to toolchain/parser/parser_handle_period.cpp index 3a358333f1fd..9156837fff37 100644 --- a/toolchain/parser/parser_handle_designator.cpp +++ b/toolchain/parser/parser_handle_period.cpp @@ -6,8 +6,11 @@ namespace Carbon { -// Handles DesignatorAs. -auto ParserHandleDesignator(ParserContext& context, bool as_struct) -> void { +// Handles PeriodAs variants. +// TODO: This currently only supports identifiers on the rhs, but will in the +// future need to handle things like `object.(Interface.member)` for qualifiers. +auto ParserHandlePeriod(ParserContext& context, ParseNodeKind node_kind) + -> void { auto state = context.PopState(); // `.` identifier @@ -18,8 +21,8 @@ auto ParserHandleDesignator(ParserContext& context, bool as_struct) -> void { CARBON_DIAGNOSTIC(ExpectedIdentifierAfterDot, Error, "Expected identifier after `.`."); context.emitter().Emit(*context.position(), ExpectedIdentifierAfterDot); - // If we see a keyword, assume it was intended to be the designated name. - // TODO: Should keywords be valid in designators? + // If we see a keyword, assume it was intended to be a name. + // TODO: Should keywords be valid here? if (context.PositionKind().is_keyword()) { context.AddLeafNode(ParseNodeKind::Name, context.Consume(), /*has_error=*/true); @@ -32,17 +35,19 @@ auto ParserHandleDesignator(ParserContext& context, bool as_struct) -> void { } } - context.AddNode(as_struct ? ParseNodeKind::StructFieldDesignator - : ParseNodeKind::DesignatorExpression, - dot, state.subtree_start, state.has_error); + context.AddNode(node_kind, dot, state.subtree_start, state.has_error); } -auto ParserHandleDesignatorAsExpression(ParserContext& context) -> void { - ParserHandleDesignator(context, /*as_struct=*/false); +auto ParserHandlePeriodAsDeclaration(ParserContext& context) -> void { + ParserHandlePeriod(context, ParseNodeKind::QualifiedDeclaration); } -auto ParserHandleDesignatorAsStruct(ParserContext& context) -> void { - ParserHandleDesignator(context, /*as_struct=*/true); +auto ParserHandlePeriodAsExpression(ParserContext& context) -> void { + ParserHandlePeriod(context, ParseNodeKind::MemberAccessExpression); +} + +auto ParserHandlePeriodAsStruct(ParserContext& context) -> void { + ParserHandlePeriod(context, ParseNodeKind::StructFieldDesignator); } } // namespace Carbon diff --git a/toolchain/parser/parser_handle_statement.cpp b/toolchain/parser/parser_handle_statement.cpp index 4063614f9401..60c1ee216db6 100644 --- a/toolchain/parser/parser_handle_statement.cpp +++ b/toolchain/parser/parser_handle_statement.cpp @@ -60,9 +60,9 @@ static auto ParserHandleStatementKeywordFinish(ParserContext& context, auto semi = context.ConsumeIf(TokenKind::Semi); if (!semi) { - CARBON_DIAGNOSTIC(ExpectedSemiAfter, Error, "Expected `;` after `{0}`.", - TokenKind); - context.emitter().Emit(*context.position(), ExpectedSemiAfter, + CARBON_DIAGNOSTIC(ExpectedStatementSemi, Error, + "`{0}` statements must end with a `;`.", TokenKind); + context.emitter().Emit(*context.position(), ExpectedStatementSemi, context.tokens().GetKind(state.token)); state.has_error = true; // Recover to the next semicolon if possible, otherwise indicate the diff --git a/toolchain/parser/parser_handle_type.cpp b/toolchain/parser/parser_handle_type.cpp index 5c2e34d2cbba..3c6d4bdf3b23 100644 --- a/toolchain/parser/parser_handle_type.cpp +++ b/toolchain/parser/parser_handle_type.cpp @@ -16,8 +16,8 @@ static auto ParserHandleTypeIntroducer(ParserContext& context, state.state = after_params_state; context.PushState(state); - state.state = ParserState::DeclarationNameAndParamsAsOptional; - context.PushState(state); + context.PushState(ParserState::DeclarationNameAndParamsAsOptional, + state.token); } auto ParserHandleTypeIntroducerAsClass(ParserContext& context) -> void { diff --git a/toolchain/parser/parser_handle_var.cpp b/toolchain/parser/parser_handle_var.cpp index e6011a292fec..db8128424803 100644 --- a/toolchain/parser/parser_handle_var.cpp +++ b/toolchain/parser/parser_handle_var.cpp @@ -53,9 +53,7 @@ auto ParserHandleVarFinishAsSemicolon(ParserContext& context) -> void { end_token = context.Consume(); } else { // TODO: Disambiguate between statement and member declaration. - CARBON_DIAGNOSTIC(ExpectedSemiAfterVar, Error, - "Expected `;` to terminate `var` declaration."); - context.emitter().Emit(*context.position(), ExpectedSemiAfterVar); + context.EmitExpectedDeclarationSemi(TokenKind::Var); state.has_error = true; if (auto semi_token = context.SkipPastLikelyEnd(state.token)) { end_token = *semi_token; diff --git a/toolchain/parser/parser_state.def b/toolchain/parser/parser_state.def index 1ef944ea8c1f..6ba382ca7d93 100644 --- a/toolchain/parser/parser_state.def +++ b/toolchain/parser/parser_state.def @@ -127,8 +127,31 @@ CARBON_PARSER_STATE(CodeBlock) // (state done) CARBON_PARSER_STATE(CodeBlockFinish) -// Handles a general declaration name and parameters, such as `Foo[...](...)`. +// Handles a declaration name and parameters, such as `Foo[...](...)`. // +// Allowed parameters: +// - None: `Foo` only. +// - Optional: `Foo`, `Foo(...)`, or `Foo[...](...)`. +// - Required: ``Foo(...)` or `Foo[...](...)`. +// +// If `Identifier` followed by `Period`: +// 1. DeclarationNameAndParamsAfterNameAs(None|Optional|Required) +// If `Identifier`: +// 1. DeclarationNameAndParamsAfterNameAs(None|Optional|Required) +// 2. PeriodAsDeclaration +// Else: +// (state done) +CARBON_PARSER_STATE_VARIANTS3(DeclarationNameAndParams, None, Optional, + Required) + +// Handles a declaration name between the main name and deduced parameters. +// +// For `None`, parameters aren't supported so only `Period` or `Else` paths are +// used. +// +// If `Period`: +// 1. DeclarationNameAndParamsAfterNameAs(None|Optional|Required) +// 2. PeriodAsDeclaration // If `OpenSquareBracket`: // 1. ParameterListAsDeduced // 2. DeclarationNameAndParamsAfterDeduced @@ -136,7 +159,8 @@ CARBON_PARSER_STATE(CodeBlockFinish) // 1. ParameterListAsRegular // Else: // (state done) -CARBON_PARSER_STATE_VARIANTS2(DeclarationNameAndParams, Optional, Required) +CARBON_PARSER_STATE_VARIANTS3(DeclarationNameAndParamsAfterName, None, Optional, + Required) // Handles regular parameters such as `(...)` for the general declaration case. // Only used after deduced parameters. @@ -164,6 +188,9 @@ CARBON_PARSER_STATE(DeclarationNameAndParamsAfterDeduced) // If `Interface`: // 1. TypeIntroducerAsInterface // 2. DeclarationScopeLoop +// If `Namespace`: +// 2. Namespace +// 3. DeclarationScopeLoop // If `Semi`: // 1. DeclarationScopeLoop // If `Var`: @@ -173,11 +200,20 @@ CARBON_PARSER_STATE(DeclarationNameAndParamsAfterDeduced) // 1. DeclarationScopeLoop CARBON_PARSER_STATE(DeclarationScopeLoop) -// Handles a designator expression, such as `.z` in `x.(y.z)`. +// Handles periods. Only does one `.` segment; the source is +// responsible for handling chaining. +// +// The forms of this are: +// - Designated names in structs. +// - Qualified names in declarations. +// - Member access expressions. +// +// Declarations and expressions have qualifiers such as `x.y`, while structs +// have designators such as `.z`. // // Always: // (state done) -CARBON_PARSER_STATE_VARIANTS2(Designator, Expression, Struct) +CARBON_PARSER_STATE_VARIANTS3(Period, Declaration, Expression, Struct) // Handles processing of an expression. // @@ -212,7 +248,7 @@ CARBON_PARSER_STATE(ExpressionInPostfix) // such as designators or parenthesized parameters. // // If `Period`: -// 1. DesignatorAsExpression +// 1. PeriodAsExpression // 2. ExpressionInPostfixLoop // If `OpenParen`: // 1. CallExpression @@ -329,6 +365,19 @@ CARBON_PARSER_STATE(FunctionSignatureFinish) // (state done) CARBON_PARSER_STATE(FunctionDefinitionFinish) +// Handles `namespace`. +// +// Always: +// 1. DeclarationNameAndParamsAsNone +// 2. NamespaceFinish +CARBON_PARSER_STATE(Namespace) + +// Handles `namespace` after the name. +// +// Always: +// (state done) +CARBON_PARSER_STATE(NamespaceFinish) + // Handles `package`. // // Always: diff --git a/toolchain/parser/testdata/basics/fail_invalid_designators.carbon b/toolchain/parser/testdata/basics/fail_invalid_designators.carbon index ae13bfb5138b..c03f2bce7afb 100644 --- a/toolchain/parser/testdata/basics/fail_invalid_designators.carbon +++ b/toolchain/parser/testdata/basics/fail_invalid_designators.carbon @@ -11,15 +11,15 @@ // CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, // CHECK:STDOUT: {kind: 'NameExpression', text: 'a'}, // CHECK:STDOUT: {kind: 'Name', text: ';', has_error: yes}, -// CHECK:STDOUT: {kind: 'DesignatorExpression', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExpressionStatement', text: ';', has_error: yes, subtree_size: 4}, // CHECK:STDOUT: {kind: 'NameExpression', text: 'a'}, // CHECK:STDOUT: {kind: 'Name', text: 'fn', has_error: yes}, -// CHECK:STDOUT: {kind: 'DesignatorExpression', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExpressionStatement', text: ';', subtree_size: 4}, // CHECK:STDOUT: {kind: 'NameExpression', text: 'a'}, // CHECK:STDOUT: {kind: 'Name', text: '42', has_error: yes}, -// CHECK:STDOUT: {kind: 'DesignatorExpression', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExpressionStatement', text: ';', has_error: yes, subtree_size: 4}, // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, diff --git a/toolchain/parser/testdata/basics/fail_paren_match_regression.carbon b/toolchain/parser/testdata/basics/fail_paren_match_regression.carbon index f3df7730af90..edb001686dcc 100644 --- a/toolchain/parser/testdata/basics/fail_paren_match_regression.carbon +++ b/toolchain/parser/testdata/basics/fail_paren_match_regression.carbon @@ -18,5 +18,5 @@ // CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+3]]:5: Expected pattern in `var` declaration. // CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+2]]:12: Expected `,` or `)`. -// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+1]]:15: Expected `;` to terminate `var` declaration. +// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+1]]:15: `var` declarations must end with a `;`. var = (foo {}) diff --git a/toolchain/parser/testdata/basics/function_call.carbon b/toolchain/parser/testdata/basics/function_call.carbon index 0d463ca33d80..bc3358d85219 100644 --- a/toolchain/parser/testdata/basics/function_call.carbon +++ b/toolchain/parser/testdata/basics/function_call.carbon @@ -11,20 +11,20 @@ // CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, // CHECK:STDOUT: {kind: 'NameExpression', text: 'a'}, // CHECK:STDOUT: {kind: 'Name', text: 'b'}, -// CHECK:STDOUT: {kind: 'DesignatorExpression', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'Name', text: 'f'}, -// CHECK:STDOUT: {kind: 'DesignatorExpression', text: '.', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 5}, // CHECK:STDOUT: {kind: 'CallExpressionStart', text: '(', subtree_size: 6}, // CHECK:STDOUT: {kind: 'NameExpression', text: 'c'}, // CHECK:STDOUT: {kind: 'Name', text: 'd'}, -// CHECK:STDOUT: {kind: 'DesignatorExpression', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'CallExpressionComma', text: ','}, // CHECK:STDOUT: {kind: 'ParenExpressionOrTupleLiteralStart', text: '('}, // CHECK:STDOUT: {kind: 'NameExpression', text: 'e'}, // CHECK:STDOUT: {kind: 'ParenExpression', text: ')', subtree_size: 3}, // CHECK:STDOUT: {kind: 'CallExpression', text: ')', subtree_size: 14}, // CHECK:STDOUT: {kind: 'Name', text: 'g'}, -// CHECK:STDOUT: {kind: 'DesignatorExpression', text: '.', subtree_size: 16}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 16}, // CHECK:STDOUT: {kind: 'CallExpressionStart', text: '(', subtree_size: 17}, // CHECK:STDOUT: {kind: 'CallExpression', text: ')', subtree_size: 18}, // CHECK:STDOUT: {kind: 'ExpressionStatement', text: ';', subtree_size: 19}, diff --git a/toolchain/parser/testdata/class/fn_definitions.carbon b/toolchain/parser/testdata/class/fn_definitions.carbon index 4f7aa367e0e6..1d40fdbcf55e 100644 --- a/toolchain/parser/testdata/class/fn_definitions.carbon +++ b/toolchain/parser/testdata/class/fn_definitions.carbon @@ -38,7 +38,7 @@ // CHECK:STDOUT: {kind: 'ReturnStatementStart', text: 'return'}, // CHECK:STDOUT: {kind: 'SelfValueName', text: 'self'}, // CHECK:STDOUT: {kind: 'Name', text: 'x'}, -// CHECK:STDOUT: {kind: 'DesignatorExpression', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ReturnStatement', text: ';', subtree_size: 5}, // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18}, // CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 38}, diff --git a/toolchain/parser/testdata/function/declaration/fail_missing_name.carbon b/toolchain/parser/testdata/function/declaration/fail_missing_name.carbon index b4b4db89b7ff..e448ab4d4eb3 100644 --- a/toolchain/parser/testdata/function/declaration/fail_missing_name.carbon +++ b/toolchain/parser/testdata/function/declaration/fail_missing_name.carbon @@ -5,7 +5,8 @@ // AUTOUPDATE // CHECK:STDOUT: [ // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '('}, +// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parser/testdata/function/declaration/fail_only_fn_and_semi.carbon b/toolchain/parser/testdata/function/declaration/fail_only_fn_and_semi.carbon index 49f693568ef9..b1697e36989a 100644 --- a/toolchain/parser/testdata/function/declaration/fail_only_fn_and_semi.carbon +++ b/toolchain/parser/testdata/function/declaration/fail_only_fn_and_semi.carbon @@ -5,7 +5,8 @@ // AUTOUPDATE // CHECK:STDOUT: [ // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ';'}, +// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parser/testdata/function/declaration/fail_repeated_fn_and_semi.carbon b/toolchain/parser/testdata/function/declaration/fail_repeated_fn_and_semi.carbon index f7d14d9301dc..3bcab2dc76e5 100644 --- a/toolchain/parser/testdata/function/declaration/fail_repeated_fn_and_semi.carbon +++ b/toolchain/parser/testdata/function/declaration/fail_repeated_fn_and_semi.carbon @@ -5,7 +5,8 @@ // AUTOUPDATE // CHECK:STDOUT: [ // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: 'fn'}, +// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_until_outdent.carbon b/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_until_outdent.carbon index 7ff5e20f82f0..248904cb4708 100644 --- a/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_until_outdent.carbon +++ b/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_until_outdent.carbon @@ -5,7 +5,8 @@ // AUTOUPDATE // CHECK:STDOUT: [ // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '('}, +// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, // CHECK:STDOUT: {kind: 'Name', text: 'F'}, // CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, diff --git a/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_with_semi.carbon b/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_with_semi.carbon index c4df435cd534..bcacfef68499 100644 --- a/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_with_semi.carbon +++ b/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_with_semi.carbon @@ -5,7 +5,8 @@ // AUTOUPDATE // CHECK:STDOUT: [ // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '('}, +// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, // CHECK:STDOUT: {kind: 'Name', text: 'F'}, // CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, diff --git a/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_without_semi.carbon b/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_without_semi.carbon index d00e8f7e9f83..5551a423d4c0 100644 --- a/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_without_semi.carbon +++ b/toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_without_semi.carbon @@ -5,7 +5,8 @@ // AUTOUPDATE // CHECK:STDOUT: [ // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '('}, +// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, // CHECK:STDOUT: {kind: 'Name', text: 'F'}, // CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, diff --git a/toolchain/parser/testdata/function/declaration/fail_skip_to_newline_without_semi.carbon b/toolchain/parser/testdata/function/declaration/fail_skip_to_newline_without_semi.carbon index f878e916ade9..23bd0e4ad7be 100644 --- a/toolchain/parser/testdata/function/declaration/fail_skip_to_newline_without_semi.carbon +++ b/toolchain/parser/testdata/function/declaration/fail_skip_to_newline_without_semi.carbon @@ -5,7 +5,8 @@ // AUTOUPDATE // CHECK:STDOUT: [ // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '('}, +// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, // CHECK:STDOUT: {kind: 'Name', text: 'F'}, // CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, diff --git a/toolchain/parser/testdata/function/declaration/fail_without_name_and_many_tokens_in_params.carbon b/toolchain/parser/testdata/function/declaration/fail_without_name_and_many_tokens_in_params.carbon index e991f568f3b3..6202520595de 100644 --- a/toolchain/parser/testdata/function/declaration/fail_without_name_and_many_tokens_in_params.carbon +++ b/toolchain/parser/testdata/function/declaration/fail_without_name_and_many_tokens_in_params.carbon @@ -5,7 +5,8 @@ // AUTOUPDATE // CHECK:STDOUT: [ // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '('}, +// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parser/testdata/function/definition/fail_identifier_in_statements.carbon b/toolchain/parser/testdata/function/definition/fail_identifier_in_statements.carbon index f50c1b6f4906..12beb16bf27a 100644 --- a/toolchain/parser/testdata/function/definition/fail_identifier_in_statements.carbon +++ b/toolchain/parser/testdata/function/definition/fail_identifier_in_statements.carbon @@ -18,5 +18,5 @@ fn F() { // Note: this might become valid depending on the expression syntax. This test // shouldn't be taken as a sign it should remain invalid. bar -// CHECK:STDERR: fail_identifier_in_statements.carbon:[[@LINE+1]]:1: Expected `;` after expression. +// CHECK:STDERR: fail_identifier_in_statements.carbon:[[@LINE+1]]:1: Expected `;` after expression statement. } diff --git a/toolchain/parser/testdata/generics/interface/fail_missing_name.carbon b/toolchain/parser/testdata/generics/interface/fail_missing_name.carbon index 3b6536aacbe4..35e0e55e73c0 100644 --- a/toolchain/parser/testdata/generics/interface/fail_missing_name.carbon +++ b/toolchain/parser/testdata/generics/interface/fail_missing_name.carbon @@ -5,7 +5,8 @@ // AUTOUPDATE // CHECK:STDOUT: [ // CHECK:STDOUT: {kind: 'InterfaceIntroducer', text: 'interface'}, -// CHECK:STDOUT: {kind: 'InterfaceDeclaration', text: 'interface', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '{'}, +// CHECK:STDOUT: {kind: 'InterfaceDeclaration', text: 'interface', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parser/testdata/generics/interface/fail_missing_open_curly.carbon b/toolchain/parser/testdata/generics/interface/fail_missing_open_curly.carbon index 82a61827e89e..f7a58cef1535 100644 --- a/toolchain/parser/testdata/generics/interface/fail_missing_open_curly.carbon +++ b/toolchain/parser/testdata/generics/interface/fail_missing_open_curly.carbon @@ -13,8 +13,8 @@ // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] -// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+1]]:15: `interface` should either end with a `;` for a declaration or have a `{ ... }` block for a definition. +// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+1]]:15: `interface` declarations must either end with a `;` or have a `{ ... }` block for a definition. interface Bar Baz {} -// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+1]]:14: `interface` should either end with a `;` for a declaration or have a `{ ... }` block for a definition. +// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+1]]:14: `interface` declarations must either end with a `;` or have a `{ ... }` block for a definition. interface Foo diff --git a/toolchain/parser/testdata/namespace/basic.carbon b/toolchain/parser/testdata/namespace/basic.carbon new file mode 100644 index 000000000000..d865669c3708 --- /dev/null +++ b/toolchain/parser/testdata/namespace/basic.carbon @@ -0,0 +1,29 @@ +// 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 +// CHECK:STDOUT: [ +// CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, +// CHECK:STDOUT: {kind: 'Name', text: 'Foo'}, +// CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'Bar'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'NameExpression', text: 'Foo'}, +// CHECK:STDOUT: {kind: 'Name', text: 'Baz'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'CallExpressionStart', text: '(', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'CallExpression', text: ')', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ExpressionStatement', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 12}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] + +namespace Foo; + +fn Bar() { + Foo.Baz(); +} diff --git a/toolchain/parser/testdata/namespace/fail_args.carbon b/toolchain/parser/testdata/namespace/fail_args.carbon new file mode 100644 index 000000000000..c7397a5e02b7 --- /dev/null +++ b/toolchain/parser/testdata/namespace/fail_args.carbon @@ -0,0 +1,14 @@ +// 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 +// CHECK:STDOUT: [ +// CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, +// CHECK:STDOUT: {kind: 'Name', text: 'Foo'}, +// CHECK:STDOUT: {kind: 'Namespace', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] + +// CHECK:STDERR: fail_args.carbon:[[@LINE+1]]:14: `namespace` declarations must end with a `;`. +namespace Foo(); diff --git a/toolchain/parser/testdata/namespace/fail_incomplete_name.carbon b/toolchain/parser/testdata/namespace/fail_incomplete_name.carbon new file mode 100644 index 000000000000..30676cbb369e --- /dev/null +++ b/toolchain/parser/testdata/namespace/fail_incomplete_name.carbon @@ -0,0 +1,16 @@ +// 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 +// CHECK:STDOUT: [ +// CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, +// 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}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] + +// CHECK:STDERR: fail_incomplete_name.carbon:[[@LINE+1]]:15: Expected identifier after `.`. +namespace Foo.; diff --git a/toolchain/parser/testdata/namespace/fail_no_name.carbon b/toolchain/parser/testdata/namespace/fail_no_name.carbon new file mode 100644 index 000000000000..5b98ad97c886 --- /dev/null +++ b/toolchain/parser/testdata/namespace/fail_no_name.carbon @@ -0,0 +1,14 @@ +// 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 +// CHECK:STDOUT: [ +// CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ';'}, +// CHECK:STDOUT: {kind: 'Namespace', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] + +// CHECK:STDERR: fail_no_name.carbon:[[@LINE+1]]:10: `namespace` introducer should be followed by a name. +namespace; diff --git a/toolchain/parser/testdata/namespace/nested.carbon b/toolchain/parser/testdata/namespace/nested.carbon new file mode 100644 index 000000000000..1f65d4e3a560 --- /dev/null +++ b/toolchain/parser/testdata/namespace/nested.carbon @@ -0,0 +1,41 @@ +// 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 +// CHECK:STDOUT: [ +// CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, +// 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: '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: 'Name', text: 'Bar'}, +// CHECK:STDOUT: {kind: 'QualifiedDeclaration', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'Name', text: 'Baz'}, +// CHECK:STDOUT: {kind: 'QualifiedDeclaration', text: '.', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'NameExpression', text: 'Foo'}, +// CHECK:STDOUT: {kind: 'Name', text: 'Bar'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'Name', text: 'Wiz'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpression', text: '.', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'CallExpressionStart', text: '(', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'CallExpression', text: ')', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'ExpressionStatement', text: ';', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] + +namespace Foo; +namespace Foo.Bar; + +fn Foo.Bar.Baz() { + Foo.Bar.Wiz(); +} diff --git a/toolchain/parser/testdata/operators/fail_infix_uneven_space_after.carbon b/toolchain/parser/testdata/operators/fail_infix_uneven_space_after.carbon index 30b8e7d544fc..bf4cb93f8ca5 100644 --- a/toolchain/parser/testdata/operators/fail_infix_uneven_space_after.carbon +++ b/toolchain/parser/testdata/operators/fail_infix_uneven_space_after.carbon @@ -17,5 +17,5 @@ // TODO: We could figure out that this first Failed example is infix // with one-token lookahead. -// CHECK:STDERR: fail_infix_uneven_space_after.carbon:[[@LINE+1]]:16: Expected `;` to terminate `var` declaration. +// CHECK:STDERR: fail_infix_uneven_space_after.carbon:[[@LINE+1]]:16: `var` declarations must end with a `;`. var n: i8 = n* n; diff --git a/toolchain/parser/testdata/operators/fail_star_star_no_space.carbon b/toolchain/parser/testdata/operators/fail_star_star_no_space.carbon index 98c06760bd67..ae1990861014 100644 --- a/toolchain/parser/testdata/operators/fail_star_star_no_space.carbon +++ b/toolchain/parser/testdata/operators/fail_star_star_no_space.carbon @@ -20,5 +20,5 @@ // before we notice the missing whitespace around the second `*`. // It'd be better to (somehow) form n*(*p) and reject due to the missing // whitespace around the first `*`. -// CHECK:STDERR: fail_star_star_no_space.carbon:[[@LINE+1]]:16: Expected `;` to terminate `var` declaration. +// CHECK:STDERR: fail_star_star_no_space.carbon:[[@LINE+1]]:16: `var` declarations must end with a `;`. var n: i8 = n**p; diff --git a/toolchain/parser/testdata/package/fail_no_semi.carbon b/toolchain/parser/testdata/package/fail_no_semi.carbon index 7cc9f9d7617e..7f52f5a005f0 100644 --- a/toolchain/parser/testdata/package/fail_no_semi.carbon +++ b/toolchain/parser/testdata/package/fail_no_semi.carbon @@ -11,5 +11,5 @@ // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:21: Expected `;` to end package directive. +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:21: `package` declarations must end with a `;`. package Geometry api diff --git a/toolchain/parser/testdata/return/fail_expr_no_semi.carbon b/toolchain/parser/testdata/return/fail_expr_no_semi.carbon index 533cb7b159a6..50f4de959723 100644 --- a/toolchain/parser/testdata/return/fail_expr_no_semi.carbon +++ b/toolchain/parser/testdata/return/fail_expr_no_semi.carbon @@ -18,5 +18,5 @@ fn F() { return x -// CHECK:STDERR: fail_expr_no_semi.carbon:[[@LINE+1]]:1: Expected `;` after `return`. +// CHECK:STDERR: fail_expr_no_semi.carbon:[[@LINE+1]]:1: `return` statements must end with a `;`. } diff --git a/toolchain/parser/testdata/return/fail_no_semi.carbon b/toolchain/parser/testdata/return/fail_no_semi.carbon index 80c1c5872085..9376ddd33b9b 100644 --- a/toolchain/parser/testdata/return/fail_no_semi.carbon +++ b/toolchain/parser/testdata/return/fail_no_semi.carbon @@ -19,5 +19,5 @@ fn F() { return // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:1: Expected expression. -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:1: Expected `;` after `return`. +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:1: `return` statements must end with a `;`. } diff --git a/toolchain/parser/testdata/while/fail_no_semi.carbon b/toolchain/parser/testdata/while/fail_no_semi.carbon index 38e9388254c3..3d2090ce2af3 100644 --- a/toolchain/parser/testdata/while/fail_no_semi.carbon +++ b/toolchain/parser/testdata/while/fail_no_semi.carbon @@ -39,11 +39,11 @@ fn F() { while (a) { if (b) { break - // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:5: Expected `;` after `break`. + // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:5: `break` statements must end with a `;`. } if (c) { continue - // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:5: Expected `;` after `continue`. + // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:5: `continue` statements must end with a `;`. } } } diff --git a/toolchain/semantics/semantics_context.cpp b/toolchain/semantics/semantics_context.cpp index 004bfd6d20bc..271de8e13388 100644 --- a/toolchain/semantics/semantics_context.cpp +++ b/toolchain/semantics/semantics_context.cpp @@ -8,7 +8,6 @@ #include "common/vlog.h" #include "toolchain/diagnostics/diagnostic_kind.h" -#include "toolchain/lexer/token_kind.h" #include "toolchain/lexer/tokenized_buffer.h" #include "toolchain/parser/parse_node_kind.h" #include "toolchain/semantics/semantics_ir.h" @@ -17,6 +16,8 @@ namespace Carbon { +CARBON_DIAGNOSTIC(NameNotFound, Error, "Name {0} not found", llvm::StringRef); + SemanticsContext::SemanticsContext(const TokenizedBuffer& tokens, DiagnosticEmitter& emitter, const ParseTree& parse_tree, @@ -74,43 +75,93 @@ 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::AddNameToLookup(ParseTree::Node name_node, SemanticsStringId name_id, SemanticsNodeId target_id) -> void { if (current_scope().names.insert(name_id).second) { name_lookup_[name_id].push_back(target_id); } else { - CARBON_DIAGNOSTIC(NameRedefined, Error, "Redefining {0} in the same scope.", - llvm::StringRef); - CARBON_DIAGNOSTIC(PreviousDefinition, Note, "Previous definition is here."); auto prev_def_id = name_lookup_[name_id].back(); auto prev_def = semantics_ir_->GetNode(prev_def_id); - - emitter_->Build(name_node, NameRedefined, semantics_ir_->GetString(name_id)) - .Note(prev_def.parse_node(), PreviousDefinition) + 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; + } +} + auto SemanticsContext::LookupName(ParseTree::Node parse_node, - llvm::StringRef name) -> SemanticsNodeId { - CARBON_DIAGNOSTIC(NameNotFound, Error, "Name {0} not found", llvm::StringRef); + SemanticsStringId name_id, + SemanticsNameScopeId scope_id, + bool print_diagnostics) -> SemanticsNodeId { + if (scope_id == SemanticsNameScopeId::Invalid) { + 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)); + } + return SemanticsNodeId::BuiltinInvalidType; + } + CARBON_CHECK(!it->second.empty()) + << "Should have been erased: " << semantics_ir_->GetString(name_id); - auto name_id = semantics_ir_->GetStringID(name); - if (!name_id) { - emitter_->Emit(parse_node, NameNotFound, name); - return SemanticsNodeId::BuiltinInvalidType; + // TODO: Check for ambiguous lookups. + return it->second.back(); + } else { + const auto& scope = semantics_ir_->GetNameScope(scope_id); + auto it = scope.find(name_id); + if (it == scope.end()) { + if (print_diagnostics) { + emitter_->Emit(parse_node, NameNotFound, + semantics_ir_->GetString(name_id)); + } + return SemanticsNodeId::BuiltinInvalidType; + } + + return it->second; } - - auto it = name_lookup_.find(*name_id); - if (it == name_lookup_.end()) { - emitter_->Emit(parse_node, NameNotFound, name); - return SemanticsNodeId::BuiltinInvalidType; - } - CARBON_CHECK(!it->second.empty()) << "Should have been erased: " << name; - - // TODO: Check for ambiguous lookups. - return it->second.back(); } auto SemanticsContext::PushScope() -> void { scope_stack_.push_back({}); } @@ -244,6 +295,105 @@ 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(ParseNodeKind::QualifiedDeclaration); + } else { + // The name had no qualifiers, so we need to process the node now. + auto [parse_node, node_or_name_id] = + node_stack_.PopWithParseNode(); + 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::BuiltinInvalidType) { + // 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 805af9268ad7..a087e049cd60 100644 --- a/toolchain/semantics/semantics_context.h +++ b/toolchain/semantics/semantics_context.h @@ -20,6 +20,83 @@ 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, @@ -47,8 +124,14 @@ class SemanticsContext { auto AddNameToLookup(ParseTree::Node name_node, SemanticsStringId name_id, SemanticsNodeId target_id) -> void; - // Lookup up a name, returning the referenced node. - auto LookupName(ParseTree::Node parse_node, llvm::StringRef name) + // 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; // Pushes a new scope onto scope_stack_. @@ -99,6 +182,20 @@ 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 @@ -275,6 +372,10 @@ 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_; + // 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, // regardless of how many scopes exist between the name declaration and diff --git a/toolchain/semantics/semantics_handle.cpp b/toolchain/semantics/semantics_handle.cpp index 310372b72009..9ad0cc899418 100644 --- a/toolchain/semantics/semantics_handle.cpp +++ b/toolchain/semantics/semantics_handle.cpp @@ -23,56 +23,6 @@ auto SemanticsHandleDeducedParameterListStart(SemanticsContext& context, return context.TODO(parse_node, "HandleDeducedParameterListStart"); } -auto SemanticsHandleDesignatorExpression(SemanticsContext& context, - ParseTree::Node parse_node) -> bool { - auto name_id = - context.node_stack().Pop(ParseNodeKind::Name); - - auto base_id = context.node_stack().Pop(); - auto base = context.semantics_ir().GetNode(base_id); - auto base_type = context.semantics_ir().GetNode( - context.semantics_ir().GetType(base.type_id())); - - switch (base_type.kind()) { - case SemanticsNodeKind::StructType: { - auto refs = - context.semantics_ir().GetNodeBlock(base_type.GetAsStructType()); - // TODO: Do we need to optimize this with a lookup table for O(1)? - for (int i = 0; i < static_cast(refs.size()); ++i) { - auto ref = context.semantics_ir().GetNode(refs[i]); - if (name_id == ref.GetAsStructTypeField()) { - context.AddNodeAndPush( - parse_node, - SemanticsNode::StructMemberAccess::Make( - parse_node, ref.type_id(), base_id, SemanticsMemberIndex(i))); - return true; - } - } - CARBON_DIAGNOSTIC(DesignatorExpressionNameNotFound, Error, - "Type `{0}` does not have a member `{1}`.", std::string, - llvm::StringRef); - context.emitter().Emit( - parse_node, DesignatorExpressionNameNotFound, - context.semantics_ir().StringifyType(base.type_id()), - context.semantics_ir().GetString(name_id)); - break; - } - default: { - CARBON_DIAGNOSTIC(DesignatorExpressionUnsupported, Error, - "Type `{0}` does not support designator expressions.", - std::string); - context.emitter().Emit( - parse_node, DesignatorExpressionUnsupported, - context.semantics_ir().StringifyType(base.type_id())); - break; - } - } - - // Should only be reached on error. - context.node_stack().Push(parse_node, SemanticsNodeId::BuiltinInvalidType); - return true; -} - auto SemanticsHandleEmptyDeclaration(SemanticsContext& /*context*/, ParseTree::Node /*parse_node*/) -> bool { // Empty declarations have no actions associated. @@ -233,6 +183,65 @@ auto SemanticsHandleLiteral(SemanticsContext& context, return true; } +auto SemanticsHandleMemberAccessExpression(SemanticsContext& context, + ParseTree::Node parse_node) -> bool { + auto name_id = + context.node_stack().Pop(ParseNodeKind::Name); + + auto base_id = context.node_stack().Pop(); + auto base = context.semantics_ir().GetNode(base_id); + if (base.kind() == SemanticsNodeKind::Namespace) { + // For a namespace, just resolve the name. + auto node_id = + context.LookupName(parse_node, name_id, base.GetAsNamespace(), + /*print_diagnostics=*/true); + context.node_stack().Push(parse_node, node_id); + return true; + } + + auto base_type = context.semantics_ir().GetNode( + context.semantics_ir().GetType(base.type_id())); + + switch (base_type.kind()) { + case SemanticsNodeKind::StructType: { + auto refs = + context.semantics_ir().GetNodeBlock(base_type.GetAsStructType()); + // TODO: Do we need to optimize this with a lookup table for O(1)? + for (int i = 0; i < static_cast(refs.size()); ++i) { + auto ref = context.semantics_ir().GetNode(refs[i]); + if (name_id == ref.GetAsStructTypeField()) { + context.AddNodeAndPush( + parse_node, + SemanticsNode::StructMemberAccess::Make( + parse_node, ref.type_id(), base_id, SemanticsMemberIndex(i))); + return true; + } + } + CARBON_DIAGNOSTIC(QualifiedExpressionNameNotFound, Error, + "Type `{0}` does not have a member `{1}`.", std::string, + llvm::StringRef); + context.emitter().Emit( + parse_node, QualifiedExpressionNameNotFound, + context.semantics_ir().StringifyType(base.type_id()), + context.semantics_ir().GetString(name_id)); + break; + } + default: { + CARBON_DIAGNOSTIC(QualifiedExpressionUnsupported, Error, + "Type `{0}` does not support qualified expressions.", + std::string); + context.emitter().Emit( + parse_node, QualifiedExpressionUnsupported, + context.semantics_ir().StringifyType(base.type_id())); + break; + } + } + + // Should only be reached on error. + context.node_stack().Push(parse_node, SemanticsNodeId::BuiltinInvalidType); + return true; +} + auto SemanticsHandleName(SemanticsContext& context, ParseTree::Node parse_node) -> bool { auto name_str = context.parse_tree().GetNodeText(parse_node); @@ -244,8 +253,12 @@ auto SemanticsHandleName(SemanticsContext& context, ParseTree::Node parse_node) auto SemanticsHandleNameExpression(SemanticsContext& context, ParseTree::Node parse_node) -> bool { - auto name = context.parse_tree().GetNodeText(parse_node); - context.node_stack().Push(parse_node, context.LookupName(parse_node, name)); + auto name_str = context.parse_tree().GetNodeText(parse_node); + auto name_id = context.semantics_ir().AddString(name_str); + context.node_stack().Push( + parse_node, + context.LookupName(parse_node, name_id, SemanticsNameScopeId::Invalid, + /*print_diagnostics=*/true)); return true; } @@ -372,6 +385,31 @@ auto SemanticsHandlePrefixOperator(SemanticsContext& context, return true; } +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().PopWithParseNode(); + 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().PopWithParseNode(); + 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); + } else { + // Nothing to do: the QualifiedDeclaration remains as a bracketing node for + // later QualifiedDeclarations. + } + context.ApplyDeclarationNameQualifier(parse_node2, node_or_name_id2); + + return true; +} + auto SemanticsHandleReturnType(SemanticsContext& context, ParseTree::Node parse_node) -> bool { // Propagate the type expression. diff --git a/toolchain/semantics/semantics_handle_call_expression.cpp b/toolchain/semantics/semantics_handle_call_expression.cpp index a25a6c766c80..04840712aeda 100644 --- a/toolchain/semantics/semantics_handle_call_expression.cpp +++ b/toolchain/semantics/semantics_handle_call_expression.cpp @@ -57,8 +57,7 @@ auto SemanticsHandleCallExpressionComma(SemanticsContext& context, auto SemanticsHandleCallExpressionStart(SemanticsContext& context, ParseTree::Node parse_node) -> bool { - auto name_id = - context.node_stack().Pop(ParseNodeKind::NameExpression); + auto name_id = context.node_stack().Pop(); context.node_stack().Push(parse_node, name_id); context.ParamOrArgStart(); return true; diff --git a/toolchain/semantics/semantics_handle_function.cpp b/toolchain/semantics/semantics_handle_function.cpp index 723c7d4c3e54..2bb0ba1a8d71 100644 --- a/toolchain/semantics/semantics_handle_function.cpp +++ b/toolchain/semantics/semantics_handle_function.cpp @@ -51,9 +51,7 @@ auto SemanticsHandleFunctionDefinitionStart(SemanticsContext& context, } auto param_refs_id = context.node_stack().Pop( ParseNodeKind::ParameterList); - auto [name_node, name_id] = - context.node_stack().PopWithParseNode( - ParseNodeKind::Name); + auto name_context = context.PopDeclarationName(); auto fn_node = context.node_stack().PopForSoloParseNode( ParseNodeKind::FunctionIntroducer); @@ -61,16 +59,19 @@ auto SemanticsHandleFunctionDefinitionStart(SemanticsContext& context, auto outer_block = context.node_block_stack().PeekForAdd(); context.node_block_stack().Push(); + // TODO: Support out-of-line definitions, which will have a resolved + // name_context. Right now, those become errors in AddNameToLookup. + // Add the callable. auto function_id = context.semantics_ir().AddFunction( - {.name_id = name_id, + {.name_id = name_context.unresolved_name_id, .param_refs_id = param_refs_id, .return_type_id = return_type_id, .body_block_ids = {context.node_block_stack().PeekForAdd()}}); auto decl_id = context.AddNodeToBlock( outer_block, SemanticsNode::FunctionDeclaration::Make(fn_node, function_id)); - context.AddNameToLookup(name_node, name_id, decl_id); + context.AddNameToLookup(name_context, decl_id); context.PushScope(); for (auto ref_id : context.semantics_ir().GetNodeBlock(param_refs_id)) { @@ -86,8 +87,10 @@ auto SemanticsHandleFunctionDefinitionStart(SemanticsContext& context, auto SemanticsHandleFunctionIntroducer(SemanticsContext& context, ParseTree::Node parse_node) -> bool { - // No action, just a bracketing node. + // Push the bracketing node. context.node_stack().Push(parse_node); + // A name should always follow. + context.PushDeclarationName(); return true; } diff --git a/toolchain/semantics/semantics_handle_namespace.cpp b/toolchain/semantics/semantics_handle_namespace.cpp new file mode 100644 index 000000000000..585edd04e73e --- /dev/null +++ b/toolchain/semantics/semantics_handle_namespace.cpp @@ -0,0 +1,25 @@ +// 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_context.h" +#include "toolchain/semantics/semantics_node.h" + +namespace Carbon { + +auto SemanticsHandleNamespaceStart(SemanticsContext& context, + ParseTree::Node /*parse_node*/) -> bool { + context.PushDeclarationName(); + return true; +} + +auto SemanticsHandleNamespace(SemanticsContext& context, + ParseTree::Node parse_node) -> bool { + auto name_context = context.PopDeclarationName(); + auto namespace_id = context.AddNode(SemanticsNode::Namespace::Make( + parse_node, context.semantics_ir().AddNameScope())); + context.AddNameToLookup(name_context, namespace_id); + return true; +} + +} // namespace Carbon diff --git a/toolchain/semantics/semantics_ir.cpp b/toolchain/semantics/semantics_ir.cpp index 022361963ad0..278077eba0cd 100644 --- a/toolchain/semantics/semantics_ir.cpp +++ b/toolchain/semantics/semantics_ir.cpp @@ -254,6 +254,7 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string { case SemanticsNodeKind::CrossReference: case SemanticsNodeKind::FunctionDeclaration: case SemanticsNodeKind::IntegerLiteral: + case SemanticsNodeKind::Namespace: case SemanticsNodeKind::RealLiteral: case SemanticsNodeKind::Return: case SemanticsNodeKind::ReturnExpression: diff --git a/toolchain/semantics/semantics_ir.h b/toolchain/semantics/semantics_ir.h index 6902fa687911..b98966b7dfe9 100644 --- a/toolchain/semantics/semantics_ir.h +++ b/toolchain/semantics/semantics_ir.h @@ -114,6 +114,27 @@ class SemanticsIR { return integer_literals_[int_id.index]; } + // Adds a name scope, returning an ID to reference it. + auto AddNameScope() -> SemanticsNameScopeId { + SemanticsNameScopeId name_scopes_id(name_scopes_.size()); + name_scopes_.resize(name_scopes_id.index + 1); + return name_scopes_id; + } + + // Adds an entry to a name scope. Returns true on success, false on + // duplicates. + auto AddNameScopeEntry(SemanticsNameScopeId scope_id, + SemanticsStringId name_id, SemanticsNodeId target_id) + -> bool { + return name_scopes_[scope_id.index].insert({name_id, target_id}).second; + } + + // Returns the requested name scope. + auto GetNameScope(SemanticsNameScopeId scope_id) + -> const llvm::DenseMap& { + return name_scopes_[scope_id.index]; + } + // Adds a node to a specified block, returning an ID to reference the node. auto AddNode(SemanticsNodeBlockId block_id, SemanticsNode node) -> SemanticsNodeId { @@ -271,6 +292,10 @@ class SemanticsIR { // Storage for integer literals. llvm::SmallVector integer_literals_; + // Storage for name scopes. + llvm::SmallVector> + name_scopes_; + // Storage for real literals. llvm::SmallVector real_literals_; diff --git a/toolchain/semantics/semantics_node.h b/toolchain/semantics/semantics_node.h index 925c398a2e58..6d0e2eb4ab02 100644 --- a/toolchain/semantics/semantics_node.h +++ b/toolchain/semantics/semantics_node.h @@ -101,6 +101,21 @@ struct SemanticsIntegerLiteralId : public IndexBase { } }; +// The ID of a name scope. +struct SemanticsNameScopeId : public IndexBase { + // An explicitly invalid ID. + static const SemanticsNameScopeId Invalid; + + using IndexBase::IndexBase; + auto Print(llvm::raw_ostream& out) const -> void { + out << "name_scope"; + IndexBase::Print(out); + } +}; + +constexpr SemanticsNameScopeId SemanticsNameScopeId::Invalid = + SemanticsNameScopeId(SemanticsNameScopeId::InvalidIndex); + // The ID of a node block. struct SemanticsNodeBlockId : public IndexBase { // All SemanticsIR instances must provide the 0th node block as empty. @@ -355,6 +370,9 @@ class SemanticsNode { using IntegerLiteral = Factory; + using Namespace = FactoryNoType; + using RealLiteral = Factory; diff --git a/toolchain/semantics/semantics_node_kind.def b/toolchain/semantics/semantics_node_kind.def index 4e6dd615afeb..5f3a54eb04fc 100644 --- a/toolchain/semantics/semantics_node_kind.def +++ b/toolchain/semantics/semantics_node_kind.def @@ -39,6 +39,7 @@ CARBON_SEMANTICS_NODE_KIND(Builtin) CARBON_SEMANTICS_NODE_KIND(Call) CARBON_SEMANTICS_NODE_KIND(FunctionDeclaration) CARBON_SEMANTICS_NODE_KIND(IntegerLiteral) +CARBON_SEMANTICS_NODE_KIND(Namespace) CARBON_SEMANTICS_NODE_KIND(RealLiteral) CARBON_SEMANTICS_NODE_KIND_WITH_TERMINATOR_KIND(Return, Terminator) CARBON_SEMANTICS_NODE_KIND_WITH_TERMINATOR_KIND(ReturnExpression, Terminator) diff --git a/toolchain/semantics/testdata/basics/fail_name_lookup.carbon b/toolchain/semantics/testdata/basics/fail_name_lookup.carbon index 2f235bef5737..df7b5e7cf519 100644 --- a/toolchain/semantics/testdata/basics/fail_name_lookup.carbon +++ b/toolchain/semantics/testdata/basics/fail_name_lookup.carbon @@ -13,6 +13,7 @@ // CHECK:STDOUT: ] // CHECK:STDOUT: strings: [ // CHECK:STDOUT: Main, +// CHECK:STDOUT: x, // CHECK:STDOUT: ] // CHECK:STDOUT: types: [ // CHECK:STDOUT: nodeEmptyTupleType, diff --git a/toolchain/semantics/testdata/designators/fail_unsupported.carbon b/toolchain/semantics/testdata/basics/fail_qualifier_unsupported.carbon similarity index 91% rename from toolchain/semantics/testdata/designators/fail_unsupported.carbon rename to toolchain/semantics/testdata/basics/fail_qualifier_unsupported.carbon index 49f6a32ebcad..d9417874cf16 100644 --- a/toolchain/semantics/testdata/designators/fail_unsupported.carbon +++ b/toolchain/semantics/testdata/basics/fail_qualifier_unsupported.carbon @@ -38,5 +38,5 @@ // CHECK:STDOUT: ] var x: i32; -// CHECK:STDERR: fail_unsupported.carbon:[[@LINE+1]]:15: Type `i32` does not support designator expressions. +// CHECK:STDERR: fail_qualifier_unsupported.carbon:[[@LINE+1]]:15: Type `i32` does not support qualified expressions. var y: i32 = x.b; diff --git a/toolchain/semantics/testdata/function/definition/fail_param_name_conflict.carbon b/toolchain/semantics/testdata/function/definition/fail_param_name_conflict.carbon index 1c2c5ad3fc5e..b0608a09b9da 100644 --- a/toolchain/semantics/testdata/function/definition/fail_param_name_conflict.carbon +++ b/toolchain/semantics/testdata/function/definition/fail_param_name_conflict.carbon @@ -48,6 +48,6 @@ // CHECK:STDOUT: ], // CHECK:STDOUT: ] -// CHECK:STDERR: fail_param_name_conflict.carbon:[[@LINE+2]]:16: Redefining a in the same scope. -// CHECK:STDERR: fail_param_name_conflict.carbon:[[@LINE+1]]:8: Previous definition is here. +// CHECK:STDERR: fail_param_name_conflict.carbon:[[@LINE+2]]:16: Duplicate name being declared in the same scope. +// CHECK:STDERR: fail_param_name_conflict.carbon:[[@LINE+1]]:8: Name is previously declared here. fn Bar(a: i32, a: i32) {} diff --git a/toolchain/semantics/testdata/namespace/fail_duplicate.carbon b/toolchain/semantics/testdata/namespace/fail_duplicate.carbon new file mode 100644 index 000000000000..30ad0c458443 --- /dev/null +++ b/toolchain/semantics/testdata/namespace/fail_duplicate.carbon @@ -0,0 +1,53 @@ +// 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 +// CHECK:STDOUT: cross_reference_irs_size: 1 +// CHECK:STDOUT: functions: [ +// CHECK:STDOUT: {name: str1, param_refs: block0, body: {block2}}}, +// CHECK:STDOUT: {name: str8, param_refs: block0, body: {block3}}}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: integer_literals: [ +// CHECK:STDOUT: ] +// CHECK:STDOUT: real_literals: [ +// CHECK:STDOUT: ] +// CHECK:STDOUT: strings: [ +// CHECK:STDOUT: Foo, +// CHECK:STDOUT: Baz, +// CHECK:STDOUT: ] +// CHECK:STDOUT: types: [ +// CHECK:STDOUT: nodeEmptyTupleType, +// CHECK:STDOUT: ] +// CHECK:STDOUT: nodes: [ +// CHECK:STDOUT: {kind: Namespace, arg0: name_scope0}, +// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: function0}, +// CHECK:STDOUT: {kind: Return}, +// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: function1}, +// CHECK:STDOUT: {kind: Return}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: node_blocks: [ +// CHECK:STDOUT: [ +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+0, +// CHECK:STDOUT: node+1, +// CHECK:STDOUT: node+3, +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+2, +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+4, +// CHECK:STDOUT: ], +// CHECK:STDOUT: ] + +namespace Foo; + +fn Foo.Baz() { +} + +// CHECK:STDERR: fail_duplicate.carbon:[[@LINE+2]]:8: Duplicate name being declared in the same scope. +// CHECK:STDERR: fail_duplicate.carbon:[[@LINE-4]]:1: Name is previously declared here. +fn Foo.Baz() { +} diff --git a/toolchain/semantics/testdata/namespace/fail_unresolved_scope.carbon b/toolchain/semantics/testdata/namespace/fail_unresolved_scope.carbon new file mode 100644 index 000000000000..82117cc7e463 --- /dev/null +++ b/toolchain/semantics/testdata/namespace/fail_unresolved_scope.carbon @@ -0,0 +1,38 @@ +// 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 +// CHECK:STDOUT: cross_reference_irs_size: 1 +// CHECK:STDOUT: functions: [ +// CHECK:STDOUT: {name: str0, param_refs: block0, body: {block2}}}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: integer_literals: [ +// CHECK:STDOUT: ] +// CHECK:STDOUT: real_literals: [ +// CHECK:STDOUT: ] +// CHECK:STDOUT: strings: [ +// CHECK:STDOUT: Foo, +// CHECK:STDOUT: Baz, +// CHECK:STDOUT: ] +// CHECK:STDOUT: types: [ +// CHECK:STDOUT: nodeEmptyTupleType, +// CHECK:STDOUT: ] +// CHECK:STDOUT: nodes: [ +// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: function0}, +// CHECK:STDOUT: {kind: Return}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: node_blocks: [ +// CHECK:STDOUT: [ +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+0, +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+1, +// CHECK:STDOUT: ], +// CHECK:STDOUT: ] + +// CHECK:STDERR: fail_unresolved_scope.carbon:[[@LINE+1]]:4: Name Foo not found +fn Foo.Baz() { +} diff --git a/toolchain/semantics/testdata/namespace/function.carbon b/toolchain/semantics/testdata/namespace/function.carbon new file mode 100644 index 000000000000..4b4896af8feb --- /dev/null +++ b/toolchain/semantics/testdata/namespace/function.carbon @@ -0,0 +1,66 @@ +// 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 +// CHECK:STDOUT: cross_reference_irs_size: 1 +// CHECK:STDOUT: functions: [ +// CHECK:STDOUT: {name: str1, param_refs: block0, body: {block2}}}, +// CHECK:STDOUT: {name: str1, param_refs: block0, body: {block3}}}, +// CHECK:STDOUT: {name: str2, param_refs: block0, body: {block4}}}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: integer_literals: [ +// CHECK:STDOUT: ] +// CHECK:STDOUT: real_literals: [ +// CHECK:STDOUT: ] +// CHECK:STDOUT: strings: [ +// CHECK:STDOUT: Foo, +// CHECK:STDOUT: Baz, +// CHECK:STDOUT: Bar, +// CHECK:STDOUT: ] +// CHECK:STDOUT: types: [ +// CHECK:STDOUT: nodeEmptyTupleType, +// CHECK:STDOUT: ] +// CHECK:STDOUT: nodes: [ +// CHECK:STDOUT: {kind: Namespace, arg0: name_scope0}, +// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: function0}, +// CHECK:STDOUT: {kind: Return}, +// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: function1}, +// CHECK:STDOUT: {kind: Return}, +// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: function2}, +// CHECK:STDOUT: {kind: Call, arg0: block0, arg1: function1}, +// CHECK:STDOUT: {kind: Return}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: node_blocks: [ +// CHECK:STDOUT: [ +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+0, +// CHECK:STDOUT: node+1, +// CHECK:STDOUT: node+3, +// CHECK:STDOUT: node+5, +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+2, +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+4, +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+6, +// CHECK:STDOUT: node+7, +// CHECK:STDOUT: ], +// CHECK:STDOUT: ] + +namespace Foo; + +// Never called, just here to help catch bugs in name lookup. +fn Baz() { +} + +fn Foo.Baz() { +} + +fn Bar() { + Foo.Baz(); +} diff --git a/toolchain/semantics/testdata/namespace/nested.carbon b/toolchain/semantics/testdata/namespace/nested.carbon new file mode 100644 index 000000000000..d90d3e415b3a --- /dev/null +++ b/toolchain/semantics/testdata/namespace/nested.carbon @@ -0,0 +1,59 @@ +// 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 +// CHECK:STDOUT: cross_reference_irs_size: 1 +// CHECK:STDOUT: functions: [ +// CHECK:STDOUT: {name: str2, param_refs: block0, body: {block2}}}, +// CHECK:STDOUT: {name: str3, param_refs: block0, body: {block3}}}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: integer_literals: [ +// CHECK:STDOUT: ] +// CHECK:STDOUT: real_literals: [ +// CHECK:STDOUT: ] +// CHECK:STDOUT: strings: [ +// CHECK:STDOUT: Foo, +// CHECK:STDOUT: Bar, +// CHECK:STDOUT: Wiz, +// CHECK:STDOUT: Baz, +// CHECK:STDOUT: ] +// CHECK:STDOUT: types: [ +// CHECK:STDOUT: nodeEmptyTupleType, +// CHECK:STDOUT: ] +// CHECK:STDOUT: nodes: [ +// CHECK:STDOUT: {kind: Namespace, arg0: name_scope0}, +// CHECK:STDOUT: {kind: Namespace, arg0: name_scope1}, +// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: function0}, +// CHECK:STDOUT: {kind: Return}, +// CHECK:STDOUT: {kind: FunctionDeclaration, arg0: function1}, +// CHECK:STDOUT: {kind: Call, arg0: block0, arg1: function0}, +// CHECK:STDOUT: {kind: Return}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: node_blocks: [ +// CHECK:STDOUT: [ +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+0, +// CHECK:STDOUT: node+1, +// CHECK:STDOUT: node+2, +// CHECK:STDOUT: node+4, +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+3, +// CHECK:STDOUT: ], +// CHECK:STDOUT: [ +// CHECK:STDOUT: node+5, +// CHECK:STDOUT: node+6, +// CHECK:STDOUT: ], +// CHECK:STDOUT: ] + +namespace Foo; +namespace Foo.Bar; + +fn Foo.Bar.Wiz() { +} + +fn Foo.Bar.Baz() { + Foo.Bar.Wiz(); +} diff --git a/toolchain/semantics/testdata/var/fail_duplicate_decl.carbon b/toolchain/semantics/testdata/var/fail_duplicate_decl.carbon index 17b9c287ef10..a97451030159 100644 --- a/toolchain/semantics/testdata/var/fail_duplicate_decl.carbon +++ b/toolchain/semantics/testdata/var/fail_duplicate_decl.carbon @@ -55,7 +55,7 @@ fn Main() { var x: i32 = 0; - // CHECK:STDERR: fail_duplicate_decl.carbon:[[@LINE+2]]:7: Redefining x in the same scope. - // CHECK:STDERR: fail_duplicate_decl.carbon:[[@LINE-2]]:7: Previous definition is here. + // CHECK:STDERR: fail_duplicate_decl.carbon:[[@LINE+2]]:7: Duplicate name being declared in the same scope. + // CHECK:STDERR: fail_duplicate_decl.carbon:[[@LINE-2]]:7: Name is previously declared here. var x: i32 = 0; }