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; }