Parcourir la source

Basic check support for adapters. (#3874)

No support is provided yet for converting between a type and an adapter
for that type.
Richard Smith il y a 2 ans
Parent
commit
28ceb4dd4e

+ 1 - 0
toolchain/check/context.cpp

@@ -950,6 +950,7 @@ class TypeCompleter {
   auto BuildValueRepr(SemIR::TypeId type_id, SemIR::Inst inst) const
       -> SemIR::ValueRepr {
     CARBON_KIND_SWITCH(inst) {
+      case SemIR::AdaptDecl::Kind:
       case SemIR::AddrOf::Kind:
       case SemIR::AddrPattern::Kind:
       case SemIR::ArrayIndex::Kind:

+ 1 - 0
toolchain/check/decl_state.h

@@ -66,6 +66,7 @@ struct DeclState {
   // The kind of declaration.
   enum DeclKind : int8_t {
     FileScope,
+    Adapt,
     Alias,
     Base,
     Class,

+ 1 - 0
toolchain/check/eval.cpp

@@ -901,6 +901,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     }
 
     // These cases are either not expressions or not constant.
+    case SemIR::AdaptDecl::Kind:
     case SemIR::AddrPattern::Kind:
     case SemIR::Assign::Kind:
     case SemIR::BindName::Kind:

+ 152 - 32
toolchain/check/handle_class.cpp

@@ -9,6 +9,17 @@
 
 namespace Carbon::Check {
 
+// If `type_id` is a class type, get its corresponding `SemIR::Class` object.
+// Otherwise returns `nullptr`.
+static auto TryGetAsClass(Context& context, SemIR::TypeId type_id)
+    -> SemIR::Class* {
+  auto class_type = context.types().TryGetAs<SemIR::ClassType>(type_id);
+  if (!class_type) {
+    return nullptr;
+  }
+  return &context.classes().Get(class_type->class_id);
+}
+
 auto HandleClassIntroducer(Context& context, Parse::ClassIntroducerId node_id)
     -> bool {
   // Create an instruction block to hold the instructions created as part of the
@@ -172,15 +183,113 @@ auto HandleClassDefinitionStart(Context& context,
   return true;
 }
 
-auto HandleAdaptIntroducer(Context& /*context*/,
+// Diagnoses a class-specific declaration appearing outside a class.
+static auto DiagnoseClassSpecificDeclOutsideClass(Context& context,
+                                                  SemIRLoc loc,
+                                                  Lex::TokenKind tok) -> void {
+  CARBON_DIAGNOSTIC(ClassSpecificDeclOutsideClass, Error,
+                    "`{0}` declaration can only be used in a class.",
+                    Lex::TokenKind);
+  context.emitter().Emit(loc, ClassSpecificDeclOutsideClass, tok);
+}
+
+// Returns the declaration of the immediately-enclosing class scope, or
+// diagonses if there isn't one.
+static auto GetEnclosingClassOrDiagnose(Context& context, SemIRLoc loc,
+                                        Lex::TokenKind tok)
+    -> std::optional<SemIR::ClassDecl> {
+  auto class_scope = context.GetCurrentScopeAs<SemIR::ClassDecl>();
+  if (!class_scope) {
+    DiagnoseClassSpecificDeclOutsideClass(context, loc, tok);
+  }
+  return class_scope;
+}
+
+// Diagnoses a class-specific declaration that is repeated within a class, but
+// is not permitted to be repeated.
+static auto DiagnoseClassSpecificDeclRepeated(Context& context,
+                                              SemIRLoc new_loc,
+                                              SemIRLoc prev_loc,
+                                              Lex::TokenKind tok) -> void {
+  CARBON_DIAGNOSTIC(ClassSpecificDeclRepeated, Error,
+                    "Multiple `{0}` declarations in class.{1}", Lex::TokenKind,
+                    std::string);
+  const llvm::StringRef extra = tok == Lex::TokenKind::Base
+                                    ? " Multiple inheritance is not permitted."
+                                    : "";
+  CARBON_DIAGNOSTIC(ClassSpecificDeclPrevious, Note,
+                    "Previous `{0}` declaration is here.", Lex::TokenKind);
+  context.emitter()
+      .Build(new_loc, ClassSpecificDeclRepeated, tok, extra.str())
+      .Note(prev_loc, ClassSpecificDeclPrevious, tok)
+      .Emit();
+}
+
+auto HandleAdaptIntroducer(Context& context,
                            Parse::AdaptIntroducerId /*node_id*/) -> bool {
+  context.decl_state_stack().Push(DeclState::Adapt);
   return true;
 }
 
-auto HandleAdaptDecl(Context& context, Parse::AdaptDeclId /*node_id*/) -> bool {
-  auto adapted_expr_id = context.node_stack().PopExpr();
-  (void)adapted_expr_id;
-  // TODO: Process the `adapt` declaration.
+auto HandleAdaptDecl(Context& context, Parse::AdaptDeclId node_id) -> bool {
+  auto [adapted_type_node, adapted_type_expr_id] =
+      context.node_stack().PopExprWithNodeId();
+
+  // Process modifiers. `extend` is permitted, no others are allowed.
+  LimitModifiersOnDecl(context, KeywordModifierSet::Extend,
+                       Lex::TokenKind::Adapt);
+  auto modifiers = context.decl_state_stack().innermost().modifier_set;
+  context.decl_state_stack().Pop(DeclState::Adapt);
+
+  auto enclosing_class_decl =
+      GetEnclosingClassOrDiagnose(context, node_id, Lex::TokenKind::Adapt);
+  if (!enclosing_class_decl) {
+    return true;
+  }
+
+  auto& class_info = context.classes().Get(enclosing_class_decl->class_id);
+  if (class_info.adapt_id.is_valid()) {
+    DiagnoseClassSpecificDeclRepeated(context, node_id, class_info.adapt_id,
+                                      Lex::TokenKind::Adapt);
+    return true;
+  }
+
+  auto adapted_type_id = ExprAsType(context, node_id, adapted_type_expr_id);
+  adapted_type_id = context.AsCompleteType(adapted_type_id, [&] {
+    CARBON_DIAGNOSTIC(IncompleteTypeInAdaptDecl, Error,
+                      "Adapted type `{0}` is an incomplete type.",
+                      SemIR::TypeId);
+    return context.emitter().Build(node_id, IncompleteTypeInAdaptDecl,
+                                   adapted_type_id);
+  });
+
+  // Build a SemIR representation for the declaration.
+  class_info.adapt_id =
+      context.AddInst({node_id, SemIR::AdaptDecl{adapted_type_id}});
+
+  // Extend the class scope with the adapted type's scope if requested.
+  if (!!(modifiers & KeywordModifierSet::Extend)) {
+    auto extended_scope_id = SemIR::NameScopeId::Invalid;
+    if (adapted_type_id == SemIR::TypeId::Error) {
+      // Recover by not extending any scope. We instead set has_error to true
+      // below.
+    } else if (auto* adapted_class_info =
+                   TryGetAsClass(context, adapted_type_id)) {
+      extended_scope_id = adapted_class_info->scope_id;
+      CARBON_CHECK(adapted_class_info->scope_id.is_valid())
+          << "Complete class should have a scope";
+    } else {
+      // TODO: Accept any type that has a scope.
+      context.TODO(node_id, "extending non-class type");
+    }
+
+    auto& class_scope = context.name_scopes().Get(class_info.scope_id);
+    if (extended_scope_id.is_valid()) {
+      class_scope.extended_scopes.push_back(extended_scope_id);
+    } else {
+      class_scope.has_error = true;
+    }
+  }
   return true;
 }
 
@@ -208,17 +317,6 @@ constexpr BaseInfo BaseInfo::Error = {.type_id = SemIR::TypeId::Error,
                                       .scope_id = SemIR::NameScopeId::Invalid};
 }  // namespace
 
-// If `type_id` is a class type, get its corresponding `SemIR::Class` object.
-// Otherwise returns `nullptr`.
-static auto TryGetAsClass(Context& context, SemIR::TypeId type_id)
-    -> SemIR::Class* {
-  auto class_type = context.types().TryGetAs<SemIR::ClassType>(type_id);
-  if (!class_type) {
-    return nullptr;
-  }
-  return &context.classes().Get(class_type->class_id);
-}
-
 // Diagnoses an attempt to derive from a final type.
 static auto DiagnoseBaseIsFinal(Context& context, Parse::NodeId node_id,
                                 SemIR::TypeId base_type_id) -> void {
@@ -268,7 +366,7 @@ auto HandleBaseDecl(Context& context, Parse::BaseDeclId node_id) -> bool {
   auto [base_type_node_id, base_type_expr_id] =
       context.node_stack().PopExprWithNodeId();
 
-  // Process modifiers. `extend` is required, none others are allowed.
+  // Process modifiers. `extend` is required, no others are allowed.
   LimitModifiersOnDecl(context, KeywordModifierSet::Extend,
                        Lex::TokenKind::Base);
   auto modifiers = context.decl_state_stack().innermost().modifier_set;
@@ -279,25 +377,16 @@ auto HandleBaseDecl(Context& context, Parse::BaseDeclId node_id) -> bool {
   }
   context.decl_state_stack().Pop(DeclState::Base);
 
-  auto enclosing_class_decl = context.GetCurrentScopeAs<SemIR::ClassDecl>();
+  auto enclosing_class_decl =
+      GetEnclosingClassOrDiagnose(context, node_id, Lex::TokenKind::Base);
   if (!enclosing_class_decl) {
-    CARBON_DIAGNOSTIC(BaseOutsideClass, Error,
-                      "`base` declaration can only be used in a class.");
-    context.emitter().Emit(node_id, BaseOutsideClass);
     return true;
   }
 
   auto& class_info = context.classes().Get(enclosing_class_decl->class_id);
   if (class_info.base_id.is_valid()) {
-    CARBON_DIAGNOSTIC(BaseRepeated, Error,
-                      "Multiple `base` declarations in class. Multiple "
-                      "inheritance is not permitted.");
-    CARBON_DIAGNOSTIC(BasePrevious, Note,
-                      "Previous `base` declaration is here.");
-    context.emitter()
-        .Build(node_id, BaseRepeated)
-        .Note(class_info.base_id, BasePrevious)
-        .Emit();
+    DiagnoseClassSpecificDeclRepeated(context, node_id, class_info.base_id,
+                                      Lex::TokenKind::Base);
     return true;
   }
 
@@ -316,6 +405,7 @@ auto HandleBaseDecl(Context& context, Parse::BaseDeclId node_id) -> bool {
 
   // Add a corresponding field to the object representation of the class.
   // TODO: Consider whether we want to use `partial T` here.
+  // TODO: Should we diagnose if there are already any fields?
   context.args_type_info_stack().AddInstId(context.AddInstInNoBlock(
       {node_id,
        SemIR::StructTypeField{SemIR::NameId::Base, base_info.type_id}}));
@@ -345,9 +435,39 @@ auto HandleClassDefinition(Context& context,
       context.node_stack().Pop<Parse::NodeKind::ClassDefinitionStart>();
   context.inst_block_stack().Pop();
 
-  // The class type is now fully defined.
+  // The class type is now fully defined. Compute its object representation.
   auto& class_info = context.classes().Get(class_id);
-  class_info.object_repr_id = context.GetStructType(fields_id);
+  if (class_info.adapt_id.is_valid()) {
+    class_info.object_repr_id = SemIR::TypeId::Error;
+    if (class_info.base_id.is_valid()) {
+      CARBON_DIAGNOSTIC(AdaptWithBase, Error,
+                        "Adapter cannot have a base class.");
+      CARBON_DIAGNOSTIC(AdaptBaseHere, Note, "`base` declaration is here.");
+      context.emitter()
+          .Build(class_info.adapt_id, AdaptWithBase)
+          .Note(class_info.base_id, AdaptBaseHere)
+          .Emit();
+    } else if (!context.inst_blocks().Get(fields_id).empty()) {
+      auto first_field_id = context.inst_blocks().Get(fields_id).front();
+      CARBON_DIAGNOSTIC(AdaptWithFields, Error, "Adapter cannot have fields.");
+      CARBON_DIAGNOSTIC(AdaptFieldHere, Note,
+                        "First field declaration is here.");
+      context.emitter()
+          .Build(class_info.adapt_id, AdaptWithFields)
+          .Note(first_field_id, AdaptFieldHere)
+          .Emit();
+    } else {
+      // The object representation of the adapter is the adapted type.
+      // TODO: If the adapted type is a class, should we use its object
+      // representation type instead?
+      class_info.object_repr_id =
+          context.insts()
+              .GetAs<SemIR::AdaptDecl>(class_info.adapt_id)
+              .adapted_type_id;
+    }
+  } else {
+    class_info.object_repr_id = context.GetStructType(fields_id);
+  }
 
   // The decl_name_stack and scopes are popped by `ProcessNodeIds`.
   return true;

+ 146 - 0
toolchain/check/testdata/class/adapt.carbon

@@ -0,0 +1,146 @@
+// 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
+
+// --- basic.carbon
+
+library "basic" api;
+
+class SomeClass {
+  var a: i32;
+  var b: i32;
+}
+
+class SomeClassAdapter {
+  adapt SomeClass;
+}
+
+class StructAdapter {
+  adapt {.a: i32, .b: i32};
+}
+
+// --- fail_not_extend.carbon
+
+library "fail_not_extend" api;
+
+class Adapted {
+  fn F();
+}
+
+class AdaptNotExtend {
+  adapt Adapted;
+}
+
+fn F(a: AdaptNotExtend) {
+  // `Adapted` is not extended, so lookup for `F` finds nothing.
+  // CHECK:STDERR: fail_not_extend.carbon:[[@LINE+3]]:3: ERROR: Name `F` not found.
+  // CHECK:STDERR:   a.F();
+  // CHECK:STDERR:   ^~~
+  a.F();
+}
+
+// CHECK:STDOUT: --- basic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %SomeClass: type = class_type @SomeClass [template]
+// CHECK:STDOUT:   %.1: type = unbound_element_type SomeClass, i32 [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %SomeClassAdapter: type = class_type @SomeClassAdapter [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %StructAdapter: type = class_type @StructAdapter [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .SomeClass = %SomeClass.decl
+// CHECK:STDOUT:     .SomeClassAdapter = %SomeClassAdapter.decl
+// CHECK:STDOUT:     .StructAdapter = %StructAdapter.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %SomeClass.decl: type = class_decl @SomeClass [template = constants.%SomeClass] {}
+// CHECK:STDOUT:   %SomeClassAdapter.decl: type = class_decl @SomeClassAdapter [template = constants.%SomeClassAdapter] {}
+// CHECK:STDOUT:   %StructAdapter.decl: type = class_decl @StructAdapter [template = constants.%StructAdapter] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @SomeClass {
+// CHECK:STDOUT:   %.loc5: <unbound element of class SomeClass> = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc6: <unbound element of class SomeClass> = field_decl b, element1 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%SomeClass
+// CHECK:STDOUT:   .a = %.loc5
+// CHECK:STDOUT:   .b = %.loc6
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @SomeClassAdapter {
+// CHECK:STDOUT:   %SomeClass.ref: type = name_ref SomeClass, file.%SomeClass.decl [template = constants.%SomeClass]
+// CHECK:STDOUT:   adapt_decl SomeClass
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%SomeClassAdapter
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @StructAdapter {
+// CHECK:STDOUT:   %.loc14: type = struct_type {.a: i32, .b: i32} [template = constants.%.2]
+// CHECK:STDOUT:   adapt_decl {.a: i32, .b: i32}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%StructAdapter
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_not_extend.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Adapted: type = class_type @Adapted [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %AdaptNotExtend: type = class_type @AdaptNotExtend [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: type = ptr_type Adapted [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Adapted = %Adapted.decl
+// CHECK:STDOUT:     .AdaptNotExtend = %AdaptNotExtend.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Adapted.decl: type = class_decl @Adapted [template = constants.%Adapted] {}
+// CHECK:STDOUT:   %AdaptNotExtend.decl: type = class_decl @AdaptNotExtend [template = constants.%AdaptNotExtend] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {
+// CHECK:STDOUT:     %AdaptNotExtend.ref: type = name_ref AdaptNotExtend, %AdaptNotExtend.decl [template = constants.%AdaptNotExtend]
+// CHECK:STDOUT:     %a.loc12_6.1: AdaptNotExtend = param a
+// CHECK:STDOUT:     @F.2.%a: AdaptNotExtend = bind_name a, %a.loc12_6.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Adapted {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template] {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Adapted
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @AdaptNotExtend {
+// CHECK:STDOUT:   %Adapted.ref: type = name_ref Adapted, file.%Adapted.decl [template = constants.%Adapted]
+// CHECK:STDOUT:   adapt_decl Adapted
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%AdaptNotExtend
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2(%a: AdaptNotExtend) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %a.ref: AdaptNotExtend = name_ref a, %a
+// CHECK:STDOUT:   %F.ref: <error> = name_ref F, <error> [template = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 314 - 0
toolchain/check/testdata/class/extend_adapt.carbon

@@ -0,0 +1,314 @@
+// 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
+
+// --- basic.carbon
+
+library "basic" api;
+
+class SomeClassAdapter;
+
+class SomeClass {
+  var a: i32;
+  var b: i32;
+
+  fn StaticMemberFunction();
+
+  fn AdapterMethod[self: SomeClassAdapter]();
+}
+
+class SomeClassAdapter {
+  extend adapt SomeClass;
+}
+
+fn TestStaticMemberFunction(a: SomeClassAdapter) {
+  a.StaticMemberFunction();
+}
+
+fn TestAdapterMethod(a: SomeClassAdapter) {
+  a.AdapterMethod();
+}
+
+// --- fail_todo_method_access.carbon
+
+library "fail_todo_method_access" api;
+
+class SomeClass {
+  fn F[self: Self]();
+}
+
+class SomeClassAdapter {
+  extend adapt SomeClass;
+}
+
+fn F(a: SomeClassAdapter) {
+  // CHECK:STDERR: fail_todo_method_access.carbon:[[@LINE+7]]:3: ERROR: Cannot implicitly convert from `SomeClassAdapter` to `SomeClass`.
+  // CHECK:STDERR:   a.F();
+  // CHECK:STDERR:   ^~~~
+  // CHECK:STDERR: fail_todo_method_access.carbon:[[@LINE-11]]:8: Initializing `self` parameter of method declared here.
+  // CHECK:STDERR:   fn F[self: Self]();
+  // CHECK:STDERR:        ^~~~
+  // CHECK:STDERR:
+  a.F();
+}
+
+// --- fail_todo_field_access.carbon
+
+library "fail_todo_field_access" api;
+
+class SomeClass {
+  var a: i32;
+  var b: i32;
+}
+
+class SomeClassAdapter {
+  extend adapt SomeClass;
+}
+
+fn F(a: SomeClassAdapter) -> i32 {
+  // CHECK:STDERR: fail_todo_field_access.carbon:[[@LINE+4]]:10: ERROR: Cannot implicitly convert from `SomeClassAdapter` to `SomeClass`.
+  // CHECK:STDERR:   return a.b;
+  // CHECK:STDERR:          ^~~
+  // CHECK:STDERR:
+  return a.b;
+}
+
+// --- fail_todo_adapt_non_class.carbon
+
+library "fail_todo_adapt_non_class" api;
+
+class StructAdapter {
+  // CHECK:STDERR: fail_todo_adapt_non_class.carbon:[[@LINE+3]]:3: ERROR: Semantics TODO: `extending non-class type`.
+  // CHECK:STDERR:   extend adapt {.a: i32, .b: i32};
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  extend adapt {.a: i32, .b: i32};
+}
+
+// CHECK:STDOUT: --- basic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %SomeClassAdapter: type = class_type @SomeClassAdapter [template]
+// CHECK:STDOUT:   %SomeClass: type = class_type @SomeClass [template]
+// CHECK:STDOUT:   %.1: type = unbound_element_type SomeClass, i32 [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %.4: type = ptr_type SomeClass [template]
+// CHECK:STDOUT:   %.5: type = tuple_type () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .SomeClassAdapter = %SomeClassAdapter.decl.loc4
+// CHECK:STDOUT:     .SomeClass = %SomeClass.decl
+// CHECK:STDOUT:     .TestStaticMemberFunction = %TestStaticMemberFunction
+// CHECK:STDOUT:     .TestAdapterMethod = %TestAdapterMethod
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %SomeClassAdapter.decl.loc4: type = class_decl @SomeClassAdapter [template = constants.%SomeClassAdapter] {}
+// CHECK:STDOUT:   %SomeClass.decl: type = class_decl @SomeClass [template = constants.%SomeClass] {}
+// CHECK:STDOUT:   %SomeClassAdapter.decl.loc15: type = class_decl @SomeClassAdapter [template = constants.%SomeClassAdapter] {}
+// CHECK:STDOUT:   %TestStaticMemberFunction: <function> = fn_decl @TestStaticMemberFunction [template] {
+// CHECK:STDOUT:     %SomeClassAdapter.ref.loc19: type = name_ref SomeClassAdapter, %SomeClassAdapter.decl.loc4 [template = constants.%SomeClassAdapter]
+// CHECK:STDOUT:     %a.loc19_29.1: SomeClassAdapter = param a
+// CHECK:STDOUT:     @TestStaticMemberFunction.%a: SomeClassAdapter = bind_name a, %a.loc19_29.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %TestAdapterMethod: <function> = fn_decl @TestAdapterMethod [template] {
+// CHECK:STDOUT:     %SomeClassAdapter.ref.loc23: type = name_ref SomeClassAdapter, %SomeClassAdapter.decl.loc4 [template = constants.%SomeClassAdapter]
+// CHECK:STDOUT:     %a.loc23_22.1: SomeClassAdapter = param a
+// CHECK:STDOUT:     @TestAdapterMethod.%a: SomeClassAdapter = bind_name a, %a.loc23_22.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @SomeClassAdapter {
+// CHECK:STDOUT:   %SomeClass.ref: type = name_ref SomeClass, file.%SomeClass.decl [template = constants.%SomeClass]
+// CHECK:STDOUT:   adapt_decl SomeClass
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%SomeClassAdapter
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @SomeClass {
+// CHECK:STDOUT:   %.loc7: <unbound element of class SomeClass> = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc8: <unbound element of class SomeClass> = field_decl b, element1 [template]
+// CHECK:STDOUT:   %StaticMemberFunction: <function> = fn_decl @StaticMemberFunction [template] {}
+// CHECK:STDOUT:   %AdapterMethod: <function> = fn_decl @AdapterMethod [template] {
+// CHECK:STDOUT:     %SomeClassAdapter.ref: type = name_ref SomeClassAdapter, file.%SomeClassAdapter.decl.loc4 [template = constants.%SomeClassAdapter]
+// CHECK:STDOUT:     %self.loc12_20.1: SomeClassAdapter = param self
+// CHECK:STDOUT:     %self.loc12_20.2: SomeClassAdapter = bind_name self, %self.loc12_20.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%SomeClass
+// CHECK:STDOUT:   .a = %.loc7
+// CHECK:STDOUT:   .b = %.loc8
+// CHECK:STDOUT:   .StaticMemberFunction = %StaticMemberFunction
+// CHECK:STDOUT:   .AdapterMethod = %AdapterMethod
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @StaticMemberFunction();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @AdapterMethod[@SomeClass.%self.loc12_20.2: SomeClassAdapter]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @TestStaticMemberFunction(%a: SomeClassAdapter) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %a.ref: SomeClassAdapter = name_ref a, %a
+// CHECK:STDOUT:   %StaticMemberFunction.ref: <function> = name_ref StaticMemberFunction, @SomeClass.%StaticMemberFunction [template = @SomeClass.%StaticMemberFunction]
+// CHECK:STDOUT:   %.loc20: init () = call %StaticMemberFunction.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @TestAdapterMethod(%a: SomeClassAdapter) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %a.ref: SomeClassAdapter = name_ref a, %a
+// CHECK:STDOUT:   %AdapterMethod.ref: <function> = name_ref AdapterMethod, @SomeClass.%AdapterMethod [template = @SomeClass.%AdapterMethod]
+// CHECK:STDOUT:   %.loc24_4: <bound method> = bound_method %a.ref, %AdapterMethod.ref
+// CHECK:STDOUT:   %.loc24_18: init () = call %.loc24_4(%a.ref)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_method_access.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %SomeClass: type = class_type @SomeClass [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %SomeClassAdapter: type = class_type @SomeClassAdapter [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: type = ptr_type SomeClass [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .SomeClass = %SomeClass.decl
+// CHECK:STDOUT:     .SomeClassAdapter = %SomeClassAdapter.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %SomeClass.decl: type = class_decl @SomeClass [template = constants.%SomeClass] {}
+// CHECK:STDOUT:   %SomeClassAdapter.decl: type = class_decl @SomeClassAdapter [template = constants.%SomeClassAdapter] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template] {
+// CHECK:STDOUT:     %SomeClassAdapter.ref: type = name_ref SomeClassAdapter, %SomeClassAdapter.decl [template = constants.%SomeClassAdapter]
+// CHECK:STDOUT:     %a.loc12_6.1: SomeClassAdapter = param a
+// CHECK:STDOUT:     @F.2.%a: SomeClassAdapter = bind_name a, %a.loc12_6.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @SomeClass {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template] {
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%SomeClass [template = constants.%SomeClass]
+// CHECK:STDOUT:     %self.loc5_8.1: SomeClass = param self
+// CHECK:STDOUT:     %self.loc5_8.2: SomeClass = bind_name self, %self.loc5_8.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%SomeClass
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @SomeClassAdapter {
+// CHECK:STDOUT:   %SomeClass.ref: type = name_ref SomeClass, file.%SomeClass.decl [template = constants.%SomeClass]
+// CHECK:STDOUT:   adapt_decl SomeClass
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%SomeClassAdapter
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1[@SomeClass.%self.loc5_8.2: SomeClass]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2(%a: SomeClassAdapter) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %a.ref: SomeClassAdapter = name_ref a, %a
+// CHECK:STDOUT:   %F.ref: <function> = name_ref F, @SomeClass.%F [template = @SomeClass.%F]
+// CHECK:STDOUT:   %.loc20_4: <bound method> = bound_method %a.ref, %F.ref
+// CHECK:STDOUT:   %.loc20_6: init () = call %.loc20_4(<invalid>) [template = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_field_access.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %SomeClass: type = class_type @SomeClass [template]
+// CHECK:STDOUT:   %.1: type = unbound_element_type SomeClass, i32 [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %SomeClassAdapter: type = class_type @SomeClassAdapter [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %.4: type = ptr_type SomeClass [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .SomeClass = %SomeClass.decl
+// CHECK:STDOUT:     .SomeClassAdapter = %SomeClassAdapter.decl
+// CHECK:STDOUT:     .F = %F
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %SomeClass.decl: type = class_decl @SomeClass [template = constants.%SomeClass] {}
+// CHECK:STDOUT:   %SomeClassAdapter.decl: type = class_decl @SomeClassAdapter [template = constants.%SomeClassAdapter] {}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
+// CHECK:STDOUT:     %SomeClassAdapter.ref: type = name_ref SomeClassAdapter, %SomeClassAdapter.decl [template = constants.%SomeClassAdapter]
+// CHECK:STDOUT:     %a.loc13_6.1: SomeClassAdapter = param a
+// CHECK:STDOUT:     @F.%a: SomeClassAdapter = bind_name a, %a.loc13_6.1
+// CHECK:STDOUT:     %return.var: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @SomeClass {
+// CHECK:STDOUT:   %.loc5: <unbound element of class SomeClass> = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc6: <unbound element of class SomeClass> = field_decl b, element1 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%SomeClass
+// CHECK:STDOUT:   .a = %.loc5
+// CHECK:STDOUT:   .b = %.loc6
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @SomeClassAdapter {
+// CHECK:STDOUT:   %SomeClass.ref: type = name_ref SomeClass, file.%SomeClass.decl [template = constants.%SomeClass]
+// CHECK:STDOUT:   adapt_decl SomeClass
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%SomeClassAdapter
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%a: SomeClassAdapter) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %a.ref: SomeClassAdapter = name_ref a, %a
+// CHECK:STDOUT:   %b.ref: <unbound element of class SomeClass> = name_ref b, @SomeClass.%.loc6 [template = @SomeClass.%.loc6]
+// CHECK:STDOUT:   %.loc18: i32 = class_element_access <error>, element1 [template = <error>]
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_adapt_non_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %StructAdapter: type = class_type @StructAdapter [template]
+// CHECK:STDOUT:   %.1: type = struct_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %.2: type = ptr_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .StructAdapter = %StructAdapter.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %StructAdapter.decl: type = class_decl @StructAdapter [template = constants.%StructAdapter] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @StructAdapter {
+// CHECK:STDOUT:   %.loc8: type = struct_type {.a: i32, .b: i32} [template = constants.%.1]
+// CHECK:STDOUT:   adapt_decl {.a: i32, .b: i32}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%StructAdapter
+// CHECK:STDOUT:   has_error
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 256 - 0
toolchain/check/testdata/class/fail_adapt_bad_decl.carbon

@@ -0,0 +1,256 @@
+// 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
+
+// --- fail_not_type.carbon
+
+library "not_type" api;
+
+class Bad {
+  // CHECK:STDERR: fail_not_type.carbon:[[@LINE+4]]:3: ERROR: Cannot implicitly convert from `i32` to `type`.
+  // CHECK:STDERR:   adapt 100;
+  // CHECK:STDERR:   ^~~~~~~~~~
+  // CHECK:STDERR:
+  adapt 100;
+}
+
+// CHECK:STDERR: fail_not_type.carbon:[[@LINE+4]]:18: ERROR: Name `F` not found.
+// CHECK:STDERR: fn Use(b: Bad) { b.F(); }
+// CHECK:STDERR:                  ^~~
+// CHECK:STDERR:
+fn Use(b: Bad) { b.F(); }
+
+// --- fail_extend_not_type.carbon
+
+library "extend_not_type" api;
+
+class Bad {
+  // CHECK:STDERR: fail_extend_not_type.carbon:[[@LINE+4]]:3: ERROR: Cannot implicitly convert from `i32` to `type`.
+  // CHECK:STDERR:   extend adapt 100;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  extend adapt 100;
+}
+
+// No diagnostic here, we don't know what names Bad has.
+fn Use(b: Bad) { b.F(); }
+
+// --- fail_repeated.carbon
+
+library "fail_repeated" api;
+
+class MultipleAdapts {
+  adapt ();
+  // CHECK:STDERR: fail_repeated.carbon:[[@LINE+7]]:3: ERROR: Multiple `adapt` declarations in class.
+  // CHECK:STDERR:   adapt {};
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_repeated.carbon:[[@LINE-4]]:3: Previous `adapt` declaration is here.
+  // CHECK:STDERR:   adapt ();
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR:
+  adapt {};
+}
+
+class MultipleAdaptsSameType {
+  adapt ();
+  // CHECK:STDERR: fail_repeated.carbon:[[@LINE+7]]:3: ERROR: Multiple `adapt` declarations in class.
+  // CHECK:STDERR:   adapt ();
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_repeated.carbon:[[@LINE-4]]:3: Previous `adapt` declaration is here.
+  // CHECK:STDERR:   adapt ();
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR:
+  adapt ();
+}
+
+// --- fail_bad_scope.carbon
+
+library "fail_bad_scope" api;
+
+// CHECK:STDERR: fail_bad_scope.carbon:[[@LINE+4]]:1: ERROR: `adapt` declaration can only be used in a class.
+// CHECK:STDERR: adapt {};
+// CHECK:STDERR: ^~~~~~~~~
+// CHECK:STDERR:
+adapt {};
+
+interface I {
+  // CHECK:STDERR: fail_bad_scope.carbon:[[@LINE+4]]:3: ERROR: `adapt` declaration can only be used in a class.
+  // CHECK:STDERR:   adapt {};
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR:
+  adapt {};
+}
+
+class C {
+  interface I {
+    // CHECK:STDERR: fail_bad_scope.carbon:[[@LINE+3]]:5: ERROR: `adapt` declaration can only be used in a class.
+    // CHECK:STDERR:     adapt {};
+    // CHECK:STDERR:     ^~~~~~~~~
+    adapt {};
+  }
+}
+
+// CHECK:STDOUT: --- fail_not_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bad: type = class_type @Bad [template]
+// CHECK:STDOUT:   %.1: i32 = int_literal 100 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Bad = %Bad.decl
+// CHECK:STDOUT:     .Use = %Use
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Bad.decl: type = class_decl @Bad [template = constants.%Bad] {}
+// CHECK:STDOUT:   %Use: <function> = fn_decl @Use [template] {
+// CHECK:STDOUT:     %Bad.ref: type = name_ref Bad, %Bad.decl [template = constants.%Bad]
+// CHECK:STDOUT:     %b.loc16_8.1: Bad = param b
+// CHECK:STDOUT:     @Use.%b: Bad = bind_name b, %b.loc16_8.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Bad {
+// CHECK:STDOUT:   %.loc9: i32 = int_literal 100 [template = constants.%.1]
+// CHECK:STDOUT:   adapt_decl <error>
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Bad
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use(%b: Bad) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %b.ref: Bad = name_ref b, %b
+// CHECK:STDOUT:   %F.ref: <error> = name_ref F, <error> [template = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_extend_not_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bad: type = class_type @Bad [template]
+// CHECK:STDOUT:   %.1: i32 = int_literal 100 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Bad = %Bad.decl
+// CHECK:STDOUT:     .Use = %Use
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Bad.decl: type = class_decl @Bad [template = constants.%Bad] {}
+// CHECK:STDOUT:   %Use: <function> = fn_decl @Use [template] {
+// CHECK:STDOUT:     %Bad.ref: type = name_ref Bad, %Bad.decl [template = constants.%Bad]
+// CHECK:STDOUT:     %b.loc13_8.1: Bad = param b
+// CHECK:STDOUT:     @Use.%b: Bad = bind_name b, %b.loc13_8.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Bad {
+// CHECK:STDOUT:   %.loc9: i32 = int_literal 100 [template = constants.%.1]
+// CHECK:STDOUT:   adapt_decl <error>
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Bad
+// CHECK:STDOUT:   has_error
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use(%b: Bad) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %b.ref: Bad = name_ref b, %b
+// CHECK:STDOUT:   %F.ref: <error> = name_ref F, <error> [template = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_repeated.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MultipleAdapts: type = class_type @MultipleAdapts [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %MultipleAdaptsSameType: type = class_type @MultipleAdaptsSameType [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .MultipleAdapts = %MultipleAdapts.decl
+// CHECK:STDOUT:     .MultipleAdaptsSameType = %MultipleAdaptsSameType.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %MultipleAdapts.decl: type = class_decl @MultipleAdapts [template = constants.%MultipleAdapts] {}
+// CHECK:STDOUT:   %MultipleAdaptsSameType.decl: type = class_decl @MultipleAdaptsSameType [template = constants.%MultipleAdaptsSameType] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @MultipleAdapts {
+// CHECK:STDOUT:   %.loc5_10.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc5_10.2: type = converted %.loc5_10.1, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:   adapt_decl ()
+// CHECK:STDOUT:   %.loc13: {} = struct_literal ()
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%MultipleAdapts
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @MultipleAdaptsSameType {
+// CHECK:STDOUT:   %.loc17_10.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc17_10.2: type = converted %.loc17_10.1, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:   adapt_decl ()
+// CHECK:STDOUT:   %.loc25: () = tuple_literal ()
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%MultipleAdaptsSameType
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_bad_scope.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: type = interface_type @I.1 [template]
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.3: type = interface_type @I.2 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .I = %I.decl
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %.loc8: {} = struct_literal ()
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I.1 [template = constants.%.2] {}
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I.1 {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:   %.loc15: {} = struct_literal ()
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I.2 {
+// CHECK:STDOUT:   %Self: I = bind_symbolic_name Self [symbolic]
+// CHECK:STDOUT:   %.loc23: {} = struct_literal ()
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I.2 [template = constants.%.3] {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   .I = %I.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 50 - 0
toolchain/check/testdata/class/fail_adapt_bad_type.carbon

@@ -0,0 +1,50 @@
+// 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
+
+// --- fail_incomplete_type.carbon
+
+library "incomplete_type" api;
+
+class Incomplete;
+
+class AdaptIncomplete {
+  // CHECK:STDERR: fail_incomplete_type.carbon:[[@LINE+6]]:3: ERROR: Adapted type `Incomplete` is an incomplete type.
+  // CHECK:STDERR:   adapt Incomplete;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_incomplete_type.carbon:[[@LINE-6]]:1: Class was forward declared here.
+  // CHECK:STDERR: class Incomplete;
+  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~
+  adapt Incomplete;
+}
+
+// CHECK:STDOUT: --- fail_incomplete_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Incomplete: type = class_type @Incomplete [template]
+// CHECK:STDOUT:   %AdaptIncomplete: type = class_type @AdaptIncomplete [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Incomplete = %Incomplete.decl
+// CHECK:STDOUT:     .AdaptIncomplete = %AdaptIncomplete.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Incomplete.decl: type = class_decl @Incomplete [template = constants.%Incomplete] {}
+// CHECK:STDOUT:   %AdaptIncomplete.decl: type = class_decl @AdaptIncomplete [template = constants.%AdaptIncomplete] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Incomplete;
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @AdaptIncomplete {
+// CHECK:STDOUT:   %Incomplete.ref: type = name_ref Incomplete, file.%Incomplete.decl [template = constants.%Incomplete]
+// CHECK:STDOUT:   adapt_decl <error>
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%AdaptIncomplete
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 133 - 0
toolchain/check/testdata/class/fail_adapt_modifiers.carbon

@@ -0,0 +1,133 @@
+// 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
+
+class B {}
+
+class C1 {
+  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+4]]:3: ERROR: `private` not allowed on `adapt` declaration.
+  // CHECK:STDERR:   private adapt B;
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  private adapt B;
+}
+
+class C2 {
+  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+4]]:3: ERROR: `abstract` not allowed on `adapt` declaration.
+  // CHECK:STDERR:   abstract adapt B;
+  // CHECK:STDERR:   ^~~~~~~~
+  // CHECK:STDERR:
+  abstract adapt B;
+}
+
+class C3 {
+  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+4]]:3: ERROR: `default` not allowed on `adapt` declaration.
+  // CHECK:STDERR:   default adapt B;
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  default adapt B;
+}
+
+class C4 {
+  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+7]]:10: ERROR: `extend` repeated on declaration.
+  // CHECK:STDERR:   extend extend adapt B;
+  // CHECK:STDERR:          ^~~~~~
+  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+4]]:3: `extend` previously appeared here.
+  // CHECK:STDERR:   extend extend adapt B;
+  // CHECK:STDERR:   ^~~~~~
+  // CHECK:STDERR:
+  extend extend adapt B;
+}
+
+class C5 {
+  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+6]]:10: ERROR: `base` not allowed on declaration with `extend`.
+  // CHECK:STDERR:   extend base adapt B;
+  // CHECK:STDERR:          ^~~~
+  // CHECK:STDERR: fail_adapt_modifiers.carbon:[[@LINE+3]]:3: `extend` previously appeared here.
+  // CHECK:STDERR:   extend base adapt B;
+  // CHECK:STDERR:   ^~~~~~
+  extend base adapt B;
+}
+
+// CHECK:STDOUT: --- fail_adapt_modifiers.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %B: type = class_type @B [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %C1: type = class_type @C1 [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %C2: type = class_type @C2 [template]
+// CHECK:STDOUT:   %C3: type = class_type @C3 [template]
+// CHECK:STDOUT:   %C4: type = class_type @C4 [template]
+// CHECK:STDOUT:   %C5: type = class_type @C5 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %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: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %B.decl: type = class_decl @B [template = constants.%B] {}
+// CHECK:STDOUT:   %C1.decl: type = class_decl @C1 [template = constants.%C1] {}
+// CHECK:STDOUT:   %C2.decl: type = class_decl @C2 [template = constants.%C2] {}
+// CHECK:STDOUT:   %C3.decl: type = class_decl @C3 [template = constants.%C3] {}
+// CHECK:STDOUT:   %C4.decl: type = class_decl @C4 [template = constants.%C4] {}
+// CHECK:STDOUT:   %C5.decl: type = class_decl @C5 [template = constants.%C5] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// 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 [template = constants.%B]
+// CHECK:STDOUT:   adapt_decl B
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C2 {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
+// CHECK:STDOUT:   adapt_decl B
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C3 {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
+// CHECK:STDOUT:   adapt_decl B
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C4 {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
+// CHECK:STDOUT:   adapt_decl B
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C4
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C5 {
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
+// CHECK:STDOUT:   adapt_decl B
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C5
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 195 - 0
toolchain/check/testdata/class/fail_adapt_with_subobjects.carbon

@@ -0,0 +1,195 @@
+// 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
+
+// --- fail_adapt_with_base.carbon
+
+library "fail_adapt_with_base" api;
+
+base class Base {}
+
+class AdaptWithBase {
+  // CHECK:STDERR: fail_adapt_with_base.carbon:[[@LINE+3]]:3: ERROR: Adapter cannot have a base class.
+  // CHECK:STDERR:   adapt i32;
+  // CHECK:STDERR:   ^~~~~~~~~~
+  adapt i32;
+  // CHECK:STDERR: fail_adapt_with_base.carbon:[[@LINE+4]]:3: `base` declaration is here.
+  // CHECK:STDERR:   extend base: Base;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  extend base: Base;
+}
+
+// --- fail_adapt_with_fields.carbon
+
+library "fail_adapt_with_fields" api;
+
+class AdaptWithField {
+  // CHECK:STDERR: fail_adapt_with_fields.carbon:[[@LINE+3]]:3: ERROR: Adapter cannot have fields.
+  // CHECK:STDERR:   adapt i32;
+  // CHECK:STDERR:   ^~~~~~~~~~
+  adapt i32;
+  // CHECK:STDERR: fail_adapt_with_fields.carbon:[[@LINE+4]]:7: First field declaration is here.
+  // CHECK:STDERR:   var n: i32;
+  // CHECK:STDERR:       ^~~~~~
+  // CHECK:STDERR:
+  var n: i32;
+}
+
+class AdaptWithFields {
+  // CHECK:STDERR: fail_adapt_with_fields.carbon:[[@LINE+3]]:3: ERROR: Adapter cannot have fields.
+  // CHECK:STDERR:   adapt i32;
+  // CHECK:STDERR:   ^~~~~~~~~~
+  adapt i32;
+  // CHECK:STDERR: fail_adapt_with_fields.carbon:[[@LINE+4]]:7: First field declaration is here.
+  // CHECK:STDERR:   var a: i32;
+  // CHECK:STDERR:       ^~~~~~
+  // CHECK:STDERR:
+  var a: i32;
+  var b: i32;
+  var c: i32;
+}
+
+// --- fail_adapt_with_base_and_fields.carbon
+
+library "fail_adapt_with_base_and_fields" api;
+
+base class Base {}
+
+class AdaptWithBaseAndFields {
+  extend base: Base;
+  var n: i32;
+  // CHECK:STDERR: fail_adapt_with_base_and_fields.carbon:[[@LINE+6]]:3: ERROR: Adapter cannot have a base class.
+  // CHECK:STDERR:   adapt {};
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_adapt_with_base_and_fields.carbon:[[@LINE-5]]:3: `base` declaration is here.
+  // CHECK:STDERR:   extend base: Base;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~
+  adapt {};
+}
+
+// CHECK:STDOUT: --- fail_adapt_with_base.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Base: type = class_type @Base [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %AdaptWithBase: type = class_type @AdaptWithBase [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: type = unbound_element_type AdaptWithBase, Base [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Base = %Base.decl
+// CHECK:STDOUT:     .AdaptWithBase = %AdaptWithBase.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Base.decl: type = class_decl @Base [template = constants.%Base] {}
+// CHECK:STDOUT:   %AdaptWithBase.decl: type = class_decl @AdaptWithBase [template = constants.%AdaptWithBase] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Base {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Base
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @AdaptWithBase {
+// CHECK:STDOUT:   adapt_decl i32
+// CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base]
+// CHECK:STDOUT:   %.loc15: <unbound element of class AdaptWithBase> = base_decl Base, element0 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%AdaptWithBase
+// CHECK:STDOUT:   .base = %.loc15
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_adapt_with_fields.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %AdaptWithField: type = class_type @AdaptWithField [template]
+// CHECK:STDOUT:   %.1: type = unbound_element_type AdaptWithField, i32 [template]
+// CHECK:STDOUT:   %AdaptWithFields: type = class_type @AdaptWithFields [template]
+// CHECK:STDOUT:   %.2: type = unbound_element_type AdaptWithFields, i32 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .AdaptWithField = %AdaptWithField.decl
+// CHECK:STDOUT:     .AdaptWithFields = %AdaptWithFields.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %AdaptWithField.decl: type = class_decl @AdaptWithField [template = constants.%AdaptWithField] {}
+// CHECK:STDOUT:   %AdaptWithFields.decl: type = class_decl @AdaptWithFields [template = constants.%AdaptWithFields] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @AdaptWithField {
+// CHECK:STDOUT:   adapt_decl i32
+// CHECK:STDOUT:   %.loc13: <unbound element of class AdaptWithField> = field_decl n, element0 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%AdaptWithField
+// CHECK:STDOUT:   .n = %.loc13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @AdaptWithFields {
+// CHECK:STDOUT:   adapt_decl i32
+// CHECK:STDOUT:   %.loc25: <unbound element of class AdaptWithFields> = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc26: <unbound element of class AdaptWithFields> = field_decl b, element1 [template]
+// CHECK:STDOUT:   %.loc27: <unbound element of class AdaptWithFields> = field_decl c, element2 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%AdaptWithFields
+// CHECK:STDOUT:   .a = %.loc25
+// CHECK:STDOUT:   .b = %.loc26
+// CHECK:STDOUT:   .c = %.loc27
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_adapt_with_base_and_fields.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Base: type = class_type @Base [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %AdaptWithBaseAndFields: type = class_type @AdaptWithBaseAndFields [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.4: type = unbound_element_type AdaptWithBaseAndFields, Base [template]
+// CHECK:STDOUT:   %.5: type = unbound_element_type AdaptWithBaseAndFields, i32 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Base = %Base.decl
+// CHECK:STDOUT:     .AdaptWithBaseAndFields = %AdaptWithBaseAndFields.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %Base.decl: type = class_decl @Base [template = constants.%Base] {}
+// CHECK:STDOUT:   %AdaptWithBaseAndFields.decl: type = class_decl @AdaptWithBaseAndFields [template = constants.%AdaptWithBaseAndFields] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Base {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Base
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @AdaptWithBaseAndFields {
+// CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base]
+// CHECK:STDOUT:   %.loc7: <unbound element of class AdaptWithBaseAndFields> = base_decl Base, element0 [template]
+// CHECK:STDOUT:   %.loc8: <unbound element of class AdaptWithBaseAndFields> = field_decl n, element1 [template]
+// CHECK:STDOUT:   %.loc15_10.1: {} = struct_literal ()
+// CHECK:STDOUT:   %.loc15_10.2: type = converted %.loc15_10.1, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:   adapt_decl {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%AdaptWithBaseAndFields
+// CHECK:STDOUT:   .base = %.loc7
+// CHECK:STDOUT:   .n = %.loc8
+// CHECK:STDOUT:   extend name_scope2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 295 - 0
toolchain/check/testdata/class/init_adapt.carbon

@@ -0,0 +1,295 @@
+// 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
+
+// --- fail_todo_init_adapt.carbon
+
+library "fail_todo_init_adapt" api;
+
+class C {
+  var a: i32;
+  var b: i32;
+}
+
+class AdaptC {
+  adapt C;
+}
+
+let a: C = {.a = 1, .b = 2};
+
+// TODO: Allow these as explicit conversions.
+
+// CHECK:STDERR: fail_todo_init_adapt.carbon:[[@LINE+4]]:17: ERROR: Cannot convert from `C` to `AdaptC` with `as`.
+// CHECK:STDERR: let b: AdaptC = a as AdaptC;
+// CHECK:STDERR:                 ^~~~~~~~~~~
+// CHECK:STDERR:
+let b: AdaptC = a as AdaptC;
+
+// CHECK:STDERR: fail_todo_init_adapt.carbon:[[@LINE+4]]:12: ERROR: Cannot convert from `AdaptC` to `C` with `as`.
+// CHECK:STDERR: let c: C = b as C;
+// CHECK:STDERR:            ^~~~~~
+// CHECK:STDERR:
+let c: C = b as C;
+
+fn MakeC() -> C;
+
+fn MakeAdaptC() -> AdaptC;
+
+// CHECK:STDERR: fail_todo_init_adapt.carbon:[[@LINE+4]]:17: ERROR: Cannot convert from `C` to `AdaptC` with `as`.
+// CHECK:STDERR: var d: AdaptC = MakeC() as AdaptC;
+// CHECK:STDERR:                 ^~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+var d: AdaptC = MakeC() as AdaptC;
+
+// CHECK:STDERR: fail_todo_init_adapt.carbon:[[@LINE+4]]:12: ERROR: Cannot convert from `AdaptC` to `C` with `as`.
+// CHECK:STDERR: var e: C = MakeAdaptC() as C;
+// CHECK:STDERR:            ^~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+var e: C = MakeAdaptC() as C;
+
+// --- fail_not_implicit.carbon
+
+library "fail_not_implicit" api;
+
+class C {
+  var a: i32;
+  var b: i32;
+}
+
+class AdaptC {
+  adapt C;
+}
+
+let a: C = {.a = 1, .b = 2};
+
+// Cannot implicitly convert between a type and an adapter for the type.
+
+// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+4]]:1: ERROR: Cannot implicitly convert from `C` to `AdaptC`.
+// CHECK:STDERR: let b: AdaptC = a;
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+let b: AdaptC = a;
+
+// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+4]]:1: ERROR: Cannot implicitly convert from `AdaptC` to `C`.
+// CHECK:STDERR: let c: C = b;
+// CHECK:STDERR: ^~~~~~~~~~~~~
+// CHECK:STDERR:
+let c: C = b;
+
+fn MakeC() -> C;
+
+fn MakeAdaptC() -> AdaptC;
+
+// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+4]]:1: ERROR: Cannot implicitly convert from `C` to `AdaptC`.
+// CHECK:STDERR: var d: AdaptC = MakeC();
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+var d: AdaptC = MakeC();
+
+// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+3]]:1: ERROR: Cannot implicitly convert from `AdaptC` to `C`.
+// CHECK:STDERR: var e: C = MakeAdaptC();
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~
+var e: C = MakeAdaptC();
+
+// CHECK:STDOUT: --- fail_todo_init_adapt.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = unbound_element_type C, i32 [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %AdaptC: type = class_type @AdaptC [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.6: C = struct_value (%.4, %.5) [template]
+// CHECK:STDOUT:   %.7: type = ptr_type C [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .AdaptC = %AdaptC.decl
+// CHECK:STDOUT:     .MakeC = %MakeC
+// CHECK:STDOUT:     .MakeAdaptC = %MakeAdaptC
+// CHECK:STDOUT:     .d = %d
+// CHECK:STDOUT:     .e = %e
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT:   %AdaptC.decl: type = class_decl @AdaptC [template = constants.%AdaptC] {}
+// CHECK:STDOUT:   %C.ref.loc13: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:   %.loc13_18: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_26: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc13_27.1: {.a: i32, .b: i32} = struct_literal (%.loc13_18, %.loc13_26)
+// CHECK:STDOUT:   %.loc13_27.2: ref C = temporary_storage
+// CHECK:STDOUT:   %.loc13_27.3: ref i32 = class_element_access %.loc13_27.2, element0
+// CHECK:STDOUT:   %.loc13_27.4: init i32 = initialize_from %.loc13_18 to %.loc13_27.3 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_27.5: ref i32 = class_element_access %.loc13_27.2, element1
+// CHECK:STDOUT:   %.loc13_27.6: init i32 = initialize_from %.loc13_26 to %.loc13_27.5 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc13_27.7: init C = class_init (%.loc13_27.4, %.loc13_27.6), %.loc13_27.2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc13_27.8: ref C = temporary %.loc13_27.2, %.loc13_27.7
+// CHECK:STDOUT:   %.loc13_27.9: ref C = converted %.loc13_27.1, %.loc13_27.8
+// CHECK:STDOUT:   %.loc13_27.10: C = bind_value %.loc13_27.9
+// CHECK:STDOUT:   %a: C = bind_name a, %.loc13_27.10
+// CHECK:STDOUT:   %AdaptC.ref.loc21_8: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:   %a.ref: C = name_ref a, %a
+// CHECK:STDOUT:   %AdaptC.ref.loc21_22: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:   %b: AdaptC = bind_name b, <error>
+// CHECK:STDOUT:   %C.ref.loc27_8: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:   %b.ref: AdaptC = name_ref b, %b
+// CHECK:STDOUT:   %C.ref.loc27_17: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:   %c: C = bind_name c, <error>
+// CHECK:STDOUT:   %MakeC: <function> = fn_decl @MakeC [template] {
+// CHECK:STDOUT:     %C.ref.loc29: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:     @MakeC.%return: ref C = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MakeAdaptC: <function> = fn_decl @MakeAdaptC [template] {
+// CHECK:STDOUT:     %AdaptC.ref.loc31: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:     @MakeAdaptC.%return: ref AdaptC = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %AdaptC.ref.loc37: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:   %d.var: ref AdaptC = var d
+// CHECK:STDOUT:   %d: ref AdaptC = bind_name d, %d.var
+// CHECK:STDOUT:   %C.ref.loc43: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:   %e.var: ref C = var e
+// CHECK:STDOUT:   %e: ref C = bind_name e, %e.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %.loc5: <unbound element of class C> = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc6: <unbound element of class C> = field_decl b, element1 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   .a = %.loc5
+// CHECK:STDOUT:   .b = %.loc6
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @AdaptC {
+// CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:   adapt_decl C
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%AdaptC
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MakeC() -> %return: C;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MakeAdaptC() -> %return: AdaptC;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %MakeC.ref: <function> = name_ref MakeC, file.%MakeC [template = file.%MakeC]
+// CHECK:STDOUT:   %.loc37_22.1: ref C = temporary_storage
+// CHECK:STDOUT:   %.loc37_22.2: init C = call %MakeC.ref() to %.loc37_22.1
+// CHECK:STDOUT:   %AdaptC.ref: type = name_ref AdaptC, file.%AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:   assign file.%d.var, <error>
+// CHECK:STDOUT:   %MakeAdaptC.ref: <function> = name_ref MakeAdaptC, file.%MakeAdaptC [template = file.%MakeAdaptC]
+// CHECK:STDOUT:   %.loc43_22.1: ref AdaptC = temporary_storage
+// CHECK:STDOUT:   %.loc43_22.2: init AdaptC = call %MakeAdaptC.ref() to %.loc43_22.1
+// CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:   assign file.%e.var, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_not_implicit.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = unbound_element_type C, i32 [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %AdaptC: type = class_type @AdaptC [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.6: C = struct_value (%.4, %.5) [template]
+// CHECK:STDOUT:   %.7: type = ptr_type C [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .AdaptC = %AdaptC.decl
+// CHECK:STDOUT:     .MakeC = %MakeC
+// CHECK:STDOUT:     .MakeAdaptC = %MakeAdaptC
+// CHECK:STDOUT:     .d = %d
+// CHECK:STDOUT:     .e = %e
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT:   %AdaptC.decl: type = class_decl @AdaptC [template = constants.%AdaptC] {}
+// CHECK:STDOUT:   %C.ref.loc13: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:   %.loc13_18: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_26: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc13_27.1: {.a: i32, .b: i32} = struct_literal (%.loc13_18, %.loc13_26)
+// CHECK:STDOUT:   %.loc13_27.2: ref C = temporary_storage
+// CHECK:STDOUT:   %.loc13_27.3: ref i32 = class_element_access %.loc13_27.2, element0
+// CHECK:STDOUT:   %.loc13_27.4: init i32 = initialize_from %.loc13_18 to %.loc13_27.3 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_27.5: ref i32 = class_element_access %.loc13_27.2, element1
+// CHECK:STDOUT:   %.loc13_27.6: init i32 = initialize_from %.loc13_26 to %.loc13_27.5 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc13_27.7: init C = class_init (%.loc13_27.4, %.loc13_27.6), %.loc13_27.2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc13_27.8: ref C = temporary %.loc13_27.2, %.loc13_27.7
+// CHECK:STDOUT:   %.loc13_27.9: ref C = converted %.loc13_27.1, %.loc13_27.8
+// CHECK:STDOUT:   %.loc13_27.10: C = bind_value %.loc13_27.9
+// CHECK:STDOUT:   %a: C = bind_name a, %.loc13_27.10
+// CHECK:STDOUT:   %AdaptC.ref.loc21: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:   %a.ref: C = name_ref a, %a
+// CHECK:STDOUT:   %b: AdaptC = bind_name b, <error>
+// CHECK:STDOUT:   %C.ref.loc27: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:   %b.ref: AdaptC = name_ref b, %b
+// CHECK:STDOUT:   %c: C = bind_name c, <error>
+// CHECK:STDOUT:   %MakeC: <function> = fn_decl @MakeC [template] {
+// CHECK:STDOUT:     %C.ref.loc29: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:     @MakeC.%return: ref C = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MakeAdaptC: <function> = fn_decl @MakeAdaptC [template] {
+// CHECK:STDOUT:     %AdaptC.ref.loc31: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:     @MakeAdaptC.%return: ref AdaptC = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %AdaptC.ref.loc37: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:   %d.var: ref AdaptC = var d
+// CHECK:STDOUT:   %d: ref AdaptC = bind_name d, %d.var
+// CHECK:STDOUT:   %C.ref.loc42: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:   %e.var: ref C = var e
+// CHECK:STDOUT:   %e: ref C = bind_name e, %e.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %.loc5: <unbound element of class C> = field_decl a, element0 [template]
+// CHECK:STDOUT:   %.loc6: <unbound element of class C> = field_decl b, element1 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   .a = %.loc5
+// CHECK:STDOUT:   .b = %.loc6
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @AdaptC {
+// CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:   adapt_decl C
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%AdaptC
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MakeC() -> %return: C;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MakeAdaptC() -> %return: AdaptC;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %MakeC.ref: <function> = name_ref MakeC, file.%MakeC [template = file.%MakeC]
+// CHECK:STDOUT:   %.loc37_22.1: ref C = temporary_storage
+// CHECK:STDOUT:   %.loc37_22.2: init C = call %MakeC.ref() to %.loc37_22.1
+// CHECK:STDOUT:   assign file.%d.var, <error>
+// CHECK:STDOUT:   %MakeAdaptC.ref: <function> = name_ref MakeAdaptC, file.%MakeAdaptC [template = file.%MakeAdaptC]
+// CHECK:STDOUT:   %.loc42_22.1: ref AdaptC = temporary_storage
+// CHECK:STDOUT:   %.loc42_22.2: init AdaptC = call %MakeAdaptC.ref() to %.loc42_22.1
+// CHECK:STDOUT:   assign file.%e.var, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 8 - 3
toolchain/diagnostics/diagnostic_kind.def

@@ -183,16 +183,20 @@ CARBON_DIAGNOSTIC_KIND(InvalidBuiltinSignature)
 CARBON_DIAGNOSTIC_KIND(FunctionExternMismatch)
 
 // Class checking.
+CARBON_DIAGNOSTIC_KIND(AdaptWithBase)
+CARBON_DIAGNOSTIC_KIND(AdaptWithFields)
+CARBON_DIAGNOSTIC_KIND(AdaptBaseHere)
+CARBON_DIAGNOSTIC_KIND(AdaptFieldHere)
 CARBON_DIAGNOSTIC_KIND(BaseIsFinal)
 CARBON_DIAGNOSTIC_KIND(BaseMissingExtend)
-CARBON_DIAGNOSTIC_KIND(BaseOutsideClass)
-CARBON_DIAGNOSTIC_KIND(BasePrevious)
-CARBON_DIAGNOSTIC_KIND(BaseRepeated)
 CARBON_DIAGNOSTIC_KIND(ClassForwardDeclaredHere)
 CARBON_DIAGNOSTIC_KIND(ClassPreviousDefinition)
 CARBON_DIAGNOSTIC_KIND(ClassRedeclarationDifferentIntroducer)
 CARBON_DIAGNOSTIC_KIND(ClassRedeclarationDifferentIntroducerPrevious)
 CARBON_DIAGNOSTIC_KIND(ClassRedefinition)
+CARBON_DIAGNOSTIC_KIND(ClassSpecificDeclOutsideClass)
+CARBON_DIAGNOSTIC_KIND(ClassSpecificDeclPrevious)
+CARBON_DIAGNOSTIC_KIND(ClassSpecificDeclRepeated)
 CARBON_DIAGNOSTIC_KIND(ClassIncompleteWithinDefinition)
 CARBON_DIAGNOSTIC_KIND(ConstructionOfAbstractClass)
 
@@ -260,6 +264,7 @@ CARBON_DIAGNOSTIC_KIND(NameNotFound)
 CARBON_DIAGNOSTIC_KIND(NameDeclDuplicate)
 CARBON_DIAGNOSTIC_KIND(NameDeclPrevious)
 CARBON_DIAGNOSTIC_KIND(RepeatedConst)
+CARBON_DIAGNOSTIC_KIND(IncompleteTypeInAdaptDecl)
 CARBON_DIAGNOSTIC_KIND(IncompleteTypeInBaseDecl)
 CARBON_DIAGNOSTIC_KIND(IncompleteTypeInConversion)
 CARBON_DIAGNOSTIC_KIND(IncompleteTypeInFunctionParam)

+ 5 - 0
toolchain/lower/handle.cpp

@@ -26,6 +26,11 @@ static auto FatalErrorIfEncountered(InstT inst) -> void {
       << inst;
 }
 
+auto HandleAdaptDecl(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
+                     SemIR::AdaptDecl inst) -> void {
+  FatalErrorIfEncountered(inst);
+}
+
 auto HandleAddrOf(FunctionContext& context, SemIR::InstId inst_id,
                   SemIR::AddrOf inst) -> void {
   context.SetLocal(inst_id, context.GetValue(inst.lvalue_id));

+ 26 - 0
toolchain/lower/testdata/class/adapt.carbon

@@ -0,0 +1,26 @@
+// 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
+
+class PairOfInts {
+  var a: i32;
+  var b: i32;
+}
+
+class PairAdapter {
+  adapt PairOfInts;
+}
+
+fn MakePair() -> PairAdapter {
+  returned var v: PairAdapter;
+  return var;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'adapt.carbon'
+// CHECK:STDOUT: source_filename = "adapt.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @MakePair(ptr sret({ i32, i32 }) %return) {
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }

+ 40 - 7
toolchain/parse/testdata/class/fail_modifiers.carbon

@@ -4,26 +4,59 @@
 //
 // AUTOUPDATE
 
+// --- fail_virtual_class.carbon
+
 virtual class B
+// CHECK:STDERR: fail_virtual_class.carbon:[[@LINE+4]]:1: ERROR: `virtual` declarations must either end with a `;` or have a `{ ... }` block for a definition.
+// CHECK:STDERR:
+// CHECK:STDERR: ^
+// CHECK:STDERR:
+
+// --- fail_impl_class.carbon
 
-// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+7]]:1: ERROR: `virtual` declarations must either end with a `;` or have a `{ ... }` block for a definition.
+// CHECK:STDERR: fail_impl_class.carbon:[[@LINE+4]]:1: ERROR: `impl` introducer should be followed by a name.
 // CHECK:STDERR: impl class
 // CHECK:STDERR: ^~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+3]]:6: ERROR: `class` introducer should be followed by a name.
-// CHECK:STDERR: impl class
-// CHECK:STDERR:      ^~~~~
 impl class
 
-// CHECK:STDOUT: - filename: fail_modifiers.carbon
+// --- fail_extend_adapt_base.carbon
+
+class C {
+  // CHECK:STDERR: fail_extend_adapt_base.carbon:[[@LINE+3]]:16: ERROR: Expected expression.
+  // CHECK:STDERR:   extend adapt base B;
+  // CHECK:STDERR:                ^~~~
+  extend adapt base B;
+}
+
+// CHECK:STDOUT: - filename: fail_virtual_class.carbon
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
 // CHECK:STDOUT:       {kind: 'VirtualModifier', text: 'virtual'},
 // CHECK:STDOUT:       {kind: 'IdentifierName', text: 'B'},
-// CHECK:STDOUT:     {kind: 'ClassDecl', text: 'impl', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'ClassDecl', text: 'B', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_impl_class.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:       {kind: 'ImplModifier', text: 'impl'},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: '', has_error: yes},
-// CHECK:STDOUT:     {kind: 'ClassDecl', text: 'class', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'ClassDecl', text: 'class', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_extend_adapt_base.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'C'},
+// CHECK:STDOUT:       {kind: 'ClassDefinitionStart', text: '{', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'AdaptIntroducer', text: 'adapt'},
+// CHECK:STDOUT:         {kind: 'ExtendModifier', text: 'extend'},
+// CHECK:STDOUT:         {kind: 'InvalidParse', text: 'base', has_error: yes},
+// CHECK:STDOUT:       {kind: 'AdaptDecl', text: ';', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'ClassDefinition', text: '}', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 5 - 0
toolchain/sem_ir/class.h

@@ -56,6 +56,11 @@ struct Class : public Printable<Class> {
 
   // The following members are accumulated throughout the class definition.
 
+  // The adapted type declaration, if any. Invalid if the class is not an
+  // adapter. This is an AdaptDecl instruction.
+  // TODO: Consider sharing the storage for `adapt_id` and `base_id`. A class
+  // can't have both.
+  InstId adapt_id = InstId::Invalid;
   // The base class declaration. Invalid if the class has no base class. This is
   // a BaseDecl instruction.
   InstId base_id = InstId::Invalid;

+ 3 - 0
toolchain/sem_ir/file.cpp

@@ -221,6 +221,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
     case PointerType::Kind:
       return -2;
 
+    case AdaptDecl::Kind:
     case AddrOf::Kind:
     case AddrPattern::Kind:
     case ArrayIndex::Kind:
@@ -477,6 +478,7 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
         }
         break;
       }
+      case AdaptDecl::Kind:
       case AddrOf::Kind:
       case AddrPattern::Kind:
       case ArrayIndex::Kind:
@@ -560,6 +562,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
   while (true) {
     auto untyped_inst = ir->insts().Get(inst_id);
     CARBON_KIND_SWITCH(untyped_inst) {
+      case AdaptDecl::Kind:
       case Assign::Kind:
       case BaseDecl::Kind:
       case Branch::Kind:

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -17,6 +17,7 @@
 
 // For each instruction kind declared here there is a matching definition in
 // `typed_insts.h`.
+CARBON_SEM_IR_INST_KIND(AdaptDecl)
 CARBON_SEM_IR_INST_KIND(AddrOf)
 CARBON_SEM_IR_INST_KIND(AddrPattern)
 CARBON_SEM_IR_INST_KIND(ArrayIndex)

+ 9 - 0
toolchain/sem_ir/typed_insts.h

@@ -44,6 +44,15 @@
 
 namespace Carbon::SemIR {
 
+// An adapted type declaration in a class, of the form `adapt T;`.
+struct AdaptDecl {
+  static constexpr auto Kind =
+      InstKind::AdaptDecl.Define<Parse::AdaptDeclId>("adapt_decl");
+
+  // No type_id; this is not a value.
+  TypeId adapted_type_id;
+};
+
 struct AddrOf {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =