Просмотр исходного кода

Add framework for the extern keyword. (#3755)

This doesn't add full support. I'm separating it out to make the effects
of the modifier changes clearer for review. I'm restructuring a little
with the expectation that we'll have some more categories of modifier
keywords in the future (similar to `extern`, these may not be in a "set"
such as access), and thus easily scaling up to a few more would be
useful.
Jon Ross-Perkins 2 лет назад
Родитель
Сommit
096daecc57

+ 36 - 14
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<uint32_t>(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<int8_t>(order)];
+  }
+  auto set_modifier_node_id(ModifierOrder order, Parse::NodeId node_id)
+      -> void {
+    ordered_modifier_node_ids[static_cast<int8_t>(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<int8_t>(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;
 };
 

+ 2 - 1
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);

+ 2 - 1
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 =

+ 11 - 3
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);

+ 2 - 1
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);
   }

+ 2 - 1
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);

+ 4 - 2
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);

+ 36 - 10
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<int8_t>(order) + 1;
+         later_order <= static_cast<int8_t>(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;
 }

+ 2 - 1
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");
   }
 

+ 26 - 28
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<int8_t>(ModifierOrder::Last); ++order_index) {
+    auto order = static_cast<ModifierOrder>(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<SemIR::Inst> {
   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 {

+ 8 - 6
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.

+ 7 - 0
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> = 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:

+ 13 - 0
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:

+ 18 - 0
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: <function> = fn_decl @WrongOrder [template] {}
 // CHECK:STDOUT:   %DuplicateVirtual: <function> = fn_decl @DuplicateVirtual [template] {}
@@ -87,6 +99,7 @@ default final virtual fn ModifiersConflict2() {}
 // CHECK:STDOUT:   %ModifiersConflict: <function> = fn_decl @ModifiersConflict [template] {}
 // CHECK:STDOUT:   %InvalidModifier: <function> = fn_decl @InvalidModifier [template] {}
 // CHECK:STDOUT:   %ModifiersConflict2: <function> = fn_decl @ModifiersConflict2 [template] {}
+// CHECK:STDOUT:   %ExternOrderAndConflict: <function> = 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:

+ 40 - 30
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> = 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> = namespace [template] {}
-// CHECK:STDOUT:   %.loc36: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %.loc34: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %.loc39: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %.loc44: <namespace> = namespace [template] {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
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")

+ 1 - 0
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)

+ 11 - 0
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:   ]

+ 7 - 0
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:   ]

+ 9 - 2
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:   ]

+ 3 - 2
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'},