diff --git a/toolchain/check/decl_state.h b/toolchain/check/decl_state.h index b5cf90f64b58..9638b9d837f8 100644 --- a/toolchain/check/decl_state.h +++ b/toolchain/check/decl_state.h @@ -22,15 +22,18 @@ enum class KeywordModifierSet : uint32_t { Private = 1 << 0, Protected = 1 << 1, + // Extern is standalone. + Extern = 1 << 2, + // At most one of these declaration modifiers allowed for a given // declaration: - Abstract = 1 << 2, - Base = 1 << 3, - Default = 1 << 4, - Extend = 1 << 5, - Final = 1 << 6, - Impl = 1 << 7, - Virtual = 1 << 8, + Abstract = 1 << 3, + Base = 1 << 4, + Default = 1 << 5, + Extend = 1 << 6, + Final = 1 << 7, + Impl = 1 << 8, + Virtual = 1 << 9, // Sets of modifiers: Access = Private | Protected, @@ -38,15 +41,25 @@ enum class KeywordModifierSet : uint32_t { Method = Abstract | Impl | Virtual, ImplDecl = Extend | Final, Interface = Default | Final, + Decl = Class | Method | ImplDecl | Interface, None = 0, LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Virtual) }; -inline auto operator!(KeywordModifierSet k) -> bool { +inline constexpr auto operator!(KeywordModifierSet k) -> bool { return !static_cast(k); } +// The order of modifiers. Each of these corresponds to a group on +// KeywordModifierSet, and can be used as an array index. +enum class ModifierOrder : int8_t { Access, Extern, Decl, Last = Decl }; + +static_assert(!(KeywordModifierSet::Access & KeywordModifierSet::Extern) && + !((KeywordModifierSet::Access | KeywordModifierSet::Extern) & + KeywordModifierSet::Decl), + "Order-related sets must not overlap"); + // State stored for each declaration we are currently in: the kind of // declaration and the keyword modifiers that apply to that declaration. struct DeclState { @@ -67,15 +80,24 @@ struct DeclState { explicit DeclState(DeclKind decl_kind) : kind(decl_kind) {} + auto modifier_node_id(ModifierOrder order) -> Parse::NodeId { + return ordered_modifier_node_ids[static_cast(order)]; + } + auto set_modifier_node_id(ModifierOrder order, Parse::NodeId node_id) + -> void { + ordered_modifier_node_ids[static_cast(order)] = node_id; + } + DeclKind kind; - // Nodes of modifiers on this declaration. `Invalid` if no modifier of that - // kind is present. - Parse::NodeId saw_access_modifier = Parse::NodeId::Invalid; - Parse::NodeId saw_decl_modifier = Parse::NodeId::Invalid; + // Nodes of modifiers on this declaration, in expected order. `Invalid` if no + // modifier of that kind is present. + Parse::NodeId + ordered_modifier_node_ids[static_cast(ModifierOrder::Decl) + 1] = + {Parse::NodeId::Invalid, Parse::NodeId::Invalid, + Parse::NodeId::Invalid}; - // Invariant: contains just the modifiers represented by `saw_access_modifier` - // and `saw_other_modifier`. + // Invariant: contains just the modifiers represented by `saw_*_modifier`. KeywordModifierSet modifier_set = KeywordModifierSet::None; }; diff --git a/toolchain/check/handle_alias.cpp b/toolchain/check/handle_alias.cpp index c3700c887fab..a976da39d24b 100644 --- a/toolchain/check/handle_alias.cpp +++ b/toolchain/check/handle_alias.cpp @@ -31,7 +31,8 @@ auto HandleAlias(Context& context, Parse::AliasId /*parse_node*/) -> bool { Lex::TokenKind::Alias); auto modifiers = context.decl_state_stack().innermost().modifier_set; if (!!(modifiers & KeywordModifierSet::Access)) { - context.TODO(context.decl_state_stack().innermost().saw_access_modifier, + context.TODO(context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Access), "access modifier"); } context.decl_state_stack().Pop(DeclState::Alias); diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 64dd4c049681..b4f29d4f24d6 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -44,7 +44,8 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId parse_node) auto modifiers = context.decl_state_stack().innermost().modifier_set; if (!!(modifiers & KeywordModifierSet::Access)) { - context.TODO(context.decl_state_stack().innermost().saw_access_modifier, + context.TODO(context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Access), "access modifier"); } auto inheritance_kind = diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 788517367643..59c5270f07b8 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -101,17 +101,25 @@ static auto BuildFunctionDecl(Context& context, // Process modifiers. auto modifiers = DiagnoseModifiers(context, name_context.target_scope_id); if (!!(modifiers & KeywordModifierSet::Access)) { - context.TODO(context.decl_state_stack().innermost().saw_access_modifier, + context.TODO(context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Access), "access modifier"); } + if (!!(modifiers & KeywordModifierSet::Extern)) { + context.TODO(context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Extern), + "extern modifier"); + } if (!!(modifiers & KeywordModifierSet::Method)) { - context.TODO(context.decl_state_stack().innermost().saw_decl_modifier, + context.TODO(context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Decl), "method modifier"); } if (!!(modifiers & KeywordModifierSet::Interface)) { // TODO: Once we are saving the modifiers for a function, add check that // the function may only be defined if it is marked `default` or `final`. - context.TODO(context.decl_state_stack().innermost().saw_decl_modifier, + context.TODO(context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Decl), "interface modifier"); } context.decl_state_stack().Pop(DeclState::Fn); diff --git a/toolchain/check/handle_impl.cpp b/toolchain/check/handle_impl.cpp index c8290255f583..23a3cff45f9b 100644 --- a/toolchain/check/handle_impl.cpp +++ b/toolchain/check/handle_impl.cpp @@ -209,7 +209,8 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId parse_node) // For an `extend impl` declaration, mark the impl as extending this `impl`. if (!!(context.decl_state_stack().innermost().modifier_set & KeywordModifierSet::Extend)) { - auto extend_node = context.decl_state_stack().innermost().saw_decl_modifier; + auto extend_node = context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Decl); ExtendImpl(context, extend_node, parse_node, self_type_node, self_type_id, params_node, constraint_type_id); } diff --git a/toolchain/check/handle_interface.cpp b/toolchain/check/handle_interface.cpp index e0800552e091..856e2c516c36 100644 --- a/toolchain/check/handle_interface.cpp +++ b/toolchain/check/handle_interface.cpp @@ -45,7 +45,8 @@ static auto BuildInterfaceDecl(Context& context, auto modifiers = context.decl_state_stack().innermost().modifier_set; if (!!(modifiers & KeywordModifierSet::Access)) { - context.TODO(context.decl_state_stack().innermost().saw_access_modifier, + context.TODO(context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Access), "access modifier"); } context.decl_state_stack().Pop(DeclState::Interface); diff --git a/toolchain/check/handle_let.cpp b/toolchain/check/handle_let.cpp index 275bff1ffc75..f903d285e50f 100644 --- a/toolchain/check/handle_let.cpp +++ b/toolchain/check/handle_let.cpp @@ -87,11 +87,13 @@ auto HandleLetDecl(Context& context, Parse::LetDeclId parse_node) -> bool { auto modifiers = context.decl_state_stack().innermost().modifier_set; if (!!(modifiers & KeywordModifierSet::Access)) { - context.TODO(context.decl_state_stack().innermost().saw_access_modifier, + context.TODO(context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Access), "access modifier"); } if (!!(modifiers & KeywordModifierSet::Interface)) { - context.TODO(context.decl_state_stack().innermost().saw_decl_modifier, + context.TODO(context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Decl), "interface modifier"); } context.decl_state_stack().Pop(DeclState::Let); diff --git a/toolchain/check/handle_modifier.cpp b/toolchain/check/handle_modifier.cpp index ecc9668da0d3..5255d1a89957 100644 --- a/toolchain/check/handle_modifier.cpp +++ b/toolchain/check/handle_modifier.cpp @@ -37,26 +37,52 @@ static auto EmitNotAllowedWithDiagnostic(Context& context, static auto HandleModifier(Context& context, Parse::NodeId parse_node, KeywordModifierSet keyword) -> bool { auto& s = context.decl_state_stack().innermost(); - bool is_access = !!(keyword & KeywordModifierSet::Access); - auto& saw_modifier = is_access ? s.saw_access_modifier : s.saw_decl_modifier; + + ModifierOrder order; + KeywordModifierSet later_modifiers; + if (!!(keyword & KeywordModifierSet::Access)) { + order = ModifierOrder::Access; + later_modifiers = KeywordModifierSet::Extern | KeywordModifierSet::Decl; + } else if (keyword == KeywordModifierSet::Extern) { + order = ModifierOrder::Extern; + later_modifiers = KeywordModifierSet::Decl; + } else { + order = ModifierOrder::Decl; + later_modifiers = KeywordModifierSet::None; + } + + auto current_modifier_node_id = s.modifier_node_id(order); if (!!(s.modifier_set & keyword)) { - EmitRepeatedDiagnostic(context, saw_modifier, parse_node); - } else if (saw_modifier.is_valid()) { - EmitNotAllowedWithDiagnostic(context, saw_modifier, parse_node); - } else if (is_access && s.saw_decl_modifier.is_valid()) { + EmitRepeatedDiagnostic(context, current_modifier_node_id, parse_node); + } else if (current_modifier_node_id.is_valid()) { + EmitNotAllowedWithDiagnostic(context, current_modifier_node_id, parse_node); + } else if (auto later_modifier_set = s.modifier_set & later_modifiers; + !!later_modifier_set) { + // At least one later modifier is present. Diagnose using the closest. + Parse::NodeId closest_later_modifier = Parse::NodeId::Invalid; + for (auto later_order = static_cast(order) + 1; + later_order <= static_cast(ModifierOrder::Last); + ++later_order) { + if (s.ordered_modifier_node_ids[later_order] != Parse::NodeId::Invalid) { + closest_later_modifier = s.ordered_modifier_node_ids[later_order]; + break; + } + } + CARBON_CHECK(closest_later_modifier.is_valid()); + CARBON_DIAGNOSTIC(ModifierMustAppearBefore, Error, "`{0}` must appear before `{1}`.", Lex::TokenKind, Lex::TokenKind); context.emitter() .Build(parse_node, ModifierMustAppearBefore, context.token_kind(parse_node), - context.token_kind(s.saw_decl_modifier)) - .Note(s.saw_decl_modifier, ModifierPrevious, - context.token_kind(s.saw_decl_modifier)) + context.token_kind(closest_later_modifier)) + .Note(closest_later_modifier, ModifierPrevious, + context.token_kind(closest_later_modifier)) .Emit(); } else { s.modifier_set |= keyword; - saw_modifier = parse_node; + s.set_modifier_node_id(order, parse_node); } return true; } diff --git a/toolchain/check/handle_variable.cpp b/toolchain/check/handle_variable.cpp index b0b864642cf6..276d9dc0a009 100644 --- a/toolchain/check/handle_variable.cpp +++ b/toolchain/check/handle_variable.cpp @@ -102,7 +102,8 @@ auto HandleVariableDecl(Context& context, Parse::VariableDeclId parse_node) Lex::TokenKind::Var); auto modifiers = context.decl_state_stack().innermost().modifier_set; if (!!(modifiers & KeywordModifierSet::Access)) { - context.TODO(context.decl_state_stack().innermost().saw_access_modifier, + context.TODO(context.decl_state_stack().innermost().modifier_node_id( + ModifierOrder::Access), "access modifier"); } diff --git a/toolchain/check/modifiers.cpp b/toolchain/check/modifiers.cpp index 3d13b3be98ce..46476d285020 100644 --- a/toolchain/check/modifiers.cpp +++ b/toolchain/check/modifiers.cpp @@ -4,6 +4,8 @@ #include "toolchain/check/modifiers.h" +#include "toolchain/check/decl_state.h" + namespace Carbon::Check { static auto ReportNotAllowed(Context& context, Parse::NodeId modifier_node, @@ -24,22 +26,16 @@ static auto ReportNotAllowed(Context& context, Parse::NodeId modifier_node, diag.Emit(); } -auto LimitModifiersOnDecl(Context& context, KeywordModifierSet allowed, - Lex::TokenKind decl_kind) -> void { - auto& s = context.decl_state_stack().innermost(); - auto not_allowed = s.modifier_set & ~allowed; - if (!!(not_allowed & KeywordModifierSet::Access)) { - ReportNotAllowed(context, s.saw_access_modifier, decl_kind, "", - Parse::NodeId::Invalid); - not_allowed = not_allowed & ~KeywordModifierSet::Access; - s.saw_access_modifier = Parse::NodeId::Invalid; +// Returns the KeywordModifierSet corresponding to the ModifierOrder entry. +static auto ModifierOrderAsSet(ModifierOrder order) -> KeywordModifierSet { + switch (order) { + case ModifierOrder::Access: + return KeywordModifierSet::Access; + case ModifierOrder::Extern: + return KeywordModifierSet::Extern; + case ModifierOrder::Decl: + return KeywordModifierSet::Decl; } - if (!!not_allowed) { - ReportNotAllowed(context, s.saw_decl_modifier, decl_kind, "", - Parse::NodeId::Invalid); - s.saw_decl_modifier = Parse::NodeId::Invalid; - } - s.modifier_set &= allowed; } auto ForbidModifiersOnDecl(Context& context, KeywordModifierSet forbidden, @@ -48,23 +44,26 @@ auto ForbidModifiersOnDecl(Context& context, KeywordModifierSet forbidden, Parse::NodeId context_node) -> void { auto& s = context.decl_state_stack().innermost(); auto not_allowed = s.modifier_set & forbidden; - if (!!(not_allowed & KeywordModifierSet::Access)) { - ReportNotAllowed(context, s.saw_access_modifier, decl_kind, context_string, - context_node); - not_allowed = not_allowed & ~KeywordModifierSet::Access; - s.saw_access_modifier = Parse::NodeId::Invalid; + if (!not_allowed) { + return; } - if (!!not_allowed) { - ReportNotAllowed(context, s.saw_decl_modifier, decl_kind, context_string, - context_node); - s.saw_decl_modifier = Parse::NodeId::Invalid; + + for (auto order_index = 0; + order_index <= static_cast(ModifierOrder::Last); ++order_index) { + auto order = static_cast(order_index); + if (!!(not_allowed & ModifierOrderAsSet(order))) { + ReportNotAllowed(context, s.modifier_node_id(order), decl_kind, + context_string, context_node); + s.set_modifier_node_id(order, Parse::NodeId::Invalid); + } } - s.modifier_set = s.modifier_set & ~forbidden; + + s.modifier_set &= ~forbidden; } // Returns the instruction that owns the given scope, or Invalid if the scope is // not associated with an instruction. -auto GetScopeInstId(Context& context, SemIR::NameScopeId scope_id) +static auto GetScopeInstId(Context& context, SemIR::NameScopeId scope_id) -> SemIR::InstId { if (!scope_id.is_valid()) { return SemIR::InstId::Invalid; @@ -74,7 +73,7 @@ auto GetScopeInstId(Context& context, SemIR::NameScopeId scope_id) // Returns the instruction that owns the given scope, or Invalid if the scope is // not associated with an instruction. -auto GetScopeInst(Context& context, SemIR::NameScopeId scope_id) +static auto GetScopeInst(Context& context, SemIR::NameScopeId scope_id) -> std::optional { auto inst_id = GetScopeInstId(context, scope_id); if (!inst_id.is_valid()) { @@ -109,7 +108,6 @@ auto CheckAccessModifiersOnDecl(Context& context, Lex::TokenKind decl_kind, ", `private` is only allowed on class members and at file scope"); } -// Rules for abstract, virtual, and impl, which are only allowed in classes. auto CheckMethodModifiersOnFunction(Context& context, SemIR::NameScopeId target_scope_id) -> void { diff --git a/toolchain/check/modifiers.h b/toolchain/check/modifiers.h index b788e5d396fc..c6dae610f8a7 100644 --- a/toolchain/check/modifiers.h +++ b/toolchain/check/modifiers.h @@ -25,12 +25,6 @@ auto CheckAccessModifiersOnDecl(Context& context, Lex::TokenKind decl_kind, auto CheckMethodModifiersOnFunction(Context& context, SemIR::NameScopeId target_scope_id) -> void; -// Reports a diagnostic (using `decl_kind`) if modifiers on this declaration are -// not in `allowed`. Updates the declaration state in -// `context.decl_state_stack()`. -auto LimitModifiersOnDecl(Context& context, KeywordModifierSet allowed, - Lex::TokenKind decl_kind) -> void; - // Like `LimitModifiersOnDecl`, except says which modifiers are forbidden, and a // `context_string` (and optional `context_node`) specifying the context in // which those modifiers are forbidden. @@ -40,6 +34,14 @@ auto ForbidModifiersOnDecl(Context& context, KeywordModifierSet forbidden, Parse::NodeId context_node = Parse::NodeId::Invalid) -> void; +// Reports a diagnostic (using `decl_kind`) if modifiers on this declaration are +// not in `allowed`. Updates the declaration state in +// `context.decl_state_stack()`. +inline auto LimitModifiersOnDecl(Context& context, KeywordModifierSet allowed, + Lex::TokenKind decl_kind) -> void { + ForbidModifiersOnDecl(context, ~allowed, decl_kind, ""); +} + // Report a diagonostic if `default` and `final` modifiers are used on // declarations where they are not allowed. Right now they are only allowed // inside interfaces. diff --git a/toolchain/check/testdata/alias/fail_modifiers.carbon b/toolchain/check/testdata/alias/fail_modifiers.carbon index 6fd0cb10c58d..cfc3ec15f180 100644 --- a/toolchain/check/testdata/alias/fail_modifiers.carbon +++ b/toolchain/check/testdata/alias/fail_modifiers.carbon @@ -32,14 +32,21 @@ abstract base default final alias A = i32; // CHECK:STDERR: ^~~~ impl alias B = i32; +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+3]]:1: ERROR: `extern` not allowed on `alias` declaration. +// CHECK:STDERR: extern alias C = i32; +// CHECK:STDERR: ^~~~~~ +extern alias C = i32; + // CHECK:STDOUT: --- fail_modifiers.carbon // CHECK:STDOUT: // CHECK:STDOUT: file { // CHECK:STDOUT: package: = namespace [template] { // CHECK:STDOUT: .A = %A // CHECK:STDOUT: .B = %B +// CHECK:STDOUT: .C = %C // CHECK:STDOUT: } // CHECK:STDOUT: %A: type = bind_alias A, i32 [template = i32] // CHECK:STDOUT: %B: type = bind_alias B, i32 [template = i32] +// CHECK:STDOUT: %C: type = bind_alias C, i32 [template = i32] // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_modifiers.carbon b/toolchain/check/testdata/class/fail_modifiers.carbon index f065a4ac677d..635430aa2469 100644 --- a/toolchain/check/testdata/class/fail_modifiers.carbon +++ b/toolchain/check/testdata/class/fail_modifiers.carbon @@ -64,6 +64,11 @@ abstract protected class WrongOrder; // CHECK:STDERR: ^~~~~~~~ abstract base class AbstractAndBase {} +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+3]]:1: ERROR: `extern` not allowed on `class` declaration. +// CHECK:STDERR: extern class ExternDefined {} +// CHECK:STDERR: ^~~~~~ +extern class ExternDefined {} + // CHECK:STDOUT: --- fail_modifiers.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { @@ -74,6 +79,7 @@ abstract base class AbstractAndBase {} // CHECK:STDOUT: %Virtual: type = class_type @Virtual [template] // CHECK:STDOUT: %WrongOrder: type = class_type @WrongOrder [template] // CHECK:STDOUT: %AbstractAndBase: type = class_type @AbstractAndBase [template] +// CHECK:STDOUT: %ExternDefined: type = class_type @ExternDefined [template] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -84,6 +90,7 @@ abstract base class AbstractAndBase {} // CHECK:STDOUT: .Virtual = %Virtual.decl // CHECK:STDOUT: .WrongOrder = %WrongOrder.decl // CHECK:STDOUT: .AbstractAndBase = %AbstractAndBase.decl +// CHECK:STDOUT: .ExternDefined = %ExternDefined.decl // CHECK:STDOUT: } // CHECK:STDOUT: %DuplicatePrivate.decl: type = class_decl @DuplicatePrivate [template = constants.%DuplicatePrivate] {} // CHECK:STDOUT: %TwoAccess.decl: type = class_decl @TwoAccess [template = constants.%TwoAccess] {} @@ -91,6 +98,7 @@ abstract base class AbstractAndBase {} // CHECK:STDOUT: %Virtual.decl: type = class_decl @Virtual [template = constants.%Virtual] {} // CHECK:STDOUT: %WrongOrder.decl: type = class_decl @WrongOrder [template = constants.%WrongOrder] {} // CHECK:STDOUT: %AbstractAndBase.decl: type = class_decl @AbstractAndBase [template = constants.%AbstractAndBase] {} +// CHECK:STDOUT: %ExternDefined.decl: type = class_decl @ExternDefined [template = constants.%ExternDefined] {} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @DuplicatePrivate; @@ -114,3 +122,8 @@ abstract base class AbstractAndBase {} // CHECK:STDOUT: .Self = constants.%AbstractAndBase // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: class @ExternDefined { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%ExternDefined +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/function/declaration/fail_modifiers.carbon b/toolchain/check/testdata/function/declaration/fail_modifiers.carbon index 67585653db9c..dbbca1786f2f 100644 --- a/toolchain/check/testdata/function/declaration/fail_modifiers.carbon +++ b/toolchain/check/testdata/function/declaration/fail_modifiers.carbon @@ -70,6 +70,17 @@ base fn InvalidModifier(); // CHECK:STDERR: ^~~~~~~ default final virtual fn ModifiersConflict2() {} +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+9]]:1: ERROR: `extern` not allowed on `fn` declaration. +// CHECK:STDERR: extern private fn ExternOrderAndConflict() {} +// CHECK:STDERR: ^~~~~~ +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+6]]:8: ERROR: `private` must appear before `extern`. +// CHECK:STDERR: extern private fn ExternOrderAndConflict() {} +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+3]]:1: `extern` previously appeared here. +// CHECK:STDERR: extern private fn ExternOrderAndConflict() {} +// CHECK:STDERR: ^~~~~~ +extern private fn ExternOrderAndConflict() {} + // CHECK:STDOUT: --- fail_modifiers.carbon // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -80,6 +91,7 @@ default final virtual fn ModifiersConflict2() {} // CHECK:STDOUT: .ModifiersConflict = %ModifiersConflict // CHECK:STDOUT: .InvalidModifier = %InvalidModifier // CHECK:STDOUT: .ModifiersConflict2 = %ModifiersConflict2 +// CHECK:STDOUT: .ExternOrderAndConflict = %ExternOrderAndConflict // CHECK:STDOUT: } // CHECK:STDOUT: %WrongOrder: = fn_decl @WrongOrder [template] {} // CHECK:STDOUT: %DuplicateVirtual: = fn_decl @DuplicateVirtual [template] {} @@ -87,6 +99,7 @@ default final virtual fn ModifiersConflict2() {} // CHECK:STDOUT: %ModifiersConflict: = fn_decl @ModifiersConflict [template] {} // CHECK:STDOUT: %InvalidModifier: = fn_decl @InvalidModifier [template] {} // CHECK:STDOUT: %ModifiersConflict2: = fn_decl @ModifiersConflict2 [template] {} +// CHECK:STDOUT: %ExternOrderAndConflict: = fn_decl @ExternOrderAndConflict [template] {} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @WrongOrder(); @@ -110,3 +123,8 @@ default final virtual fn ModifiersConflict2() {} // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: fn @ExternOrderAndConflict() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/namespace/fail_modifiers.carbon b/toolchain/check/testdata/namespace/fail_modifiers.carbon index e01d9969ad49..a02740072118 100644 --- a/toolchain/check/testdata/namespace/fail_modifiers.carbon +++ b/toolchain/check/testdata/namespace/fail_modifiers.carbon @@ -4,45 +4,55 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+24]]:1: ERROR: `private` not allowed on `namespace` declaration. -// CHECK:STDERR: private abstract base default final namespace Foo; +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+27]]:1: ERROR: `private` not allowed on `namespace` declaration. +// CHECK:STDERR: private extern abstract base default final namespace A; // CHECK:STDERR: ^~~~~~~ -// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+21]]:9: ERROR: `abstract` not allowed on `namespace` declaration. -// CHECK:STDERR: private abstract base default final namespace Foo; -// CHECK:STDERR: ^~~~~~~~ -// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+18]]:18: ERROR: `base` not allowed on declaration with `abstract`. -// CHECK:STDERR: private abstract base default final namespace Foo; -// CHECK:STDERR: ^~~~ -// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+15]]:9: `abstract` previously appeared here. -// CHECK:STDERR: private abstract base default final namespace Foo; -// CHECK:STDERR: ^~~~~~~~ -// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+12]]:23: ERROR: `default` not allowed on declaration with `abstract`. -// CHECK:STDERR: private abstract base default final namespace Foo; -// CHECK:STDERR: ^~~~~~~ -// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+9]]:9: `abstract` previously appeared here. -// CHECK:STDERR: private abstract base default final namespace Foo; -// CHECK:STDERR: ^~~~~~~~ -// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+6]]:31: ERROR: `final` not allowed on declaration with `abstract`. -// CHECK:STDERR: private abstract base default final namespace Foo; -// CHECK:STDERR: ^~~~~ -// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+3]]:9: `abstract` previously appeared here. -// CHECK:STDERR: private abstract base default final namespace Foo; -// CHECK:STDERR: ^~~~~~~~ -private abstract base default final namespace Foo; +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+24]]:9: ERROR: `extern` not allowed on `namespace` declaration. +// CHECK:STDERR: private extern abstract base default final namespace A; +// CHECK:STDERR: ^~~~~~ +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+21]]:16: ERROR: `abstract` not allowed on `namespace` declaration. +// CHECK:STDERR: private extern abstract base default final namespace A; +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+18]]:25: ERROR: `base` not allowed on declaration with `abstract`. +// CHECK:STDERR: private extern abstract base default final namespace A; +// CHECK:STDERR: ^~~~ +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+15]]:16: `abstract` previously appeared here. +// CHECK:STDERR: private extern abstract base default final namespace A; +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+12]]:30: ERROR: `default` not allowed on declaration with `abstract`. +// CHECK:STDERR: private extern abstract base default final namespace A; +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+9]]:16: `abstract` previously appeared here. +// CHECK:STDERR: private extern abstract base default final namespace A; +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+6]]:38: ERROR: `final` not allowed on declaration with `abstract`. +// CHECK:STDERR: private extern abstract base default final namespace A; +// CHECK:STDERR: ^~~~~ +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+3]]:16: `abstract` previously appeared here. +// CHECK:STDERR: private extern abstract base default final namespace A; +// CHECK:STDERR: ^~~~~~~~ +private extern abstract base default final namespace A; // CHECK:STDERR: fail_modifiers.carbon:[[@LINE+3]]:1: ERROR: `impl` not allowed on `namespace` declaration. -// CHECK:STDERR: impl namespace Bar; +// CHECK:STDERR: impl namespace B; // CHECK:STDERR: ^~~~ -impl namespace Bar; +impl namespace B; + +// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+3]]:1: ERROR: `extern` not allowed on `namespace` declaration. +// CHECK:STDERR: extern namespace C; +// CHECK:STDERR: ^~~~~~ +extern namespace C; // CHECK:STDOUT: --- fail_modifiers.carbon // CHECK:STDOUT: // CHECK:STDOUT: file { // CHECK:STDOUT: package: = namespace [template] { -// CHECK:STDOUT: .Foo = %.loc31 -// CHECK:STDOUT: .Bar = %.loc36 +// CHECK:STDOUT: .A = %.loc34 +// CHECK:STDOUT: .B = %.loc39 +// CHECK:STDOUT: .C = %.loc44 // CHECK:STDOUT: } -// CHECK:STDOUT: %.loc31: = namespace [template] {} -// CHECK:STDOUT: %.loc36: = namespace [template] {} +// CHECK:STDOUT: %.loc34: = namespace [template] {} +// CHECK:STDOUT: %.loc39: = namespace [template] {} +// CHECK:STDOUT: %.loc44: = namespace [template] {} // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/lex/token_kind.def b/toolchain/lex/token_kind.def index ac5f7fef1297..502836fe5e5a 100644 --- a/toolchain/lex/token_kind.def +++ b/toolchain/lex/token_kind.def @@ -160,6 +160,7 @@ CARBON_KEYWORD_TOKEN(Default, "default") CARBON_KEYWORD_TOKEN(Destructor, "destructor") CARBON_KEYWORD_TOKEN(Else, "else") CARBON_KEYWORD_TOKEN(Extend, "extend") +CARBON_KEYWORD_TOKEN(Extern, "extern") CARBON_KEYWORD_TOKEN(False, "false") CARBON_KEYWORD_TOKEN(Final, "final") CARBON_KEYWORD_TOKEN(Fn, "fn") diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index f4b2dd283b35..85c5c7213409 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -621,6 +621,7 @@ 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(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/class/introducer.carbon b/toolchain/parse/testdata/class/introducer.carbon index 890dddce641c..bff4b0367edb 100644 --- a/toolchain/parse/testdata/class/introducer.carbon +++ b/toolchain/parse/testdata/class/introducer.carbon @@ -7,10 +7,12 @@ class A; base class B; abstract class C; +extern class D; class A {} base class B {} abstract class C {} +extern class D {} // CHECK:STDOUT: - filename: introducer.carbon // CHECK:STDOUT: parse_tree: [ @@ -26,6 +28,10 @@ abstract class C {} // CHECK:STDOUT: {kind: 'AbstractModifier', text: 'abstract'}, // CHECK:STDOUT: {kind: 'IdentifierName', text: 'C'}, // CHECK:STDOUT: {kind: 'ClassDecl', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'ClassIntroducer', text: 'class'}, +// CHECK:STDOUT: {kind: 'ExternModifier', text: 'extern'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'D'}, +// CHECK:STDOUT: {kind: 'ClassDecl', text: ';', subtree_size: 4}, // CHECK:STDOUT: {kind: 'ClassIntroducer', text: 'class'}, // CHECK:STDOUT: {kind: 'IdentifierName', text: 'A'}, // CHECK:STDOUT: {kind: 'ClassDefinitionStart', text: '{', subtree_size: 3}, @@ -40,5 +46,10 @@ abstract class C {} // CHECK:STDOUT: {kind: 'IdentifierName', text: 'C'}, // CHECK:STDOUT: {kind: 'ClassDefinitionStart', text: '{', subtree_size: 4}, // CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ClassIntroducer', text: 'class'}, +// CHECK:STDOUT: {kind: 'ExternModifier', text: 'extern'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'D'}, +// CHECK:STDOUT: {kind: 'ClassDefinitionStart', text: '{', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 5}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/function/declaration/basic.carbon b/toolchain/parse/testdata/function/declaration/basic.carbon index 9198442b49c8..2c7cb9a43281 100644 --- a/toolchain/parse/testdata/function/declaration/basic.carbon +++ b/toolchain/parse/testdata/function/declaration/basic.carbon @@ -5,6 +5,7 @@ // AUTOUPDATE fn F(); +extern fn G(); // CHECK:STDOUT: - filename: basic.carbon // CHECK:STDOUT: parse_tree: [ @@ -14,5 +15,11 @@ fn F(); // 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/definition/basic.carbon b/toolchain/parse/testdata/function/definition/basic.carbon index 93f2cec22e85..e5c3b1c1e956 100644 --- a/toolchain/parse/testdata/function/definition/basic.carbon +++ b/toolchain/parse/testdata/function/definition/basic.carbon @@ -4,8 +4,8 @@ // // AUTOUPDATE -fn F() { -} +fn F() {} +extern fn G() {} // CHECK:STDOUT: - filename: basic.carbon // CHECK:STDOUT: parse_tree: [ @@ -16,5 +16,12 @@ fn F() { // 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/namespace/modifiers.carbon b/toolchain/parse/testdata/namespace/modifiers.carbon index de6e5aa5c4c7..659b3c39ded2 100644 --- a/toolchain/parse/testdata/namespace/modifiers.carbon +++ b/toolchain/parse/testdata/namespace/modifiers.carbon @@ -4,7 +4,7 @@ // // AUTOUPDATE -private abstract base default final namespace Foo; +private extern abstract base default final namespace Foo; impl namespace Foo; @@ -13,12 +13,13 @@ impl namespace Foo; // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, // CHECK:STDOUT: {kind: 'PrivateModifier', text: 'private'}, +// CHECK:STDOUT: {kind: 'ExternModifier', text: 'extern'}, // CHECK:STDOUT: {kind: 'AbstractModifier', text: 'abstract'}, // CHECK:STDOUT: {kind: 'BaseModifier', text: 'base'}, // CHECK:STDOUT: {kind: 'DefaultModifier', text: 'default'}, // CHECK:STDOUT: {kind: 'FinalModifier', text: 'final'}, // CHECK:STDOUT: {kind: 'IdentifierName', text: 'Foo'}, -// CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 9}, // CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, // CHECK:STDOUT: {kind: 'ImplModifier', text: 'impl'}, // CHECK:STDOUT: {kind: 'IdentifierName', text: 'Foo'},