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

Allow `extend final impl as` for impl declarations (#5345)

In a class, an `impl as` can now be both `final` and `extend` instead of
only one or the other.

In https://github.com/carbon-language/carbon-lang/issues/5319 we decided
this is already allowed by the design but was an oversight in the
implementation.
Dana Jansens 1 год назад
Родитель
Сommit
94dca7967b

+ 2 - 1
toolchain/check/decl_introducer_state.h

@@ -30,7 +30,8 @@ struct DeclIntroducerState {
   // modifier of that kind is present.
   Parse::NodeId
       ordered_modifier_node_ids[static_cast<int8_t>(ModifierOrder::Decl) + 1] =
-          {Parse::NodeId::None, Parse::NodeId::None, Parse::NodeId::None};
+          {Parse::NodeId::None, Parse::NodeId::None, Parse::NodeId::None,
+           Parse::NodeId::None};
 
   // Invariant: contains just the modifiers represented by `saw_*_modifier`.
   KeywordModifierSet modifier_set = KeywordModifierSet();

+ 1 - 1
toolchain/check/handle_impl.cpp

@@ -488,7 +488,7 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
   // For an `extend impl` declaration, mark the impl as extending this `impl`.
   if (self_type_id != SemIR::ErrorInst::TypeId &&
       introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
-    auto extend_node = introducer.modifier_node_id(ModifierOrder::Decl);
+    auto extend_node = introducer.modifier_node_id(ModifierOrder::Extend);
     if (impl_info.generic_id.has_value()) {
       constraint_type_inst_id = AddTypeInst<SemIR::SpecificConstant>(
           context, context.insts().GetLocId(constraint_type_inst_id),

+ 3 - 0
toolchain/check/handle_modifier.cpp

@@ -50,6 +50,9 @@ static auto HandleModifier(Context& context, Parse::NodeId node_id,
   } else if (keyword.HasAnyOf(KeywordModifierSet::Extern)) {
     order = ModifierOrder::Extern;
     later_modifiers = KeywordModifierSet::Decl;
+  } else if (keyword.HasAnyOf(KeywordModifierSet::Extend)) {
+    order = ModifierOrder::Extend;
+    later_modifiers = KeywordModifierSet::Decl;
   } else {
     order = ModifierOrder::Decl;
     later_modifiers = KeywordModifierSet::None;

+ 12 - 9
toolchain/check/keyword_modifier_set.h

@@ -16,7 +16,7 @@ LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
 
 // 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 };
+enum class ModifierOrder : int8_t { Access, Extern, Extend, Decl, Last = Decl };
 
 // Represents a set of keyword modifiers, using a separate bit per modifier.
 class KeywordModifierSet {
@@ -35,13 +35,15 @@ class KeywordModifierSet {
     // Extern is standalone.
     Extern = 1 << 2,
 
+    // Extend can be combined with Final, but no others in the group below.
+    Extend = 1 << 3,
+
     // At most one of these declaration modifiers allowed for a given
     // declaration:
-    Abstract = 1 << 3,
-    Base = 1 << 4,
-    Default = 1 << 5,
-    Export = 1 << 6,
-    Extend = 1 << 7,
+    Abstract = 1 << 4,
+    Base = 1 << 5,
+    Default = 1 << 6,
+    Export = 1 << 7,
     Final = 1 << 8,
     Impl = 1 << 9,
     Virtual = 1 << 10,
@@ -53,7 +55,7 @@ class KeywordModifierSet {
     Method = Abstract | Impl | Virtual,
     ImplDecl = Extend | Final,
     Interface = Default | Final,
-    Decl = Class | Method | ImplDecl | Interface | Export | Returned,
+    Decl = Class | Method | Impl | Interface | Export | Returned,
     None = 0,
 
     LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Returned)
@@ -142,12 +144,13 @@ class KeywordModifierSet {
 static_assert(!KeywordModifierSet(KeywordModifierSet::Access)
                       .HasAnyOf(KeywordModifierSet::Extern) &&
                   !KeywordModifierSet(KeywordModifierSet::Access |
-                                      KeywordModifierSet::Extern)
+                                      KeywordModifierSet::Extern |
+                                      KeywordModifierSet::Extend)
                        .HasAnyOf(KeywordModifierSet::Decl),
               "Order-related sets must not overlap");
 static_assert(~KeywordModifierSet::None ==
                   (KeywordModifierSet::Access | KeywordModifierSet::Extern |
-                   KeywordModifierSet::Decl),
+                   KeywordModifierSet::Extend | KeywordModifierSet::Decl),
               "Modifier missing from all modifier sets");
 
 }  // namespace Carbon::Check

+ 2 - 0
toolchain/check/modifiers.cpp

@@ -57,6 +57,8 @@ static auto ModifierOrderAsSet(ModifierOrder order) -> KeywordModifierSet {
       return KeywordModifierSet::Access;
     case ModifierOrder::Extern:
       return KeywordModifierSet::Extern;
+    case ModifierOrder::Extend:
+      return KeywordModifierSet::Extend;
     case ModifierOrder::Decl:
       return KeywordModifierSet::Decl;
   }

+ 11 - 110
toolchain/check/testdata/class/adapter/fail_adapt_modifiers.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
+// EXTRA-ARGS: --no-dump-sem-ir
+//
 // AUTOUPDATE
 // TIP: To test this file alone, run:
 // TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/adapter/fail_adapt_modifiers.carbon
@@ -46,118 +48,17 @@ class C4 {
 }
 
 class C5 {
-  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+7]]:10: error: `base` not allowed on declaration with `extend` [ModifierNotAllowedWith]
+  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+4]]:3: error: `base` not allowed on `adapt` declaration [ModifierNotAllowedOnDeclaration]
+  // CHECK:STDERR:   base adapt B;
+  // CHECK:STDERR:   ^~~~
+  // CHECK:STDERR:
+  base adapt B;
+}
+
+class C6 {
+  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+4]]:10: error: `base` not allowed on `adapt` declaration [ModifierNotAllowedOnDeclaration]
   // CHECK:STDERR:   extend base adapt B;
   // CHECK:STDERR:          ^~~~
-  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+4]]:3: note: `extend` previously appeared here [ModifierPrevious]
-  // CHECK:STDERR:   extend base adapt B;
-  // CHECK:STDERR:   ^~~~~~
   // CHECK:STDERR:
   extend base adapt B;
 }
