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

Require `extend` before `base: BaseType;` (#3459)

josh11b 2 лет назад
Родитель
Сommit
a970b1e587

+ 7 - 5
toolchain/check/decl_state.h

@@ -23,9 +23,10 @@ enum class KeywordModifierSet {
   Abstract = 1 << 2,
   Base = 1 << 3,
   Default = 1 << 4,
-  Final = 1 << 5,
-  Impl = 1 << 6,
-  Virtual = 1 << 7,
+  Extend = 1 << 5,
+  Final = 1 << 6,
+  Impl = 1 << 7,
+  Virtual = 1 << 8,
 
   // Sets of modifiers:
   Access = Private | Protected,
@@ -45,7 +46,7 @@ inline auto operator!(KeywordModifierSet k) -> bool {
 // declaration and the keyword modifiers that apply to that declaration.
 struct DeclState {
   // What kind of declaration
-  enum DeclKind { FileScope, Class, Constraint, Fn, Interface, Let, Var };
+  enum DeclKind { FileScope, Class, Base, Constraint, Fn, Interface, Let, Var };
 
   explicit DeclState(DeclKind decl_kind, Parse::NodeId parse_node)
       : kind(decl_kind), first_node(parse_node) {}
@@ -86,7 +87,8 @@ class DeclStateStack {
 
   // Exits a declaration of kind `k`.
   auto Pop(DeclState::DeclKind k) -> void {
-    CARBON_CHECK(stack_.back().kind == k);
+    CARBON_CHECK(stack_.back().kind == k)
+        << "Found: " << stack_.back().kind << " expected: " << k;
     stack_.pop_back();
     CARBON_CHECK(!stack_.empty());
   }

+ 14 - 2
toolchain/check/handle_class.cpp

@@ -165,8 +165,8 @@ auto HandleClassDefinitionStart(Context& context, Parse::NodeId parse_node)
   return true;
 }
 
-auto HandleBaseIntroducer(Context& /*context*/, Parse::NodeId /*parse_node*/)
-    -> bool {
+auto HandleBaseIntroducer(Context& context, Parse::NodeId parse_node) -> bool {
+  context.decl_state_stack().Push(DeclState::Base, parse_node);
   return true;
 }
 
@@ -178,6 +178,18 @@ auto HandleBaseColon(Context& /*context*/, Parse::NodeId /*parse_node*/)
 auto HandleBaseDecl(Context& context, Parse::NodeId parse_node) -> bool {
   auto base_type_expr_id = context.node_stack().PopExpr();
 
+  // Process modifiers. `extend` is required, none others are allowed.
+  LimitModifiersOnDecl(context, KeywordModifierSet::Extend,
+                       Lex::TokenKind::Base);
+  auto modifiers = context.decl_state_stack().innermost().modifier_set;
+  if (!(modifiers & KeywordModifierSet::Extend)) {
+    CARBON_DIAGNOSTIC(BaseMissingExtend, Error,
+                      "Missing `extend` before `base` declaration in class.");
+    context.emitter().Emit(context.decl_state_stack().innermost().first_node,
+                           BaseMissingExtend);
+  }
+  context.decl_state_stack().Pop(DeclState::Base);
+
   auto enclosing_class_decl = context.GetCurrentScopeAs<SemIR::ClassDecl>();
   if (!enclosing_class_decl) {
     CARBON_DIAGNOSTIC(BaseOutsideClass, Error,

+ 2 - 0
toolchain/check/handle_modifier.cpp

@@ -83,6 +83,8 @@ static auto GetDeclModifierEnum(Lex::TokenKind token_kind)
       return KeywordModifierSet::Base;
     case Lex::TokenKind::Default:
       return KeywordModifierSet::Default;
+    case Lex::TokenKind::Extend:
+      return KeywordModifierSet::Extend;
     case Lex::TokenKind::Final:
       return KeywordModifierSet::Final;
     case Lex::TokenKind::Impl:

+ 44 - 45
toolchain/check/testdata/class/base.carbon

@@ -9,8 +9,7 @@ base class Base {
 }
 
 class Derived {
-  // TODO: `extend base: Base;`
-  base: Base;
+  extend base: Base;
 
   var d: i32;
 }
@@ -28,14 +27,14 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.loc9: type = struct_type {.b: i32}
 // CHECK:STDOUT:   %.loc7: type = ptr_type {.b: i32}
-// CHECK:STDOUT:   %.loc16_1.1: type = struct_type {.base: Base, .d: i32}
-// CHECK:STDOUT:   %.loc16_1.2: type = struct_type {.base: {.b: i32}*, .d: i32}
-// CHECK:STDOUT:   %.loc16_1.3: type = ptr_type {.base: {.b: i32}*, .d: i32}
+// CHECK:STDOUT:   %.loc15_1.1: type = struct_type {.base: Base, .d: i32}
+// CHECK:STDOUT:   %.loc15_1.2: type = struct_type {.base: {.b: i32}*, .d: i32}
+// CHECK:STDOUT:   %.loc15_1.3: type = ptr_type {.base: {.b: i32}*, .d: i32}
 // CHECK:STDOUT:   %.loc11: type = ptr_type {.base: Base, .d: i32}
-// CHECK:STDOUT:   %.loc19: type = struct_type {.base: {.b: i32}, .d: i32}
-// CHECK:STDOUT:   %.loc22_35.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc22_35.2: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc22_35.3: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc18: type = struct_type {.base: {.b: i32}, .d: i32}
+// CHECK:STDOUT:   %.loc21_35.1: type = tuple_type (type, type)
+// CHECK:STDOUT:   %.loc21_35.2: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc21_35.3: type = ptr_type (i32, i32)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -59,52 +58,52 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base
-// CHECK:STDOUT:   %.loc13_13.1: type = unbound_element_type Derived, Base
-// CHECK:STDOUT:   %.loc13_13.2: <unbound element of class Derived> = base_decl Base, element0
-// CHECK:STDOUT:   %.loc15_8.1: type = unbound_element_type Derived, i32
-// CHECK:STDOUT:   %.loc15_8.2: <unbound element of class Derived> = field_decl d, element1
-// CHECK:STDOUT:   %d: <unbound element of class Derived> = bind_name d, %.loc15_8.2
+// CHECK:STDOUT:   %.loc12_20.1: type = unbound_element_type Derived, Base
+// CHECK:STDOUT:   %.loc12_20.2: <unbound element of class Derived> = base_decl Base, element0
+// CHECK:STDOUT:   %.loc14_8.1: type = unbound_element_type Derived, i32
+// CHECK:STDOUT:   %.loc14_8.2: <unbound element of class Derived> = field_decl d, element1
+// CHECK:STDOUT:   %d: <unbound element of class Derived> = bind_name d, %.loc14_8.2
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc13_13.2
+// CHECK:STDOUT:   .base = %.loc12_20.2
 // CHECK:STDOUT:   .d = %d
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Make() -> %return: Derived {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc19_25: i32 = int_literal 4
-// CHECK:STDOUT:   %.loc19_26.1: {.b: i32} = struct_literal (%.loc19_25)
-// CHECK:STDOUT:   %.loc19_34: i32 = int_literal 7
-// CHECK:STDOUT:   %.loc19_35.1: {.base: {.b: i32}, .d: i32} = struct_literal (%.loc19_26.1, %.loc19_34)
-// CHECK:STDOUT:   %.loc19_35.2: ref Base = class_element_access %return, element0
-// CHECK:STDOUT:   %.loc19_26.2: ref i32 = class_element_access %.loc19_35.2, element0
-// CHECK:STDOUT:   %.loc19_26.3: init i32 = initialize_from %.loc19_25 to %.loc19_26.2
-// CHECK:STDOUT:   %.loc19_26.4: init Base = class_init (%.loc19_26.3), %.loc19_35.2
-// CHECK:STDOUT:   %.loc19_26.5: init Base = converted %.loc19_26.1, %.loc19_26.4
-// CHECK:STDOUT:   %.loc19_35.3: ref i32 = class_element_access %return, element1
-// CHECK:STDOUT:   %.loc19_35.4: init i32 = initialize_from %.loc19_34 to %.loc19_35.3
-// CHECK:STDOUT:   %.loc19_35.5: init Derived = class_init (%.loc19_26.5, %.loc19_35.4), %return
-// CHECK:STDOUT:   %.loc19_35.6: init Derived = converted %.loc19_35.1, %.loc19_35.5
-// CHECK:STDOUT:   return %.loc19_35.6
+// CHECK:STDOUT:   %.loc18_25: i32 = int_literal 4
+// CHECK:STDOUT:   %.loc18_26.1: {.b: i32} = struct_literal (%.loc18_25)
+// CHECK:STDOUT:   %.loc18_34: i32 = int_literal 7
+// CHECK:STDOUT:   %.loc18_35.1: {.base: {.b: i32}, .d: i32} = struct_literal (%.loc18_26.1, %.loc18_34)
+// CHECK:STDOUT:   %.loc18_35.2: ref Base = class_element_access %return, element0
+// CHECK:STDOUT:   %.loc18_26.2: ref i32 = class_element_access %.loc18_35.2, element0
+// CHECK:STDOUT:   %.loc18_26.3: init i32 = initialize_from %.loc18_25 to %.loc18_26.2
+// CHECK:STDOUT:   %.loc18_26.4: init Base = class_init (%.loc18_26.3), %.loc18_35.2
+// CHECK:STDOUT:   %.loc18_26.5: init Base = converted %.loc18_26.1, %.loc18_26.4
+// CHECK:STDOUT:   %.loc18_35.3: ref i32 = class_element_access %return, element1
+// CHECK:STDOUT:   %.loc18_35.4: init i32 = initialize_from %.loc18_34 to %.loc18_35.3
+// CHECK:STDOUT:   %.loc18_35.5: init Derived = class_init (%.loc18_26.5, %.loc18_35.4), %return
+// CHECK:STDOUT:   %.loc18_35.6: init Derived = converted %.loc18_35.1, %.loc18_35.5
+// CHECK:STDOUT:   return %.loc18_35.6
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Access(%d: Derived) -> %return: (i32, i32) {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %d.ref.loc23_11: Derived = name_ref d, %d
-// CHECK:STDOUT:   %.loc23_12.1: ref i32 = class_element_access %d.ref.loc23_11, element1
-// CHECK:STDOUT:   %.loc23_12.2: i32 = bind_value %.loc23_12.1
-// CHECK:STDOUT:   %d.ref.loc23_16: Derived = name_ref d, %d
-// CHECK:STDOUT:   %.loc23_17.1: ref Base = class_element_access %d.ref.loc23_16, element0
-// CHECK:STDOUT:   %.loc23_17.2: Base = bind_value %.loc23_17.1
-// CHECK:STDOUT:   %.loc23_22.1: ref i32 = class_element_access %.loc23_17.2, element0
-// CHECK:STDOUT:   %.loc23_22.2: i32 = bind_value %.loc23_22.1
-// CHECK:STDOUT:   %.loc23_24.1: (i32, i32) = tuple_literal (%.loc23_12.2, %.loc23_22.2)
-// CHECK:STDOUT:   %.loc23_24.2: ref i32 = tuple_access %return, element0
-// CHECK:STDOUT:   %.loc23_24.3: init i32 = initialize_from %.loc23_12.2 to %.loc23_24.2
-// CHECK:STDOUT:   %.loc23_24.4: ref i32 = tuple_access %return, element1
-// CHECK:STDOUT:   %.loc23_24.5: init i32 = initialize_from %.loc23_22.2 to %.loc23_24.4
-// CHECK:STDOUT:   %.loc23_24.6: init (i32, i32) = tuple_init (%.loc23_24.3, %.loc23_24.5) to %return
-// CHECK:STDOUT:   %.loc23_24.7: init (i32, i32) = converted %.loc23_24.1, %.loc23_24.6
-// CHECK:STDOUT:   return %.loc23_24.7
+// CHECK:STDOUT:   %d.ref.loc22_11: Derived = name_ref d, %d
+// CHECK:STDOUT:   %.loc22_12.1: ref i32 = class_element_access %d.ref.loc22_11, element1
+// CHECK:STDOUT:   %.loc22_12.2: i32 = bind_value %.loc22_12.1
+// CHECK:STDOUT:   %d.ref.loc22_16: Derived = name_ref d, %d
+// CHECK:STDOUT:   %.loc22_17.1: ref Base = class_element_access %d.ref.loc22_16, element0
+// CHECK:STDOUT:   %.loc22_17.2: Base = bind_value %.loc22_17.1
+// CHECK:STDOUT:   %.loc22_22.1: ref i32 = class_element_access %.loc22_17.2, element0
+// CHECK:STDOUT:   %.loc22_22.2: i32 = bind_value %.loc22_22.1
+// CHECK:STDOUT:   %.loc22_24.1: (i32, i32) = tuple_literal (%.loc22_12.2, %.loc22_22.2)
+// CHECK:STDOUT:   %.loc22_24.2: ref i32 = tuple_access %return, element0
+// CHECK:STDOUT:   %.loc22_24.3: init i32 = initialize_from %.loc22_12.2 to %.loc22_24.2
+// CHECK:STDOUT:   %.loc22_24.4: ref i32 = tuple_access %return, element1
+// CHECK:STDOUT:   %.loc22_24.5: init i32 = initialize_from %.loc22_22.2 to %.loc22_24.4
+// CHECK:STDOUT:   %.loc22_24.6: init (i32, i32) = tuple_init (%.loc22_24.3, %.loc22_24.5) to %return
+// CHECK:STDOUT:   %.loc22_24.7: init (i32, i32) = converted %.loc22_24.1, %.loc22_24.6
+// CHECK:STDOUT:   return %.loc22_24.7
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 34 - 35
toolchain/check/testdata/class/fail_abstract.carbon

@@ -9,8 +9,7 @@ abstract class Abstract {
 }
 
 class Derived {
-  // TODO: `extend base: Base;`
-  base: Abstract;
+  extend base: Abstract;
 
   var d: i32;
 }
@@ -32,14 +31,14 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.loc9: type = struct_type {.a: i32}
 // CHECK:STDOUT:   %.loc7: type = ptr_type {.a: i32}
-// CHECK:STDOUT:   %.loc16_1.1: type = struct_type {.base: Abstract, .d: i32}
-// CHECK:STDOUT:   %.loc16_1.2: type = struct_type {.base: {.a: i32}*, .d: i32}
-// CHECK:STDOUT:   %.loc16_1.3: type = ptr_type {.base: {.a: i32}*, .d: i32}
+// CHECK:STDOUT:   %.loc15_1.1: type = struct_type {.base: Abstract, .d: i32}
+// CHECK:STDOUT:   %.loc15_1.2: type = struct_type {.base: {.a: i32}*, .d: i32}
+// CHECK:STDOUT:   %.loc15_1.3: type = ptr_type {.base: {.a: i32}*, .d: i32}
 // CHECK:STDOUT:   %.loc11: type = ptr_type {.base: Abstract, .d: i32}
-// CHECK:STDOUT:   %.loc23: type = struct_type {.base: {.a: i32}, .d: i32}
-// CHECK:STDOUT:   %.loc26_35.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc26_35.2: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc26_35.3: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc22: type = struct_type {.base: {.a: i32}, .d: i32}
+// CHECK:STDOUT:   %.loc25_35.1: type = tuple_type (type, type)
+// CHECK:STDOUT:   %.loc25_35.2: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc25_35.3: type = ptr_type (i32, i32)
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -63,43 +62,43 @@ fn Access(d: Derived) -> (i32, i32) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT:   %Abstract.ref: type = name_ref Abstract, file.%Abstract
-// CHECK:STDOUT:   %.loc13_17.1: type = unbound_element_type Derived, Abstract
-// CHECK:STDOUT:   %.loc13_17.2: <unbound element of class Derived> = base_decl Abstract, element0
-// CHECK:STDOUT:   %.loc15_8.1: type = unbound_element_type Derived, i32
-// CHECK:STDOUT:   %.loc15_8.2: <unbound element of class Derived> = field_decl d, element1
-// CHECK:STDOUT:   %d: <unbound element of class Derived> = bind_name d, %.loc15_8.2
+// CHECK:STDOUT:   %.loc12_24.1: type = unbound_element_type Derived, Abstract
+// CHECK:STDOUT:   %.loc12_24.2: <unbound element of class Derived> = base_decl Abstract, element0
+// CHECK:STDOUT:   %.loc14_8.1: type = unbound_element_type Derived, i32
+// CHECK:STDOUT:   %.loc14_8.2: <unbound element of class Derived> = field_decl d, element1
+// CHECK:STDOUT:   %d: <unbound element of class Derived> = bind_name d, %.loc14_8.2
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc13_17.2
+// CHECK:STDOUT:   .base = %.loc12_24.2
 // CHECK:STDOUT:   .d = %d
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Make() -> %return: Derived {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc23_25: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc23_26: {.a: i32} = struct_literal (%.loc23_25)
-// CHECK:STDOUT:   %.loc23_34: i32 = int_literal 7
-// CHECK:STDOUT:   %.loc23_35: {.base: {.a: i32}, .d: i32} = struct_literal (%.loc23_26, %.loc23_34)
+// CHECK:STDOUT:   %.loc22_25: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc22_26: {.a: i32} = struct_literal (%.loc22_25)
+// CHECK:STDOUT:   %.loc22_34: i32 = int_literal 7
+// CHECK:STDOUT:   %.loc22_35: {.base: {.a: i32}, .d: i32} = struct_literal (%.loc22_26, %.loc22_34)
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Access(%d: Derived) -> %return: (i32, i32) {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %d.ref.loc27_11: Derived = name_ref d, %d
-// CHECK:STDOUT:   %.loc27_12.1: ref i32 = class_element_access %d.ref.loc27_11, element1
-// CHECK:STDOUT:   %.loc27_12.2: i32 = bind_value %.loc27_12.1
-// CHECK:STDOUT:   %d.ref.loc27_16: Derived = name_ref d, %d
-// CHECK:STDOUT:   %.loc27_17.1: ref Abstract = class_element_access %d.ref.loc27_16, element0
-// CHECK:STDOUT:   %.loc27_17.2: Abstract = bind_value %.loc27_17.1
-// CHECK:STDOUT:   %.loc27_22.1: ref i32 = class_element_access %.loc27_17.2, element0
-// CHECK:STDOUT:   %.loc27_22.2: i32 = bind_value %.loc27_22.1
-// CHECK:STDOUT:   %.loc27_24.1: (i32, i32) = tuple_literal (%.loc27_12.2, %.loc27_22.2)
-// CHECK:STDOUT:   %.loc27_24.2: ref i32 = tuple_access %return, element0
-// CHECK:STDOUT:   %.loc27_24.3: init i32 = initialize_from %.loc27_12.2 to %.loc27_24.2
-// CHECK:STDOUT:   %.loc27_24.4: ref i32 = tuple_access %return, element1
-// CHECK:STDOUT:   %.loc27_24.5: init i32 = initialize_from %.loc27_22.2 to %.loc27_24.4
-// CHECK:STDOUT:   %.loc27_24.6: init (i32, i32) = tuple_init (%.loc27_24.3, %.loc27_24.5) to %return
-// CHECK:STDOUT:   %.loc27_24.7: init (i32, i32) = converted %.loc27_24.1, %.loc27_24.6
-// CHECK:STDOUT:   return %.loc27_24.7
+// CHECK:STDOUT:   %d.ref.loc26_11: Derived = name_ref d, %d
+// CHECK:STDOUT:   %.loc26_12.1: ref i32 = class_element_access %d.ref.loc26_11, element1
+// CHECK:STDOUT:   %.loc26_12.2: i32 = bind_value %.loc26_12.1
+// CHECK:STDOUT:   %d.ref.loc26_16: Derived = name_ref d, %d
+// CHECK:STDOUT:   %.loc26_17.1: ref Abstract = class_element_access %d.ref.loc26_16, element0
+// CHECK:STDOUT:   %.loc26_17.2: Abstract = bind_value %.loc26_17.1
+// CHECK:STDOUT:   %.loc26_22.1: ref i32 = class_element_access %.loc26_17.2, element0
+// CHECK:STDOUT:   %.loc26_22.2: i32 = bind_value %.loc26_22.1
+// CHECK:STDOUT:   %.loc26_24.1: (i32, i32) = tuple_literal (%.loc26_12.2, %.loc26_22.2)
+// CHECK:STDOUT:   %.loc26_24.2: ref i32 = tuple_access %return, element0
+// CHECK:STDOUT:   %.loc26_24.3: init i32 = initialize_from %.loc26_12.2 to %.loc26_24.2
+// CHECK:STDOUT:   %.loc26_24.4: ref i32 = tuple_access %return, element1
+// CHECK:STDOUT:   %.loc26_24.5: init i32 = initialize_from %.loc26_22.2 to %.loc26_24.4
+// CHECK:STDOUT:   %.loc26_24.6: init (i32, i32) = tuple_init (%.loc26_24.3, %.loc26_24.5) to %return
+// CHECK:STDOUT:   %.loc26_24.7: init (i32, i32) = converted %.loc26_24.1, %.loc26_24.6
+// CHECK:STDOUT:   return %.loc26_24.7
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 49 - 49
toolchain/check/testdata/class/fail_base_bad_type.carbon

@@ -8,52 +8,52 @@ base class Base {}
 class Final {}
 
 class DeriveFromNonType {
-  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:11: ERROR: Cannot implicitly convert from `i32` to `type`.
-  // CHECK:STDERR:   base: 32;
-  // CHECK:STDERR:           ^
-  base: 32;
+  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:18: ERROR: Cannot implicitly convert from `i32` to `type`.
+  // CHECK:STDERR:   extend base: 32;
+  // CHECK:STDERR:                  ^
+  extend base: 32;
 }
 
 class DeriveFromi32 {
-  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:12: ERROR: Deriving from final type `i32`. Base type must be an `abstract` or `base` class.
-  // CHECK:STDERR:   base: i32;
-  // CHECK:STDERR:            ^
-  base: i32;
+  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:19: ERROR: Deriving from final type `i32`. Base type must be an `abstract` or `base` class.
+  // CHECK:STDERR:   extend base: i32;
+  // CHECK:STDERR:                   ^
+  extend base: i32;
 }
 
 class DeriveFromTuple {
-  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:16: ERROR: Deriving from final type `(Base,)`. Base type must be an `abstract` or `base` class.
-  // CHECK:STDERR:   base: (Base,);
-  // CHECK:STDERR:                ^
-  base: (Base,);
+  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:23: ERROR: Deriving from final type `(Base,)`. Base type must be an `abstract` or `base` class.
+  // CHECK:STDERR:   extend base: (Base,);
+  // CHECK:STDERR:                       ^
+  extend base: (Base,);
 }
 
 // TODO: Should we allow this?
 // We do allow `{.base = {.a: i32, .b: i32}}`.
 class DeriveFromStruct {
-  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:27: ERROR: Deriving from final type `{.a: i32, .b: i32}`. Base type must be an `abstract` or `base` class.
-  // CHECK:STDERR:   base: {.a: i32, .b: i32};
-  // CHECK:STDERR:                           ^
-  base: {.a: i32, .b: i32};
+  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:34: ERROR: Deriving from final type `{.a: i32, .b: i32}`. Base type must be an `abstract` or `base` class.
+  // CHECK:STDERR:   extend base: {.a: i32, .b: i32};
+  // CHECK:STDERR:                                  ^
+  extend base: {.a: i32, .b: i32};
 }
 
 base class Incomplete;
 
 class DeriveFromIncomplete {
-  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+6]]:19: ERROR: Base `Incomplete` is an incomplete type.
-  // CHECK:STDERR:   base: Incomplete;
-  // CHECK:STDERR:                   ^
+  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+6]]:26: ERROR: Base `Incomplete` is an incomplete type.
+  // CHECK:STDERR:   extend base: Incomplete;
+  // CHECK:STDERR:                          ^
   // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE-6]]:1: Class was forward declared here.
   // CHECK:STDERR: base class Incomplete;
   // CHECK:STDERR: ^~~~
-  base: Incomplete;
+  extend base: Incomplete;
 }
 
 class DeriveFromFinal {
-  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:14: ERROR: Deriving from final type `Final`. Base type must be an `abstract` or `base` class.
-  // CHECK:STDERR:   base: Final;
-  // CHECK:STDERR:              ^
-  base: Final;
+  // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:21: ERROR: Deriving from final type `Final`. Base type must be an `abstract` or `base` class.
+  // CHECK:STDERR:   extend base: Final;
+  // CHECK:STDERR:                     ^
+  extend base: Final;
 }
 
 // CHECK:STDOUT: --- fail_base_bad_type.carbon
@@ -62,11 +62,11 @@ class DeriveFromFinal {
 // CHECK:STDOUT:   %.loc7_18.1: type = struct_type {}
 // CHECK:STDOUT:   %.loc15: type = struct_type {.base: <error>}
 // CHECK:STDOUT:   %.loc22: type = struct_type {.base: i32}
-// CHECK:STDOUT:   %.loc28_15: type = tuple_type (type)
-// CHECK:STDOUT:   %.loc28_16.1: type = tuple_type (Base)
+// CHECK:STDOUT:   %.loc28_22: type = tuple_type (type)
+// CHECK:STDOUT:   %.loc28_23.1: type = tuple_type (Base)
 // CHECK:STDOUT:   %.loc7_18.2: type = tuple_type ()
 // CHECK:STDOUT:   %.loc7_1: type = ptr_type {}
-// CHECK:STDOUT:   %.loc28_16.2: type = tuple_type ({}*)
+// CHECK:STDOUT:   %.loc28_23.2: type = tuple_type ({}*)
 // CHECK:STDOUT:   %.loc29: type = struct_type {.base: (Base,)}
 // CHECK:STDOUT:   %.loc37: type = ptr_type {.a: i32, .b: i32}
 // CHECK:STDOUT:   %.loc38: type = struct_type {.base: {.a: i32, .b: i32}}
@@ -106,59 +106,59 @@ class DeriveFromFinal {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromNonType {
-// CHECK:STDOUT:   %.loc14_9: i32 = int_literal 32
-// CHECK:STDOUT:   %.loc14_11.1: type = unbound_element_type DeriveFromNonType, <error>
-// CHECK:STDOUT:   %.loc14_11.2: <unbound element of class DeriveFromNonType> = base_decl <error>, element0
+// CHECK:STDOUT:   %.loc14_16: i32 = int_literal 32
+// CHECK:STDOUT:   %.loc14_18.1: type = unbound_element_type DeriveFromNonType, <error>
+// CHECK:STDOUT:   %.loc14_18.2: <unbound element of class DeriveFromNonType> = base_decl <error>, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc14_11.2
+// CHECK:STDOUT:   .base = %.loc14_18.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromi32 {
-// CHECK:STDOUT:   %.loc21_12.1: type = unbound_element_type DeriveFromi32, i32
-// CHECK:STDOUT:   %.loc21_12.2: <unbound element of class DeriveFromi32> = base_decl i32, element0
+// CHECK:STDOUT:   %.loc21_19.1: type = unbound_element_type DeriveFromi32, i32
+// CHECK:STDOUT:   %.loc21_19.2: <unbound element of class DeriveFromi32> = base_decl i32, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc21_12.2
+// CHECK:STDOUT:   .base = %.loc21_19.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromTuple {
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base
-// CHECK:STDOUT:   %.loc28_15: (type,) = tuple_literal (%Base.ref)
-// CHECK:STDOUT:   %.loc28_16.1: type = converted %.loc28_15, constants.%.loc28_16.1
-// CHECK:STDOUT:   %.loc28_16.2: type = unbound_element_type DeriveFromTuple, (Base,)
-// CHECK:STDOUT:   %.loc28_16.3: <unbound element of class DeriveFromTuple> = base_decl (Base,), element0
+// CHECK:STDOUT:   %.loc28_22: (type,) = tuple_literal (%Base.ref)
+// CHECK:STDOUT:   %.loc28_23.1: type = converted %.loc28_22, constants.%.loc28_23.1
+// CHECK:STDOUT:   %.loc28_23.2: type = unbound_element_type DeriveFromTuple, (Base,)
+// CHECK:STDOUT:   %.loc28_23.3: <unbound element of class DeriveFromTuple> = base_decl (Base,), element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc28_16.3
+// CHECK:STDOUT:   .base = %.loc28_23.3
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromStruct {
-// CHECK:STDOUT:   %.loc37_26: type = struct_type {.a: i32, .b: i32}
-// CHECK:STDOUT:   %.loc37_27.1: type = unbound_element_type DeriveFromStruct, {.a: i32, .b: i32}
-// CHECK:STDOUT:   %.loc37_27.2: <unbound element of class DeriveFromStruct> = base_decl {.a: i32, .b: i32}, element0
+// CHECK:STDOUT:   %.loc37_33: type = struct_type {.a: i32, .b: i32}
+// CHECK:STDOUT:   %.loc37_34.1: type = unbound_element_type DeriveFromStruct, {.a: i32, .b: i32}
+// CHECK:STDOUT:   %.loc37_34.2: <unbound element of class DeriveFromStruct> = base_decl {.a: i32, .b: i32}, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc37_27.2
+// CHECK:STDOUT:   .base = %.loc37_34.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Incomplete;
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromIncomplete {
 // CHECK:STDOUT:   %Incomplete.ref: type = name_ref Incomplete, file.%Incomplete
-// CHECK:STDOUT:   %.loc49_19.1: type = unbound_element_type DeriveFromIncomplete, <error>
-// CHECK:STDOUT:   %.loc49_19.2: <unbound element of class DeriveFromIncomplete> = base_decl <error>, element0
+// CHECK:STDOUT:   %.loc49_26.1: type = unbound_element_type DeriveFromIncomplete, <error>
+// CHECK:STDOUT:   %.loc49_26.2: <unbound element of class DeriveFromIncomplete> = base_decl <error>, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc49_19.2
+// CHECK:STDOUT:   .base = %.loc49_26.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromFinal {
 // CHECK:STDOUT:   %Final.ref: type = name_ref Final, file.%Final
-// CHECK:STDOUT:   %.loc56_14.1: type = unbound_element_type DeriveFromFinal, Final
-// CHECK:STDOUT:   %.loc56_14.2: <unbound element of class DeriveFromFinal> = base_decl Final, element0
+// CHECK:STDOUT:   %.loc56_21.1: type = unbound_element_type DeriveFromFinal, Final
+// CHECK:STDOUT:   %.loc56_21.2: <unbound element of class DeriveFromFinal> = base_decl Final, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc56_14.2
+// CHECK:STDOUT:   .base = %.loc56_21.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 9 - 9
toolchain/check/testdata/class/fail_base_misplaced.carbon

@@ -6,19 +6,19 @@
 
 base class B {}
 
-// CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+3]]:8: ERROR: `base` declaration can only be used in a class.
-// CHECK:STDERR: base: B;
-// CHECK:STDERR:        ^
-base: B;
+// CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+3]]:15: ERROR: `base` declaration can only be used in a class.
+// CHECK:STDERR: extend base: B;
+// CHECK:STDERR:               ^
+extend base: B;
 
 fn F() {
   // CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+6]]:3: ERROR: Expected expression.
-  // CHECK:STDERR:   base: B;
-  // CHECK:STDERR:   ^~~~
+  // CHECK:STDERR:   extend base: B;
+  // CHECK:STDERR:   ^~~~~~
   // CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+3]]:3: ERROR: Semantics TODO: `HandleInvalidParse`.
-  // CHECK:STDERR:   base: B;
-  // CHECK:STDERR:   ^~~~
-  base: B;
+  // CHECK:STDERR:   extend base: B;
+  // CHECK:STDERR:   ^~~~~~
+  extend base: B;
 }
 
 // CHECK:STDOUT: --- fail_base_misplaced.carbon

+ 109 - 0
toolchain/check/testdata/class/fail_base_modifiers.carbon

@@ -0,0 +1,109 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+base class B {}
+
+class C1 {
+  // CHECK:STDERR: fail_base_modifiers.carbon:[[@LINE+3]]:3: ERROR: `private` not allowed on `base` declaration.
+  // CHECK:STDERR:   private extend base: B;
+  // CHECK:STDERR:   ^~~~~~~
+  private extend base: B;
+}
+
+class C2 {
+  // CHECK:STDERR: fail_base_modifiers.carbon:[[@LINE+6]]:3: ERROR: `abstract` not allowed on `base` declaration.
+  // CHECK:STDERR:   abstract base: B;
+  // CHECK:STDERR:   ^~~~~~~~
+  // CHECK:STDERR: fail_base_modifiers.carbon:[[@LINE+3]]:3: ERROR: Missing `extend` before `base` declaration in class.
+  // CHECK:STDERR:   abstract base: B;
+  // CHECK:STDERR:   ^~~~~~~~
+  abstract base: B;
+}
+
+class C3 {
+  // CHECK:STDERR: fail_base_modifiers.carbon:[[@LINE+6]]:10: ERROR: `default` not allowed on declaration with `extend`.
+  // CHECK:STDERR:   extend default base: B;
+  // CHECK:STDERR:          ^~~~~~~
+  // CHECK:STDERR: fail_base_modifiers.carbon:[[@LINE+3]]:3: `extend` previously appeared here.
+  // CHECK:STDERR:   extend default base: B;
+  // CHECK:STDERR:   ^~~~~~
+  extend default base: B;
+}
+
+class C4 {
+  // CHECK:STDERR: fail_base_modifiers.carbon:[[@LINE+6]]:10: ERROR: `extend` repeated on declaration.
+  // CHECK:STDERR:   extend extend base: B;
+  // CHECK:STDERR:          ^~~~~~
+  // CHECK:STDERR: fail_base_modifiers.carbon:[[@LINE+3]]:3: `extend` previously appeared here.
+  // CHECK:STDERR:   extend extend base: B;
+  // CHECK:STDERR:   ^~~~~~
+  extend extend base: B;
+}
+
+// CHECK:STDOUT: --- fail_base_modifiers.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc7_15.1: type = struct_type {}
+// CHECK:STDOUT:   %.loc7_15.2: type = tuple_type ()
+// CHECK:STDOUT:   %.loc7_1: type = ptr_type {}
+// CHECK:STDOUT:   %.loc14: type = struct_type {.base: B}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.B = %B.decl, .C1 = %C1.decl, .C2 = %C2.decl, .C3 = %C3.decl, .C4 = %C4.decl}
+// CHECK:STDOUT:   %B.decl = class_decl @B, ()
+// CHECK:STDOUT:   %B: type = class_type @B
+// CHECK:STDOUT:   %C1.decl = class_decl @C1, ()
+// CHECK:STDOUT:   %C1: type = class_type @C1
+// CHECK:STDOUT:   %C2.decl = class_decl @C2, ()
+// CHECK:STDOUT:   %C2: type = class_type @C2
+// CHECK:STDOUT:   %C3.decl = class_decl @C3, ()
+// CHECK:STDOUT:   %C3: type = class_type @C3
+// CHECK:STDOUT:   %C4.decl = class_decl @C4, ()
+// CHECK:STDOUT:   %C4: type = class_type @C4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C1 {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B
+// CHECK:STDOUT:   %.loc13_25.1: type = unbound_element_type C1, B
+// CHECK:STDOUT:   %.loc13_25.2: <unbound element of class C1> = base_decl B, element0
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc13_25.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C2 {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B
+// CHECK:STDOUT:   %.loc23_19.1: type = unbound_element_type C2, B
+// CHECK:STDOUT:   %.loc23_19.2: <unbound element of class C2> = base_decl B, element0
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc23_19.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C3 {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B
+// CHECK:STDOUT:   %.loc33_25.1: type = unbound_element_type C3, B
+// CHECK:STDOUT:   %.loc33_25.2: <unbound element of class C3> = base_decl B, element0
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc33_25.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C4 {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B
+// CHECK:STDOUT:   %.loc43_24.1: type = unbound_element_type C4, B
+// CHECK:STDOUT:   %.loc43_24.2: <unbound element of class C4> = base_decl B, element0
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc43_24.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 46 - 0
toolchain/check/testdata/class/fail_base_no_extend.carbon

@@ -0,0 +1,46 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+base class B {}
+
+class C {
+  // CHECK:STDERR: fail_base_no_extend.carbon:[[@LINE+3]]:3: ERROR: Missing `extend` before `base` declaration in class.
+  // CHECK:STDERR:   base: B;
+  // CHECK:STDERR:   ^~~~
+  base: B;
+}
+
+// CHECK:STDOUT: --- fail_base_no_extend.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc7_15.1: type = struct_type {}
+// CHECK:STDOUT:   %.loc7_15.2: type = tuple_type ()
+// CHECK:STDOUT:   %.loc7_1: type = ptr_type {}
+// CHECK:STDOUT:   %.loc14: type = struct_type {.base: B}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.B = %B.decl, .C = %C.decl}
+// CHECK:STDOUT:   %B.decl = class_decl @B, ()
+// CHECK:STDOUT:   %B: type = class_type @B
+// CHECK:STDOUT:   %C.decl = class_decl @C, ()
+// CHECK:STDOUT:   %C: type = class_type @C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B
+// CHECK:STDOUT:   %.loc13_10.1: type = unbound_element_type C, B
+// CHECK:STDOUT:   %.loc13_10.2: <unbound element of class C> = base_decl B, element0
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .base = %.loc13_10.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 22 - 22
toolchain/check/testdata/class/fail_base_repeated.carbon

@@ -8,26 +8,26 @@ base class B1 {}
 base class B2 {}
 
 class C {
-  base: B1;
-  // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE+6]]:11: ERROR: Multiple `base` declarations in class. Multiple inheritance is not permitted.
-  // CHECK:STDERR:   base: B2;
-  // CHECK:STDERR:           ^
-  // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE-4]]:11: Previous `base` declaration is here.
-  // CHECK:STDERR:   base: B1;
-  // CHECK:STDERR:           ^
-  base: B2;
+  extend base: B1;
+  // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE+6]]:18: ERROR: Multiple `base` declarations in class. Multiple inheritance is not permitted.
+  // CHECK:STDERR:   extend base: B2;
+  // CHECK:STDERR:                  ^
+  // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE-4]]:18: Previous `base` declaration is here.
+  // CHECK:STDERR:   extend base: B1;
+  // CHECK:STDERR:                  ^
+  extend base: B2;
 }
 
 class D {
   // TODO: Consider adding a custom diagnostic for this case.
-  base: B1;
-  // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE+6]]:11: ERROR: Multiple `base` declarations in class. Multiple inheritance is not permitted.
-  // CHECK:STDERR:   base: B1;
-  // CHECK:STDERR:           ^
-  // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE-4]]:11: Previous `base` declaration is here.
-  // CHECK:STDERR:   base: B1;
-  // CHECK:STDERR:           ^
-  base: B1;
+  extend base: B1;
+  // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE+6]]:18: ERROR: Multiple `base` declarations in class. Multiple inheritance is not permitted.
+  // CHECK:STDERR:   extend base: B1;
+  // CHECK:STDERR:                  ^
+  // CHECK:STDERR: fail_base_repeated.carbon:[[@LINE-4]]:18: Previous `base` declaration is here.
+  // CHECK:STDERR:   extend base: B1;
+  // CHECK:STDERR:                  ^
+  extend base: B1;
 }
 
 // CHECK:STDOUT: --- fail_base_repeated.carbon
@@ -63,21 +63,21 @@ class D {
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
 // CHECK:STDOUT:   %B1.ref: type = name_ref B1, file.%B1
-// CHECK:STDOUT:   %.loc11_11.1: type = unbound_element_type C, B1
-// CHECK:STDOUT:   %.loc11_11.2: <unbound element of class C> = base_decl B1, element0
+// CHECK:STDOUT:   %.loc11_18.1: type = unbound_element_type C, B1
+// CHECK:STDOUT:   %.loc11_18.2: <unbound element of class C> = base_decl B1, element0
 // CHECK:STDOUT:   %B2.ref: type = name_ref B2, file.%B2
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc11_11.2
+// CHECK:STDOUT:   .base = %.loc11_18.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @D {
 // CHECK:STDOUT:   %B1.ref.loc23: type = name_ref B1, file.%B1
-// CHECK:STDOUT:   %.loc23_11.1: type = unbound_element_type D, B1
-// CHECK:STDOUT:   %.loc23_11.2: <unbound element of class D> = base_decl B1, element0
+// CHECK:STDOUT:   %.loc23_18.1: type = unbound_element_type D, B1
+// CHECK:STDOUT:   %.loc23_18.2: <unbound element of class D> = base_decl B1, element0
 // CHECK:STDOUT:   %B1.ref.loc30: type = name_ref B1, file.%B1
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc23_11.2
+// CHECK:STDOUT:   .base = %.loc23_18.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 5 - 5
toolchain/check/testdata/class/fail_base_unbound.carbon

@@ -7,7 +7,7 @@
 base class B {}
 
 class C {
-  base: B;
+  extend base: B;
 }
 
 // CHECK:STDERR: fail_base_unbound.carbon:[[@LINE+3]]:13: ERROR: Expression cannot be used as a value.
@@ -32,7 +32,7 @@ let b: B = C.base;
 // CHECK:STDOUT:   %C: type = class_type @C
 // CHECK:STDOUT:   %B.ref: type = name_ref B, %B
 // CHECK:STDOUT:   %C.ref: type = name_ref C, %C
-// CHECK:STDOUT:   %base.ref: <unbound element of class C> = name_ref base, @C.%.loc10_10.2
+// CHECK:STDOUT:   %base.ref: <unbound element of class C> = name_ref base, @C.%.loc10_17.2
 // CHECK:STDOUT:   %b: B = bind_name b, <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -43,10 +43,10 @@ let b: B = C.base;
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
 // CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B
-// CHECK:STDOUT:   %.loc10_10.1: type = unbound_element_type C, B
-// CHECK:STDOUT:   %.loc10_10.2: <unbound element of class C> = base_decl B, element0
+// CHECK:STDOUT:   %.loc10_17.1: type = unbound_element_type C, B
+// CHECK:STDOUT:   %.loc10_17.2: <unbound element of class C> = base_decl B, element0
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .base = %.loc10_10.2
+// CHECK:STDOUT:   .base = %.loc10_17.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 1
toolchain/diagnostics/diagnostic_emitter.h

@@ -214,7 +214,9 @@ class DiagnosticEmitter {
   // A builder-pattern type to provide a fluent interface for constructing
   // a more complex diagnostic. See `DiagnosticEmitter::Build` for the
   // expected usage.
-  class DiagnosticBuilder {
+  // This is nodiscard to protect against accidentally building a diagnostic
+  // without emitting it.
+  class [[nodiscard]] DiagnosticBuilder {
    public:
     // DiagnosticBuilder is move-only and cannot be copied.
     DiagnosticBuilder(DiagnosticBuilder&&) noexcept = default;

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -147,6 +147,7 @@ CARBON_DIAGNOSTIC_KIND(MissingObjectInMethodCall)
 
 // Class checking.
 CARBON_DIAGNOSTIC_KIND(BaseIsFinal)
+CARBON_DIAGNOSTIC_KIND(BaseMissingExtend)
 CARBON_DIAGNOSTIC_KIND(BaseOutsideClass)
 CARBON_DIAGNOSTIC_KIND(BasePrevious)
 CARBON_DIAGNOSTIC_KIND(BaseRepeated)

+ 1 - 2
toolchain/lower/testdata/class/base.carbon

@@ -9,8 +9,7 @@ base class Base {
 }
 
 class Derived {
-  // TODO: `extend base: Base;`
-  base: Base;
+  extend base: Base;
 
   var d: i32;
 }

+ 16 - 0
toolchain/parse/handle_decl_scope_loop.cpp

@@ -41,6 +41,7 @@ static auto TokenIsModifierOrIntroducer(Lex::TokenKind token_kind) -> bool {
     case Lex::TokenKind::Class:
     case Lex::TokenKind::Constraint:
     case Lex::TokenKind::Default:
+    case Lex::TokenKind::Extend:
     case Lex::TokenKind::Final:
     case Lex::TokenKind::Fn:
     case Lex::TokenKind::Impl:
@@ -188,6 +189,21 @@ auto HandleDeclScopeLoop(Context& context) -> void {
         break;
       }
 
+      case Lex::TokenKind::Extend: {
+        // `extend` is considered a declaration modifier if it is followed by
+        // another modifier or an introducer.
+        if (TokenIsModifierOrIntroducer(
+                context.PositionKind(Lookahead::NextToken))) {
+          context.AddLeafNode(NodeKind::DeclModifierKeyword, context.Consume());
+          saw_modifier = true;
+        } else {
+          // TODO: Treat this `extend` token as a declaration introducer
+          HandleUnrecognizedDecl(context, state.subtree_start);
+          return;
+        }
+        break;
+      }
+
       // If we see a declaration introducer keyword token, replace the
       // placeholder node and switch to a state to parse the rest of the
       // declaration. We don't allow namespace or empty declarations here since

+ 1 - 0
toolchain/parse/node_kind.def

@@ -607,6 +607,7 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(DeclModifierKeyword, 0,
                                    CARBON_TOKEN(Abstract)
                                    CARBON_TOKEN(Base)
                                    CARBON_TOKEN(Default)
+                                   CARBON_TOKEN(Extend)
                                    CARBON_TOKEN(Final)
                                    CARBON_TOKEN(Impl)
                                    CARBON_TOKEN(Virtual))

+ 5 - 3
toolchain/parse/state.def

@@ -285,14 +285,16 @@ CARBON_PARSE_STATE(DeclNameAndParamsAfterImplicit)
 //
 // abstract
 // ^~~~~~~~
-// base
+// base class
 // ^~~~
 // default
 // ^~~~~~~
+// extend base
+// ^~~~~~
 // final
 // ^~~~~
-// override
-// ^~~~~~~~
+// impl fn
+// ^~~~
 // private
 // ^~~~~~~
 // protected

+ 15 - 3
toolchain/parse/testdata/class/base.carbon

@@ -7,8 +7,10 @@
 base class B {}
 
 class D {
-  base: B;
+  base: B1;
+  extend base: B2;
   base class Nested;
+  private base class PrivateNested;
 }
 
 // CHECK:STDOUT: - filename: base.carbon
@@ -24,12 +26,22 @@ class D {
 // CHECK:STDOUT:       {kind: 'ClassDefinitionStart', text: '{', subtree_size: 3},
 // CHECK:STDOUT:         {kind: 'BaseIntroducer', text: 'base'},
 // CHECK:STDOUT:         {kind: 'BaseColon', text: ':'},
-// CHECK:STDOUT:         {kind: 'NameExpr', text: 'B'},
+// CHECK:STDOUT:         {kind: 'NameExpr', text: 'B1'},
 // CHECK:STDOUT:       {kind: 'BaseDecl', text: ';', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'BaseIntroducer', text: 'base'},
+// CHECK:STDOUT:         {kind: 'DeclModifierKeyword', text: 'extend'},
+// CHECK:STDOUT:         {kind: 'BaseColon', text: ':'},
+// CHECK:STDOUT:         {kind: 'NameExpr', text: 'B2'},
+// CHECK:STDOUT:       {kind: 'BaseDecl', text: ';', subtree_size: 5},
 // CHECK:STDOUT:         {kind: 'ClassIntroducer', text: 'class'},
 // CHECK:STDOUT:         {kind: 'DeclModifierKeyword', text: 'base'},
 // CHECK:STDOUT:         {kind: 'Name', text: 'Nested'},
 // CHECK:STDOUT:       {kind: 'ClassDecl', text: ';', subtree_size: 4},
-// CHECK:STDOUT:     {kind: 'ClassDefinition', text: '}', subtree_size: 12},
+// CHECK:STDOUT:         {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:         {kind: 'AccessModifierKeyword', text: 'private'},
+// CHECK:STDOUT:         {kind: 'DeclModifierKeyword', text: 'base'},
+// CHECK:STDOUT:         {kind: 'Name', text: 'PrivateNested'},
+// CHECK:STDOUT:       {kind: 'ClassDecl', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'ClassDefinition', text: '}', subtree_size: 22},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]