diff --git a/toolchain/check/handle_modifier.cpp b/toolchain/check/handle_modifier.cpp index 75fa2c60b412..3cf89bb312f1 100644 --- a/toolchain/check/handle_modifier.cpp +++ b/toolchain/check/handle_modifier.cpp @@ -94,4 +94,14 @@ static auto HandleModifier(Context& context, Parse::NodeId node_id, } #include "toolchain/parse/node_kind.def" +auto HandleParseNode(Context& context, + Parse::ExternModifierWithLibraryId node_id) -> bool { + return context.TODO(node_id, "extern library syntax"); +} + +auto HandleParseNode(Context& context, Parse::ExternModifierId node_id) + -> bool { + return HandleModifier(context, node_id, KeywordModifierSet::Extern); +} + } // namespace Carbon::Check diff --git a/toolchain/check/testdata/function/declaration/no_prelude/extern.carbon b/toolchain/check/testdata/function/declaration/no_prelude/extern.carbon index 6e4e7b6b204d..8c960562acc6 100644 --- a/toolchain/check/testdata/function/declaration/no_prelude/extern.carbon +++ b/toolchain/check/testdata/function/declaration/no_prelude/extern.carbon @@ -44,18 +44,30 @@ fn F(); // --- fail_member_extern.carbon +library "member_extern"; + class C { // CHECK:STDERR: fail_member_extern.carbon:[[@LINE+4]]:3: ERROR: `extern` not allowed on `fn` declaration that is a member. // CHECK:STDERR: extern fn F(); // CHECK:STDERR: ^~~~~~ // CHECK:STDERR: extern fn F(); - // CHECK:STDERR: fail_member_extern.carbon:[[@LINE+3]]:3: ERROR: `extern` not allowed on `fn` declaration that is a member. + // CHECK:STDERR: fail_member_extern.carbon:[[@LINE+4]]:3: ERROR: `extern` not allowed on `fn` declaration that is a member. // CHECK:STDERR: extern fn G[self: Self](); // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: extern fn G[self: Self](); } +// --- fail_todo_extern_library.carbon + +library "extern_library"; + +// CHECK:STDERR: fail_todo_extern_library.carbon:[[@LINE+3]]:1: ERROR: Semantics TODO: `extern library syntax`. +// CHECK:STDERR: extern library "foo" fn F(); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~ +extern library "foo" fn F(); + // CHECK:STDOUT: --- basic.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { @@ -132,8 +144,8 @@ class C { // CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} // CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [template = constants.%G] { // CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%C [template = constants.%C] -// CHECK:STDOUT: %self.loc11_15.1: %C = param self -// CHECK:STDOUT: %self.loc11_15.2: %C = bind_name self, %self.loc11_15.1 +// CHECK:STDOUT: %self.loc14_15.1: %C = param self +// CHECK:STDOUT: %self.loc14_15.2: %C = bind_name self, %self.loc14_15.1 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: !members: @@ -144,5 +156,9 @@ class C { // CHECK:STDOUT: // CHECK:STDOUT: fn @F(); // CHECK:STDOUT: -// CHECK:STDOUT: fn @G[@C.%self.loc11_15.2: %C](); +// CHECK:STDOUT: fn @G[@C.%self.loc14_15.2: %C](); +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_todo_extern_library.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: file {} // CHECK:STDOUT: diff --git a/toolchain/parse/context.cpp b/toolchain/parse/context.cpp index b79400e385f7..e6ef5457ffbf 100644 --- a/toolchain/parse/context.cpp +++ b/toolchain/parse/context.cpp @@ -437,6 +437,43 @@ auto Context::RecoverFromDeclError(StateStackEntry state, NodeKind node_kind, /*has_error=*/true); } +auto Context::ParseLibraryName(bool accept_default) + -> std::optional { + if (auto library_name_token = ConsumeIf(Lex::TokenKind::StringLiteral)) { + AddLeafNode(NodeKind::LibraryName, *library_name_token); + return tokens().GetStringLiteralValue(*library_name_token); + } + + if (accept_default) { + if (auto default_token = ConsumeIf(Lex::TokenKind::Default)) { + AddLeafNode(NodeKind::DefaultLibrary, *default_token); + return StringLiteralValueId::Invalid; + } + } + + CARBON_DIAGNOSTIC( + ExpectedLibraryNameOrDefault, Error, + "Expected `default` or a string literal to specify the library name."); + CARBON_DIAGNOSTIC(ExpectedLibraryName, Error, + "Expected a string literal to specify the library name."); + emitter().Emit(*position(), accept_default ? ExpectedLibraryNameOrDefault + : ExpectedLibraryName); + return std::nullopt; +} + +auto Context::ParseLibrarySpecifier(bool accept_default) + -> std::optional { + auto library_token = ConsumeChecked(Lex::TokenKind::Library); + auto library_subtree_start = tree().size(); + auto library_id = ParseLibraryName(accept_default); + if (!library_id) { + AddLeafNode(NodeKind::LibraryName, *position_, /*has_error=*/true); + } + AddNode(NodeKind::LibrarySpecifier, library_token, library_subtree_start, + /*has_error=*/false); + return library_id; +} + auto Context::DiagnoseExpectedDeclSemi(Lex::TokenKind expected_kind) -> void { CARBON_DIAGNOSTIC(ExpectedDeclSemi, Error, "`{0}` declarations must end with a `;`.", Lex::TokenKind); diff --git a/toolchain/parse/context.h b/toolchain/parse/context.h index 0ccf6f944bba..348265a8079f 100644 --- a/toolchain/parse/context.h +++ b/toolchain/parse/context.h @@ -302,6 +302,18 @@ class Context { auto RecoverFromDeclError(StateStackEntry state, NodeKind node_kind, bool skip_past_likely_end) -> void; + // Handles parsing of the library name. Returns the name's ID on success, + // which may be invalid for `default`. + // TODO: Add an invalid node on error, fix callers to adapt. + auto ParseLibraryName(bool accept_default) + -> std::optional; + + // Handles parsing `library `. Requires that the position is a `library` + // token. Returns the name's ID on success, which may be invalid for + // `default`. + auto ParseLibrarySpecifier(bool accept_default) + -> std::optional; + // Sets the package declaration information. Called at most once. auto set_packaging_decl(Tree::PackagingNames packaging_names, bool is_impl) -> void { diff --git a/toolchain/parse/handle_decl_scope_loop.cpp b/toolchain/parse/handle_decl_scope_loop.cpp index 45d899bfc069..95703d4ecaab 100644 --- a/toolchain/parse/handle_decl_scope_loop.cpp +++ b/toolchain/parse/handle_decl_scope_loop.cpp @@ -177,6 +177,7 @@ static auto ResolveAmbiguousTokenAsDeclaration(Context& context, case Lex::TokenKind::Alias: case Lex::TokenKind::Class: case Lex::TokenKind::Constraint: + case Lex::TokenKind::Extern: case Lex::TokenKind::Fn: case Lex::TokenKind::Import: case Lex::TokenKind::Interface: @@ -221,6 +222,23 @@ static auto TryHandleAsModifier(Context& context) -> bool { return true; #include "toolchain/parse/node_kind.def" + case Lex::TokenKind::Extern: { + auto extern_token = context.Consume(); + if (context.PositionIs(Lex::TokenKind::Library)) { + // `extern library ` syntax. + auto subtree_start = context.tree().size(); + context.ParseLibrarySpecifier(/*accept_default=*/true); + // TODO: Consider error recovery when a non-declaration token is next, + // like a typo of the library name. + context.AddNode(NodeKind::ExternModifierWithLibrary, extern_token, + subtree_start, /*has_error=*/false); + } else { + // `extern` syntax without a library. + context.AddLeafNode(NodeKind::ExternModifier, extern_token); + } + return true; + } + default: return false; } diff --git a/toolchain/parse/handle_import_and_package.cpp b/toolchain/parse/handle_import_and_package.cpp index 4ccba7b1ad7c..fdb9668ad847 100644 --- a/toolchain/parse/handle_import_and_package.cpp +++ b/toolchain/parse/handle_import_and_package.cpp @@ -34,34 +34,6 @@ static auto HasModifier(Context& context, Context::StateStackEntry state, return false; } -// Handles parsing of the library name. Returns the name's ID on success, which -// may be invalid for `default`. -static auto HandleLibraryName(Context& context, bool accept_default) - -> std::optional { - if (auto library_name_token = - context.ConsumeIf(Lex::TokenKind::StringLiteral)) { - context.AddLeafNode(NodeKind::LibraryName, *library_name_token); - return context.tokens().GetStringLiteralValue(*library_name_token); - } - - if (accept_default) { - if (auto default_token = context.ConsumeIf(Lex::TokenKind::Default)) { - context.AddLeafNode(NodeKind::DefaultLibrary, *default_token); - return StringLiteralValueId::Invalid; - } - } - - CARBON_DIAGNOSTIC( - ExpectedLibraryNameOrDefault, Error, - "Expected `default` or a string literal to specify the library name."); - CARBON_DIAGNOSTIC(ExpectedLibraryName, Error, - "Expected a string literal to specify the library name."); - context.emitter().Emit(*context.position(), accept_default - ? ExpectedLibraryNameOrDefault - : ExpectedLibraryName); - return std::nullopt; -} - // Handles everything after the declaration's introducer. static auto HandleDeclContent(Context& context, Context::StateStackEntry state, NodeKind declaration, bool is_export, @@ -102,7 +74,7 @@ static auto HandleDeclContent(Context& context, Context::StateStackEntry state, // Parse the optional library keyword. bool accept_default = !names.package_id.is_valid(); if (declaration == NodeKind::LibraryDecl) { - auto library_id = HandleLibraryName(context, accept_default); + auto library_id = context.ParseLibraryName(accept_default); if (!library_id) { on_parse_error(); return; @@ -111,17 +83,12 @@ static auto HandleDeclContent(Context& context, Context::StateStackEntry state, } else { auto next_kind = context.PositionKind(); if (next_kind == Lex::TokenKind::Library) { - auto library_token = context.ConsumeChecked(Lex::TokenKind::Library); - auto library_subtree_start = context.tree().size(); - auto library_id = HandleLibraryName(context, accept_default); - if (!library_id) { + if (auto library_id = context.ParseLibrarySpecifier(accept_default)) { + names.library_id = *library_id; + } else { on_parse_error(); return; } - names.library_id = *library_id; - context.AddNode(NodeKind::LibrarySpecifier, library_token, - library_subtree_start, - /*has_error=*/false); } else if (next_kind == Lex::TokenKind::StringLiteral || (accept_default && next_kind == Lex::TokenKind::Default)) { // If we come across a string literal and we didn't parse `library diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 097daaf68ca8..b03228f9b585 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -286,12 +286,14 @@ CARBON_PARSE_NODE_KIND(StructComma) CARBON_PARSE_NODE_KIND(StructLiteral) CARBON_PARSE_NODE_KIND(StructTypeLiteral) +CARBON_PARSE_NODE_KIND(ExternModifierWithLibrary) +CARBON_PARSE_NODE_KIND(ExternModifier) + CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Abstract) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Base) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Default) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Export) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Extend) -CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Extern) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Final) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Impl) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Private) diff --git a/toolchain/parse/testdata/function/declaration/basic.carbon b/toolchain/parse/testdata/function/declaration/basic.carbon index 38a051f9355a..42aaefbd458a 100644 --- a/toolchain/parse/testdata/function/declaration/basic.carbon +++ b/toolchain/parse/testdata/function/declaration/basic.carbon @@ -9,7 +9,6 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/function/declaration/basic.carbon fn F(); -extern fn G(); // CHECK:STDOUT: - filename: basic.carbon // CHECK:STDOUT: parse_tree: [ @@ -19,11 +18,5 @@ extern fn G(); // CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, // CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, // CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 5}, -// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'ExternModifier', text: 'extern'}, -// CHECK:STDOUT: {kind: 'IdentifierName', text: 'G'}, -// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, -// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, -// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 6}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/function/declaration/extern.carbon b/toolchain/parse/testdata/function/declaration/extern.carbon new file mode 100644 index 000000000000..4e9ebcab0966 --- /dev/null +++ b/toolchain/parse/testdata/function/declaration/extern.carbon @@ -0,0 +1,101 @@ +// 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 +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/function/declaration/extern.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/function/declaration/extern.carbon + +// --- extern.carbon + +extern fn G(); + +// --- library.carbon + +extern library "foo" fn G(); + +// --- default_library.carbon + +extern library default fn G(); + +// --- fail_name_missing.carbon + +// CHECK:STDERR: fail_name_missing.carbon:[[@LINE+4]]:16: ERROR: Expected `default` or a string literal to specify the library name. +// CHECK:STDERR: extern library fn G(); +// CHECK:STDERR: ^~ +// CHECK:STDERR: +extern library fn G(); + +// --- fail_identifier.carbon + +// CHECK:STDERR: fail_identifier.carbon:[[@LINE+7]]:16: ERROR: Expected `default` or a string literal to specify the library name. +// CHECK:STDERR: extern library foo fn G(); +// CHECK:STDERR: ^~~ +// CHECK:STDERR: +// CHECK:STDERR: fail_identifier.carbon:[[@LINE+3]]:16: ERROR: Unrecognized declaration introducer. +// CHECK:STDERR: extern library foo fn G(); +// CHECK:STDERR: ^~~ +extern library foo fn G(); + +// CHECK:STDOUT: - filename: extern.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'ExternModifier', text: 'extern'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'G'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: library.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'LibraryName', text: '"foo"'}, +// CHECK:STDOUT: {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ExternModifierWithLibrary', text: 'extern', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'G'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: default_library.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'DefaultLibrary', text: 'default'}, +// CHECK:STDOUT: {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ExternModifierWithLibrary', text: 'extern', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'G'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_name_missing.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'LibraryName', text: 'fn', has_error: yes}, +// CHECK:STDOUT: {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ExternModifierWithLibrary', text: 'extern', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'G'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_identifier.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'InvalidParseStart', text: 'foo', has_error: yes}, +// CHECK:STDOUT: {kind: 'LibraryName', text: 'foo', has_error: yes}, +// CHECK:STDOUT: {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ExternModifierWithLibrary', text: 'extern', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'InvalidParseSubtree', text: ';', has_error: yes, subtree_size: 5}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/function/definition/basic.carbon b/toolchain/parse/testdata/function/definition/basic.carbon index d4c42806db0c..135d9643022c 100644 --- a/toolchain/parse/testdata/function/definition/basic.carbon +++ b/toolchain/parse/testdata/function/definition/basic.carbon @@ -9,7 +9,6 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/function/definition/basic.carbon fn F() {} -extern fn G() {} // CHECK:STDOUT: - filename: basic.carbon // CHECK:STDOUT: parse_tree: [ @@ -20,12 +19,5 @@ extern fn G() {} // CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, // CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 6}, -// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'ExternModifier', text: 'extern'}, -// CHECK:STDOUT: {kind: 'IdentifierName', text: 'G'}, -// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, -// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, -// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 6}, -// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 7}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/function/definition/extern.carbon b/toolchain/parse/testdata/function/definition/extern.carbon new file mode 100644 index 000000000000..3e93334769fe --- /dev/null +++ b/toolchain/parse/testdata/function/definition/extern.carbon @@ -0,0 +1,44 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/function/definition/extern.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/function/definition/extern.carbon + +// --- extern.carbon + +extern fn G() {} + +// --- library.carbon + +extern library "foo" fn G() {} + +// CHECK:STDOUT: - filename: extern.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'ExternModifier', text: 'extern'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'G'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: library.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'LibraryName', text: '"foo"'}, +// CHECK:STDOUT: {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ExternModifierWithLibrary', text: 'extern', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'G'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon b/toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon index 417044a989eb..de2d019caa76 100644 --- a/toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon +++ b/toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon @@ -18,6 +18,8 @@ import Geometry library Shapes; // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'ImportIntroducer', text: 'import'}, // CHECK:STDOUT: {kind: 'PackageName', text: 'Geometry'}, -// CHECK:STDOUT: {kind: 'ImportDecl', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LibraryName', text: 'Shapes', has_error: yes}, +// CHECK:STDOUT: {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ImportDecl', text: ';', has_error: yes, subtree_size: 5}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon b/toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon index 400ddf136819..456d5c68ea73 100644 --- a/toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon +++ b/toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon @@ -18,6 +18,8 @@ package Geometry library Shapes; // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'PackageIntroducer', text: 'package'}, // CHECK:STDOUT: {kind: 'PackageName', text: 'Geometry'}, -// CHECK:STDOUT: {kind: 'PackageDecl', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LibraryName', text: 'Shapes', has_error: yes}, +// CHECK:STDOUT: {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'PackageDecl', text: ';', has_error: yes, subtree_size: 5}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index 41a342e3bc81..00d70512d629 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -924,6 +924,19 @@ struct PostfixOperator { PostfixOperator; #include "toolchain/parse/node_kind.def" +// `extern` as a standalone modifier. +using ExternModifier = LeafNode; + +// `extern library ` modifiers. +struct ExternModifierWithLibrary { + static constexpr auto Kind = NodeKind::ExternModifierWithLibrary.Define( + {.category = NodeCategory::Modifier, .child_count = 1}); + + Lex::ExternTokenIndex token; + LibrarySpecifierId library; +}; + // The first operand of a short-circuiting infix operator: `a and` or `a or`. // The complete operator expression will be an InfixOperator with this as the // `lhs`.