-
-// CHECK:STDOUT: --- fail_adapt_modifiers.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %B: type = class_type @B [concrete]
-// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
-// CHECK:STDOUT:   %C1: type = class_type @C1 [concrete]
-// CHECK:STDOUT:   %C2: type = class_type @C2 [concrete]
-// CHECK:STDOUT:   %C3: type = class_type @C3 [concrete]
-// CHECK:STDOUT:   %C4: type = class_type @C4 [concrete]
-// CHECK:STDOUT:   %C5: type = class_type @C5 [concrete]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
-// CHECK:STDOUT:     import Core//prelude
-// CHECK:STDOUT:     import Core//prelude/...
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .B = %B.decl
-// CHECK:STDOUT:     .C1 = %C1.decl
-// CHECK:STDOUT:     .C2 = %C2.decl
-// CHECK:STDOUT:     .C3 = %C3.decl
-// CHECK:STDOUT:     .C4 = %C4.decl
-// CHECK:STDOUT:     .C5 = %C5.decl
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Core.import = import Core
-// CHECK:STDOUT:   %B.decl: type = class_decl @B [concrete = constants.%B] {} {}
-// CHECK:STDOUT:   %C1.decl: type = class_decl @C1 [concrete = constants.%C1] {} {}
-// CHECK:STDOUT:   %C2.decl: type = class_decl @C2 [concrete = constants.%C2] {} {}
-// CHECK:STDOUT:   %C3.decl: type = class_decl @C3 [concrete = constants.%C3] {} {}
-// CHECK:STDOUT:   %C4.decl: type = class_decl @C4 [concrete = constants.%C4] {} {}
-// CHECK:STDOUT:   %C5.decl: type = class_decl @C5 [concrete = constants.%C5] {} {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @B {
-// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%B
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C1 {
-// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [concrete = constants.%B]
-// CHECK:STDOUT:   adapt_decl %B.ref [concrete]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness constants.%empty_struct_type [concrete = constants.%complete_type]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C1
-// CHECK:STDOUT:   .B = <poisoned>
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C2 {
-// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [concrete = constants.%B]
-// CHECK:STDOUT:   adapt_decl %B.ref [concrete]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness constants.%empty_struct_type [concrete = constants.%complete_type]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C2
-// CHECK:STDOUT:   .B = <poisoned>
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C3 {
-// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [concrete = constants.%B]
-// CHECK:STDOUT:   adapt_decl %B.ref [concrete]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness constants.%empty_struct_type [concrete = constants.%complete_type]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C3
-// CHECK:STDOUT:   .B = <poisoned>
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C4 {
-// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [concrete = constants.%B]
-// CHECK:STDOUT:   adapt_decl %B.ref [concrete]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness constants.%empty_struct_type [concrete = constants.%complete_type]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C4
-// CHECK:STDOUT:   .B = <poisoned>
-// CHECK:STDOUT:   extend %B.ref
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C5 {
-// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [concrete = constants.%B]
-// CHECK:STDOUT:   adapt_decl %B.ref [concrete]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness constants.%empty_struct_type [concrete = constants.%complete_type]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C5
-// CHECK:STDOUT:   .B = <poisoned>
-// CHECK:STDOUT:   extend %B.ref
-// CHECK:STDOUT: }
-// CHECK:STDOUT:

+ 3 - 111
toolchain/check/testdata/class/fail_base_modifiers.carbon

@@ -2,6 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
+// EXTRA-ARGS: --no-dump-sem-ir
+//
 // AUTOUPDATE
 // TIP: To test this file alone, run:
 // TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/fail_base_modifiers.carbon
@@ -31,12 +33,9 @@ class C2 {
 }
 
 class C3 {
-  // CHECK:STDERR: fail_base_modifiers.carbon:[[@LINE+7]]:10: error: `default` not allowed on declaration with `extend` [ModifierNotAllowedWith]
+  // CHECK:STDERR: fail_base_modifiers.carbon:[[@LINE+4]]:10: error: `default` not allowed on `base` declaration [ModifierNotAllowedOnDeclaration]
   // CHECK:STDERR:   extend default base: B;
   // CHECK:STDERR:          ^~~~~~~
-  // CHECK:STDERR: fail_base_modifiers.carbon:[[@LINE+4]]:3: note: `extend` previously appeared here [ModifierPrevious]
-  // CHECK:STDERR:   extend default base: B;
-  // CHECK:STDERR:   ^~~~~~
   // CHECK:STDERR:
   extend default base: B;
 }
@@ -51,110 +50,3 @@ class C4 {
   // CHECK:STDERR:
   extend extend base: B;
 }
-
-// CHECK:STDOUT: --- fail_base_modifiers.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %B: type = class_type @B [concrete]
-// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
-// CHECK:STDOUT:   %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
-// CHECK:STDOUT:   %C1: type = class_type @C1 [concrete]
-// CHECK:STDOUT:   %C1.elem: type = unbound_element_type %C1, %B [concrete]
-// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %B} [concrete]
-// CHECK:STDOUT:   %complete_type.98e: <witness> = complete_type_witness %struct_type.base [concrete]
-// CHECK:STDOUT:   %C2: type = class_type @C2 [concrete]
-// CHECK:STDOUT:   %C2.elem: type = unbound_element_type %C2, %B [concrete]
-// CHECK:STDOUT:   %C3: type = class_type @C3 [concrete]
-// CHECK:STDOUT:   %C3.elem: type = unbound_element_type %C3, %B [concrete]
-// CHECK:STDOUT:   %C4: type = class_type @C4 [concrete]
-// CHECK:STDOUT:   %C4.elem: type = unbound_element_type %C4, %B [concrete]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
-// CHECK:STDOUT:     import Core//prelude
-// CHECK:STDOUT:     import Core//prelude/...
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .B = %B.decl
-// CHECK:STDOUT:     .C1 = %C1.decl
-// CHECK:STDOUT:     .C2 = %C2.decl
-// CHECK:STDOUT:     .C3 = %C3.decl
-// CHECK:STDOUT:     .C4 = %C4.decl
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Core.import = import Core
-// CHECK:STDOUT:   %B.decl: type = class_decl @B [concrete = constants.%B] {} {}
-// CHECK:STDOUT:   %C1.decl: type = class_decl @C1 [concrete = constants.%C1] {} {}
-// CHECK:STDOUT:   %C2.decl: type = class_decl @C2 [concrete = constants.%C2] {} {}
-// CHECK:STDOUT:   %C3.decl: type = class_decl @C3 [concrete = constants.%C3] {} {}
-// CHECK:STDOUT:   %C4.decl: type = class_decl @C4 [concrete = constants.%C4] {} {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @B {
-// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type.357]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%B
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C1 {
-// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [concrete = constants.%B]
-// CHECK:STDOUT:   %.loc18: %C1.elem = base_decl %B.ref, element0 [concrete]
-// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %B} [concrete = constants.%struct_type.base]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.98e]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C1
-// CHECK:STDOUT:   .B = <poisoned>
-// CHECK:STDOUT:   .base = %.loc18
-// CHECK:STDOUT:   extend %B.ref
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C2 {
-// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [concrete = constants.%B]
-// CHECK:STDOUT:   %.loc30: %C2.elem = base_decl %B.ref, element0 [concrete]
-// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %B} [concrete = constants.%struct_type.base]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.98e]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C2
-// CHECK:STDOUT:   .B = <poisoned>
-// CHECK:STDOUT:   .base = %.loc30
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C3 {
-// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [concrete = constants.%B]
-// CHECK:STDOUT:   %.loc41: %C3.elem = base_decl %B.ref, element0 [concrete]
-// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %B} [concrete = constants.%struct_type.base]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.98e]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C3
-// CHECK:STDOUT:   .B = <poisoned>
-// CHECK:STDOUT:   .base = %.loc41
-// CHECK:STDOUT:   extend %B.ref
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C4 {
-// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [concrete = constants.%B]
-// CHECK:STDOUT:   %.loc52: %C4.elem = base_decl %B.ref, element0 [concrete]
-// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %B} [concrete = constants.%struct_type.base]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.98e]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C4
-// CHECK:STDOUT:   .B = <poisoned>
-// CHECK:STDOUT:   .base = %.loc52
-// CHECK:STDOUT:   extend %B.ref
-// CHECK:STDOUT: }
-// CHECK:STDOUT:

+ 56 - 0
toolchain/check/testdata/impl/min_prelude/extend_final.carbon

@@ -0,0 +1,56 @@
+// 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-FILE: toolchain/testing/min_prelude/facet_types.carbon
+// EXTRA-ARGS: --no-dump-sem-ir --custom-core
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/min_prelude/extend_final.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/min_prelude/extend_final.carbon
+
+// --- extend_final_impl.carbon
+library "[[@TEST_NAME]]";
+
+interface Z {
+  let X:! type;
+}
+
+class C {
+  extend final impl as Z where .X = () {}
+}
+
+fn F() {
+  let b: C.X = ();
+}
+
+// --- fail_final_extend_impl.carbon
+library "[[@TEST_NAME]]";
+
+interface Z {}
+
+class C {
+  // CHECK:STDERR: fail_final_extend_impl.carbon:[[@LINE+7]]:9: error: `extend` must appear before `final` [ModifierMustAppearBefore]
+  // CHECK:STDERR:   final extend impl as Z {}
+  // CHECK:STDERR:         ^~~~~~
+  // CHECK:STDERR: fail_final_extend_impl.carbon:[[@LINE+4]]:3: note: `final` previously appeared here [ModifierPrevious]
+  // CHECK:STDERR:   final extend impl as Z {}
+  // CHECK:STDERR:   ^~~~~
+  // CHECK:STDERR:
+  final extend impl as Z {}
+}
+
+// --- fail_final_extend_impl_outside_class.carbon
+library "[[@TEST_NAME]]";
+
+interface Z {}
+
+class C {}
+
+// CHECK:STDERR: fail_final_extend_impl_outside_class.carbon:[[@LINE+4]]:1: error: `extend impl` can only be used in a class [ExtendImplOutsideClass]
+// CHECK:STDERR: extend final impl C as Z {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+extend final impl C as Z {}

+ 19 - 71
toolchain/check/testdata/packages/no_prelude/fail_modifiers.carbon

@@ -2,12 +2,22 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
+// EXTRA-ARGS: --no-dump-sem-ir
+//
 // AUTOUPDATE
 // TIP: To test this file alone, run:
 // TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/packages/no_prelude/fail_modifiers.carbon
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/packages/no_prelude/fail_modifiers.carbon
 
+// --- fail_extend_package.carbon
+
+// CHECK:STDERR: fail_extend_package.carbon:[[@LINE+4]]:1: error: `extend` not allowed on `package` declaration [ModifierNotAllowedOnDeclaration]
+// CHECK:STDERR: extend package ExtendPackage;
+// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR:
+extend package ExtendPackage;
+
 // --- fail_export_package.carbon
 
 // CHECK:STDERR: fail_export_package.carbon:[[@LINE+4]]:1: error: `export` not allowed on `package` declaration [ModifierNotAllowedOnDeclaration]
@@ -16,13 +26,17 @@
 // CHECK:STDERR:
 export package ExportPackage;
 
-// --- fail_extend_package.carbon
+// --- fail_final_package.carbon
 
-// CHECK:STDERR: fail_extend_package.carbon:[[@LINE+4]]:1: error: `extend` not allowed on `package` declaration [ModifierNotAllowedOnDeclaration]
-// CHECK:STDERR: extend package ExtendPackage;
-// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR: fail_final_package.carbon:[[@LINE+8]]:1: error: library's API previously provided by `fail_extend_package.carbon` [DuplicateLibraryApi]
+// CHECK:STDERR: final package ExtendPackage;
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-extend package ExtendPackage;
+// CHECK:STDERR: fail_final_package.carbon:[[@LINE+4]]:1: error: `final` not allowed on `package` declaration [ModifierNotAllowedOnDeclaration]
+// CHECK:STDERR: final package ExtendPackage;
+// CHECK:STDERR: ^~~~~
+// CHECK:STDERR:
+final package ExtendPackage;
 
 // --- fail_virtual_package.carbon
 
@@ -99,69 +113,3 @@ base import BaseImport;
 // CHECK:STDERR: ^~~~~~~
 // CHECK:STDERR:
 private import PrivateImport;
-
-// CHECK:STDOUT: --- fail_export_package.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_extend_package.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_virtual_package.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_private_package.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_export_library.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_import_modifiers.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %ImplImport: <namespace> = namespace file.%ImplImport.import, [concrete] {
-// CHECK:STDOUT:     has_error
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %ExtendImport: <namespace> = namespace file.%ExtendImport.import, [concrete] {
-// CHECK:STDOUT:     has_error
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %VirtualImport: <namespace> = namespace file.%VirtualImport.import, [concrete] {
-// CHECK:STDOUT:     has_error
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %BaseImport: <namespace> = namespace file.%BaseImport.import, [concrete] {
-// CHECK:STDOUT:     has_error
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %PrivateImport: <namespace> = namespace file.%PrivateImport.import, [concrete] {
-// CHECK:STDOUT:     has_error
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .ImplImport = imports.%ImplImport
-// CHECK:STDOUT:     .ExtendImport = imports.%ExtendImport
-// CHECK:STDOUT:     .VirtualImport = imports.%VirtualImport
-// CHECK:STDOUT:     .BaseImport = imports.%BaseImport
-// CHECK:STDOUT:     .PrivateImport = imports.%PrivateImport
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %ImplImport.import = import ImplImport
-// CHECK:STDOUT:   %ExtendImport.import = import ExtendImport
-// CHECK:STDOUT:   %VirtualImport.import = import VirtualImport
-// CHECK:STDOUT:   %BaseImport.import = import BaseImport
-// CHECK:STDOUT:   %PrivateImport.import = import PrivateImport
-// CHECK:STDOUT: }
-// CHECK:STDOUT: