Browse Source

Support explicit conversion between adapters and their adapted types. (#3889)

Allow an explicit `as` conversion to convert between adapters and their
adapted types. Also make the value representation of an adapter be the
same as the value representation of the adapted type so that the
conversion is always possible.

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
Richard Smith 2 years ago
parent
commit
462bcd9f6e

+ 11 - 5
toolchain/check/context.cpp

@@ -957,6 +957,7 @@ class TypeCompleter {
       case SemIR::AddrPattern::Kind:
       case SemIR::AddrPattern::Kind:
       case SemIR::ArrayIndex::Kind:
       case SemIR::ArrayIndex::Kind:
       case SemIR::ArrayInit::Kind:
       case SemIR::ArrayInit::Kind:
+      case SemIR::AsCompatible::Kind:
       case SemIR::Assign::Kind:
       case SemIR::Assign::Kind:
       case SemIR::AssociatedConstantDecl::Kind:
       case SemIR::AssociatedConstantDecl::Kind:
       case SemIR::AssociatedEntity::Kind:
       case SemIR::AssociatedEntity::Kind:
@@ -1031,13 +1032,18 @@ class TypeCompleter {
         return BuildTupleTypeValueRepr(type_id, tuple_type);
         return BuildTupleTypeValueRepr(type_id, tuple_type);
       }
       }
       case CARBON_KIND(SemIR::ClassType class_type): {
       case CARBON_KIND(SemIR::ClassType class_type): {
-        // The value representation for a class is a pointer to the object
-        // representation.
+        auto& class_info = context_.classes().Get(class_type.class_id);
+        // The value representation of an adapter is the value representation of
+        // its adapted type.
+        if (class_info.adapt_id.is_valid()) {
+          return GetNestedValueRepr(class_info.object_repr_id);
+        }
+        // Otherwise, the value representation for a class is a pointer to the
+        // object representation.
         // TODO: Support customized value representations for classes.
         // TODO: Support customized value representations for classes.
         // TODO: Pick a better value representation when possible.
         // TODO: Pick a better value representation when possible.
-        return MakePointerValueRepr(
-            context_.classes().Get(class_type.class_id).object_repr_id,
-            SemIR::ValueRepr::ObjectAggregate);
+        return MakePointerValueRepr(class_info.object_repr_id,
+                                    SemIR::ValueRepr::ObjectAggregate);
       }
       }
       case SemIR::InterfaceType::Kind: {
       case SemIR::InterfaceType::Kind: {
         // TODO: Should we model the value representation as a witness?
         // TODO: Should we model the value representation as a witness?

+ 47 - 2
toolchain/check/convert.cpp

@@ -14,6 +14,7 @@
 #include "toolchain/sem_ir/copy_on_write_block.h"
 #include "toolchain/sem_ir/copy_on_write_block.h"
 #include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/inst.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 
 namespace Carbon::Check {
 namespace Carbon::Check {
 
 
@@ -26,6 +27,10 @@ static auto FindReturnSlotForInitializer(SemIR::File& sem_ir,
   while (true) {
   while (true) {
     SemIR::Inst init_untyped = sem_ir.insts().Get(init_id);
     SemIR::Inst init_untyped = sem_ir.insts().Get(init_id);
     CARBON_KIND_SWITCH(init_untyped) {
     CARBON_KIND_SWITCH(init_untyped) {
+      case CARBON_KIND(SemIR::AsCompatible init): {
+        init_id = init.source_id;
+        continue;
+      }
       case CARBON_KIND(SemIR::Converted init): {
       case CARBON_KIND(SemIR::Converted init): {
         init_id = init.result_id;
         init_id = init.result_id;
         continue;
         continue;
@@ -651,6 +656,22 @@ static auto IsValidExprCategoryForConversionTarget(
   }
   }
 }
 }
 
 
+// Returns the non-adapter type that is compatible with the specified type.
+static auto GetCompatibleBaseType(Context& context, SemIR::TypeId type_id)
+    -> SemIR::TypeId {
+  // If the type is an adapter, its object representation type is its compatible
+  // non-adapter type.
+  if (auto class_type = context.types().TryGetAs<SemIR::ClassType>(type_id)) {
+    auto& class_info = context.classes().Get(class_type->class_id);
+    if (class_info.adapt_id.is_valid()) {
+      return class_info.object_repr_id;
+    }
+  }
+
+  // Otherwise, the type itself is a non-adapter type.
+  return type_id;
+}
+
 static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
 static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
                                      SemIR::InstId value_id,
                                      SemIR::InstId value_id,
                                      ConversionTarget target) -> SemIR::InstId {
                                      ConversionTarget target) -> SemIR::InstId {
@@ -714,6 +735,26 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
     }
     }
   }
   }
 
 
+  // T explicitly converts to U if T is compatible with U.
+  if (target.kind == ConversionTarget::Kind::ExplicitAs &&
+      target.type_id != value_type_id) {
+    auto target_base_id = GetCompatibleBaseType(context, target.type_id);
+    auto value_base_id = GetCompatibleBaseType(context, value_type_id);
+    if (target_base_id == value_base_id) {
+      // For a struct or tuple literal, perform a category conversion if
+      // necessary.
+      if (SemIR::GetExprCategory(context.sem_ir(), value_id) ==
+          SemIR::ExprCategory::Mixed) {
+        value_id = PerformBuiltinConversion(
+            context, loc_id, value_id,
+            ConversionTarget{.kind = ConversionTarget::Value,
+                             .type_id = value_type_id});
+      }
+      return context.AddInst(
+          {loc_id, SemIR::AsCompatible{target.type_id, value_id}});
+    }
+  }
+
   // A tuple (T1, T2, ..., Tn) converts to (U1, U2, ..., Un) if each Ti
   // A tuple (T1, T2, ..., Tn) converts to (U1, U2, ..., Un) if each Ti
   // converts to Ui.
   // converts to Ui.
   if (auto target_tuple_type = target_type_inst.TryAs<SemIR::TupleType>()) {
   if (auto target_tuple_type = target_type_inst.TryAs<SemIR::TupleType>()) {
@@ -752,8 +793,12 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
   if (auto target_class_type = target_type_inst.TryAs<SemIR::ClassType>()) {
   if (auto target_class_type = target_type_inst.TryAs<SemIR::ClassType>()) {
     if (auto src_struct_type =
     if (auto src_struct_type =
             sem_ir.types().TryGetAs<SemIR::StructType>(value_type_id)) {
             sem_ir.types().TryGetAs<SemIR::StructType>(value_type_id)) {
-      return ConvertStructToClass(context, *src_struct_type, *target_class_type,
-                                  value_id, target);
+      if (!context.classes()
+               .Get(target_class_type->class_id)
+               .adapt_id.is_valid()) {
+        return ConvertStructToClass(context, *src_struct_type,
+                                    *target_class_type, value_id, target);
+      }
     }
     }
 
 
     // An expression of type T converts to U if T is a class derived from U.
     // An expression of type T converts to U if T is a class derived from U.

+ 3 - 0
toolchain/check/eval.cpp

@@ -848,6 +848,9 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
       return SemIR::ConstantId::ForSymbolicConstant(inst_id);
       return SemIR::ConstantId::ForSymbolicConstant(inst_id);
 
 
     // These semantic wrappers don't change the constant value.
     // These semantic wrappers don't change the constant value.
+    case CARBON_KIND(SemIR::AsCompatible inst): {
+      return context.constant_values().Get(inst.source_id);
+    }
     case CARBON_KIND(SemIR::BindAlias typed_inst): {
     case CARBON_KIND(SemIR::BindAlias typed_inst): {
       return context.constant_values().Get(typed_inst.value_id);
       return context.constant_values().Get(typed_inst.value_id);
     }
     }

+ 16 - 7
toolchain/check/handle_class.cpp

@@ -457,13 +457,22 @@ auto HandleClassDefinition(Context& context,
           .Note(first_field_id, AdaptFieldHere)
           .Note(first_field_id, AdaptFieldHere)
           .Emit();
           .Emit();
     } else {
     } 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;
+      // The object representation of the adapter is the object representation
+      // of the adapted type.
+      auto adapted_type_id = context.insts()
+                                 .GetAs<SemIR::AdaptDecl>(class_info.adapt_id)
+                                 .adapted_type_id;
+      // If we adapt an adapter, directly track the non-adapter type we're
+      // adapting so that we have constant-time access to it.
+      if (auto adapted_class =
+              context.types().TryGetAs<SemIR::ClassType>(adapted_type_id)) {
+        auto& adapted_class_info =
+            context.classes().Get(adapted_class->class_id);
+        if (adapted_class_info.adapt_id.is_valid()) {
+          adapted_type_id = adapted_class_info.object_repr_id;
+        }
+      }
+      class_info.object_repr_id = adapted_type_id;
     }
     }
   } else {
   } else {
     class_info.object_repr_id = context.GetStructType(fields_id);
     class_info.object_repr_id = context.GetStructType(fields_id);

+ 448 - 0
toolchain/check/testdata/as/adapter_conversion.carbon

@@ -0,0 +1,448 @@
+// 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
+
+// --- adapt_class.carbon
+
+library "adapt_class" api;
+
+class A {
+  var x: i32;
+  var y: i32;
+
+  fn Make() -> A {
+    return {.x = 1, .y = 2};
+  }
+}
+
+class B {
+  adapt A;
+}
+
+var a_ref: A = {.x = 1, .y = 2};
+let a_val: A = a_ref;
+
+// An `as` conversion to an adapter type preserves the expression category.
+let b_val: B = a_val as B;
+let b_ptr: B* = &(a_ref as B);
+
+var b_factory: B = A.Make() as B;
+
+// --- adapt_i32.carbon
+
+library "adapt_i32" api;
+
+class A {
+  adapt i32;
+}
+
+let a: A = (1 as i32) as A;
+let n: i32 = a as i32;
+
+// --- multi_level_adapt.carbon
+
+library "multi_level_adapt" api;
+
+class A { adapt {}; }
+class B { adapt A; }
+class C { adapt B; }
+class D { adapt C; }
+
+let d: D = {} as D;
+
+// --- fail_init_class.carbon
+
+library "fail_init_class" api;
+
+class A {
+  var x: i32;
+  var y: i32;
+}
+
+class B {
+  adapt A;
+}
+
+let b_value: B = ({.x = 1, .y = 2} as A) as B;
+
+// TODO: Here, we treat `{.x = 1, .y = 2} as A` as a value expression, not an
+// initializing expression, so `(...) as B` is a value expression too, requiring
+// a copy to perform initialization. It's not clear whether that is the right
+// behavior.
+
+// CHECK:STDERR: fail_init_class.carbon:[[@LINE+4]]:17: ERROR: Cannot copy value of type `B`.
+// CHECK:STDERR: var b_init: B = ({.x = 1, .y = 2} as A) as B;
+// CHECK:STDERR:                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+var b_init: B = ({.x = 1, .y = 2} as A) as B;
+
+// --- fail_adapt_init_from_struct.carbon
+
+library "fail_adapt_init_from_struct" api;
+
+class A {
+  var x: i32;
+}
+
+class B {
+  adapt A;
+}
+
+// We do not try to implicitly convert from the first operand of `as` to the
+// adapted type of the second operand.
+
+// CHECK:STDERR: fail_adapt_init_from_struct.carbon:[[@LINE+3]]:12: ERROR: Cannot convert from `{.x: i32}` to `B` with `as`.
+// CHECK:STDERR: var b: B = {.x = 1} as B;
+// CHECK:STDERR:            ^~~~~~~~~~~~~
+var b: B = {.x = 1} as B;
+
+// CHECK:STDOUT: --- adapt_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A: type = class_type @A [template]
+// CHECK:STDOUT:   %.1: type = unbound_element_type A, i32 [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: i32, .y: i32} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: i32, .y: i32} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.6: A = struct_value (%.4, %.5) [template]
+// CHECK:STDOUT:   %B: type = class_type @B [template]
+// CHECK:STDOUT:   %.7: type = ptr_type B [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:     .a_ref = %a_ref
+// CHECK:STDOUT:     .b_factory = %b_factory
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %A.decl: type = class_decl @A [template = constants.%A] {}
+// CHECK:STDOUT:   %B.decl: type = class_decl @B [template = constants.%B] {}
+// CHECK:STDOUT:   %A.ref.loc17: type = name_ref A, %A.decl [template = constants.%A]
+// CHECK:STDOUT:   %a_ref.var: ref A = var a_ref
+// CHECK:STDOUT:   %a_ref: ref A = bind_name a_ref, %a_ref.var
+// CHECK:STDOUT:   %A.ref.loc18: type = name_ref A, %A.decl [template = constants.%A]
+// CHECK:STDOUT:   %a_ref.ref.loc18: ref A = name_ref a_ref, %a_ref
+// CHECK:STDOUT:   %.loc18: A = bind_value %a_ref.ref.loc18
+// CHECK:STDOUT:   %a_val: A = bind_name a_val, %.loc18
+// CHECK:STDOUT:   %B.ref.loc21_12: type = name_ref B, %B.decl [template = constants.%B]
+// CHECK:STDOUT:   %a_val.ref: A = name_ref a_val, %a_val
+// CHECK:STDOUT:   %B.ref.loc21_25: type = name_ref B, %B.decl [template = constants.%B]
+// CHECK:STDOUT:   %.loc21_22.1: B = as_compatible %a_val.ref
+// CHECK:STDOUT:   %.loc21_22.2: B = converted %a_val.ref, %.loc21_22.1
+// CHECK:STDOUT:   %b_val: B = bind_name b_val, %.loc21_22.2
+// CHECK:STDOUT:   %B.ref.loc22_12: type = name_ref B, %B.decl [template = constants.%B]
+// CHECK:STDOUT:   %.loc22_13: type = ptr_type B [template = constants.%.7]
+// CHECK:STDOUT:   %a_ref.ref.loc22: ref A = name_ref a_ref, %a_ref
+// CHECK:STDOUT:   %B.ref.loc22_28: type = name_ref B, %B.decl [template = constants.%B]
+// CHECK:STDOUT:   %.loc22_25.1: ref B = as_compatible %a_ref.ref.loc22
+// CHECK:STDOUT:   %.loc22_25.2: ref B = converted %a_ref.ref.loc22, %.loc22_25.1
+// CHECK:STDOUT:   %.loc22_17: B* = addr_of %.loc22_25.2
+// CHECK:STDOUT:   %b_ptr: B* = bind_name b_ptr, %.loc22_17
+// CHECK:STDOUT:   %B.ref.loc24: type = name_ref B, %B.decl [template = constants.%B]
+// CHECK:STDOUT:   %b_factory.var: ref B = var b_factory
+// CHECK:STDOUT:   %b_factory: ref B = bind_name b_factory, %b_factory.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A {
+// CHECK:STDOUT:   %.loc5: <unbound element of class A> = field_decl x, element0 [template]
+// CHECK:STDOUT:   %.loc6: <unbound element of class A> = field_decl y, element1 [template]
+// CHECK:STDOUT:   %Make: <function> = fn_decl @Make [template] {
+// CHECK:STDOUT:     %A.ref: type = name_ref A, file.%A.decl [template = constants.%A]
+// CHECK:STDOUT:     %return.var: ref A = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%A
+// CHECK:STDOUT:   .x = %.loc5
+// CHECK:STDOUT:   .y = %.loc6
+// CHECK:STDOUT:   .Make = %Make
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:   %A.ref: type = name_ref A, file.%A.decl [template = constants.%A]
+// CHECK:STDOUT:   adapt_decl A
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%B
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Make() -> @A.%return.var: A {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc9_18: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc9_26: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc9_27.1: {.x: i32, .y: i32} = struct_literal (%.loc9_18, %.loc9_26)
+// CHECK:STDOUT:   %.loc9_27.2: ref i32 = class_element_access @A.%return.var, element0
+// CHECK:STDOUT:   %.loc9_27.3: init i32 = initialize_from %.loc9_18 to %.loc9_27.2 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc9_27.4: ref i32 = class_element_access @A.%return.var, element1
+// CHECK:STDOUT:   %.loc9_27.5: init i32 = initialize_from %.loc9_26 to %.loc9_27.4 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc9_27.6: init A = class_init (%.loc9_27.3, %.loc9_27.5), @A.%return.var [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_28: init A = converted %.loc9_27.1, %.loc9_27.6 [template = constants.%.6]
+// CHECK:STDOUT:   return %.loc9_28
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc17_22: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc17_30: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc17_31.1: {.x: i32, .y: i32} = struct_literal (%.loc17_22, %.loc17_30)
+// CHECK:STDOUT:   %.loc17_31.2: ref i32 = class_element_access file.%a_ref.var, element0
+// CHECK:STDOUT:   %.loc17_31.3: init i32 = initialize_from %.loc17_22 to %.loc17_31.2 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc17_31.4: ref i32 = class_element_access file.%a_ref.var, element1
+// CHECK:STDOUT:   %.loc17_31.5: init i32 = initialize_from %.loc17_30 to %.loc17_31.4 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc17_31.6: init A = class_init (%.loc17_31.3, %.loc17_31.5), file.%a_ref.var [template = constants.%.6]
+// CHECK:STDOUT:   %.loc17_32: init A = converted %.loc17_31.1, %.loc17_31.6 [template = constants.%.6]
+// CHECK:STDOUT:   assign file.%a_ref.var, %.loc17_32
+// CHECK:STDOUT:   %A.ref: type = name_ref A, file.%A.decl [template = constants.%A]
+// CHECK:STDOUT:   %Make.ref: <function> = name_ref Make, @A.%Make [template = @A.%Make]
+// CHECK:STDOUT:   %.loc24_5: ref B = splice_block file.%b_factory.var {}
+// CHECK:STDOUT:   %.loc24_26: init A = call %Make.ref() to %.loc24_5
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
+// CHECK:STDOUT:   %.loc24_29.1: init B = as_compatible %.loc24_26
+// CHECK:STDOUT:   %.loc24_29.2: init B = converted %.loc24_26, %.loc24_29.1
+// CHECK:STDOUT:   assign file.%b_factory.var, %.loc24_29.2
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- adapt_i32.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A: type = class_type @A [template]
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %A.decl: type = class_decl @A [template = constants.%A] {}
+// CHECK:STDOUT:   %A.ref.loc8_8: type = name_ref A, %A.decl [template = constants.%A]
+// CHECK:STDOUT:   %.loc8_13: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %A.ref.loc8_26: type = name_ref A, %A.decl [template = constants.%A]
+// CHECK:STDOUT:   %.loc8_23.1: A = as_compatible %.loc8_13 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc8_23.2: A = converted %.loc8_13, %.loc8_23.1 [template = constants.%.1]
+// CHECK:STDOUT:   %a: A = bind_name a, %.loc8_23.2
+// CHECK:STDOUT:   %a.ref: A = name_ref a, %a
+// CHECK:STDOUT:   %.loc9_16.1: i32 = as_compatible %a.ref
+// CHECK:STDOUT:   %.loc9_16.2: i32 = converted %a.ref, %.loc9_16.1
+// CHECK:STDOUT:   %n: i32 = bind_name n, %.loc9_16.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A {
+// CHECK:STDOUT:   adapt_decl i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%A
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- multi_level_adapt.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A: type = class_type @A [template]
+// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %B: type = class_type @B [template]
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %D: type = class_type @D [template]
+// CHECK:STDOUT:   %.3: {} = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .D = %D.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %A.decl: type = class_decl @A [template = constants.%A] {}
+// CHECK:STDOUT:   %B.decl: type = class_decl @B [template = constants.%B] {}
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT:   %D.decl: type = class_decl @D [template = constants.%D] {}
+// CHECK:STDOUT:   %D.ref.loc9_8: type = name_ref D, %D.decl [template = constants.%D]
+// CHECK:STDOUT:   %.loc9_13.1: {} = struct_literal ()
+// CHECK:STDOUT:   %D.ref.loc9_18: type = name_ref D, %D.decl [template = constants.%D]
+// CHECK:STDOUT:   %.loc9_13.2: {} = struct_value () [template = constants.%.3]
+// CHECK:STDOUT:   %.loc9_15.1: D = as_compatible %.loc9_13.2 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc9_15.2: D = converted %.loc9_13.1, %.loc9_15.1 [template = constants.%.3]
+// CHECK:STDOUT:   %d: D = bind_name d, %.loc9_15.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A {
+// CHECK:STDOUT:   %.loc4_18: {} = struct_literal ()
+// CHECK:STDOUT:   %.loc4_19: type = converted %.loc4_18, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:   adapt_decl {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%A
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:   %A.ref: type = name_ref A, file.%A.decl [template = constants.%A]
+// CHECK:STDOUT:   adapt_decl A
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%B
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// 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.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @D {
+// 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.%D
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_init_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A: type = class_type @A [template]
+// CHECK:STDOUT:   %.1: type = unbound_element_type A, i32 [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: i32, .y: i32} [template]
+// CHECK:STDOUT:   %B: type = class_type @B [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: i32, .y: i32} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
+// CHECK:STDOUT:   %.6: A = struct_value (%.4, %.5) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:     .b_init = %b_init
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %A.decl: type = class_decl @A [template = constants.%A] {}
+// CHECK:STDOUT:   %B.decl: type = class_decl @B [template = constants.%B] {}
+// CHECK:STDOUT:   %B.ref.loc13_14: type = name_ref B, %B.decl [template = constants.%B]
+// CHECK:STDOUT:   %.loc13_25: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_33: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc13_34.1: {.x: i32, .y: i32} = struct_literal (%.loc13_25, %.loc13_33)
+// CHECK:STDOUT:   %A.ref: type = name_ref A, %A.decl [template = constants.%A]
+// CHECK:STDOUT:   %.loc13_34.2: ref A = temporary_storage
+// CHECK:STDOUT:   %.loc13_34.3: ref i32 = class_element_access %.loc13_34.2, element0
+// CHECK:STDOUT:   %.loc13_34.4: init i32 = initialize_from %.loc13_25 to %.loc13_34.3 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc13_34.5: ref i32 = class_element_access %.loc13_34.2, element1
+// CHECK:STDOUT:   %.loc13_34.6: init i32 = initialize_from %.loc13_33 to %.loc13_34.5 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc13_34.7: init A = class_init (%.loc13_34.4, %.loc13_34.6), %.loc13_34.2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc13_34.8: ref A = temporary %.loc13_34.2, %.loc13_34.7
+// CHECK:STDOUT:   %.loc13_36: ref A = converted %.loc13_34.1, %.loc13_34.8
+// CHECK:STDOUT:   %B.ref.loc13_45: type = name_ref B, %B.decl [template = constants.%B]
+// CHECK:STDOUT:   %.loc13_42.1: ref B = as_compatible %.loc13_36
+// CHECK:STDOUT:   %.loc13_42.2: ref B = converted %.loc13_36, %.loc13_42.1
+// CHECK:STDOUT:   %.loc13_42.3: B = bind_value %.loc13_42.2
+// CHECK:STDOUT:   %b_value: B = bind_name b_value, %.loc13_42.3
+// CHECK:STDOUT:   %B.ref.loc24: type = name_ref B, %B.decl [template = constants.%B]
+// CHECK:STDOUT:   %b_init.var: ref B = var b_init
+// CHECK:STDOUT:   %b_init: ref B = bind_name b_init, %b_init.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A {
+// CHECK:STDOUT:   %.loc5: <unbound element of class A> = field_decl x, element0 [template]
+// CHECK:STDOUT:   %.loc6: <unbound element of class A> = field_decl y, element1 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%A
+// CHECK:STDOUT:   .x = %.loc5
+// CHECK:STDOUT:   .y = %.loc6
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:   %A.ref: type = name_ref A, file.%A.decl [template = constants.%A]
+// CHECK:STDOUT:   adapt_decl A
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%B
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc24_24: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc24_32: i32 = int_literal 2 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc24_33.1: {.x: i32, .y: i32} = struct_literal (%.loc24_24, %.loc24_32)
+// CHECK:STDOUT:   %A.ref: type = name_ref A, file.%A.decl [template = constants.%A]
+// CHECK:STDOUT:   %.loc24_33.2: ref A = temporary_storage
+// CHECK:STDOUT:   %.loc24_33.3: ref i32 = class_element_access %.loc24_33.2, element0
+// CHECK:STDOUT:   %.loc24_33.4: init i32 = initialize_from %.loc24_24 to %.loc24_33.3 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc24_33.5: ref i32 = class_element_access %.loc24_33.2, element1
+// CHECK:STDOUT:   %.loc24_33.6: init i32 = initialize_from %.loc24_32 to %.loc24_33.5 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc24_33.7: init A = class_init (%.loc24_33.4, %.loc24_33.6), %.loc24_33.2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc24_33.8: ref A = temporary %.loc24_33.2, %.loc24_33.7
+// CHECK:STDOUT:   %.loc24_35: ref A = converted %.loc24_33.1, %.loc24_33.8
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
+// CHECK:STDOUT:   %.loc24_41.1: ref B = as_compatible %.loc24_35
+// CHECK:STDOUT:   %.loc24_41.2: ref B = converted %.loc24_35, %.loc24_41.1
+// CHECK:STDOUT:   %.loc24_41.3: B = bind_value %.loc24_41.2
+// CHECK:STDOUT:   assign file.%b_init.var, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_adapt_init_from_struct.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A: type = class_type @A [template]
+// CHECK:STDOUT:   %.1: type = unbound_element_type A, i32 [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: i32} [template]
+// CHECK:STDOUT:   %B: type = class_type @B [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: i32} [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:     .b = %b
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %A.decl: type = class_decl @A [template = constants.%A] {}
+// CHECK:STDOUT:   %B.decl: type = class_decl @B [template = constants.%B] {}
+// CHECK:STDOUT:   %B.ref: type = name_ref B, %B.decl [template = constants.%B]
+// CHECK:STDOUT:   %b.var: ref B = var b
+// CHECK:STDOUT:   %b: ref B = bind_name b, %b.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @A {
+// CHECK:STDOUT:   %.loc5: <unbound element of class A> = field_decl x, element0 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%A
+// CHECK:STDOUT:   .x = %.loc5
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:   %A.ref: type = name_ref A, file.%A.decl [template = constants.%A]
+// CHECK:STDOUT:   adapt_decl A
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%B
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc18_18: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc18_19: {.x: i32} = struct_literal (%.loc18_18)
+// CHECK:STDOUT:   %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
+// CHECK:STDOUT:   assign file.%b.var, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

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

@@ -99,7 +99,6 @@ fn F(a: AdaptNotExtend) {
 // CHECK:STDOUT:   %AdaptNotExtend: type = class_type @AdaptNotExtend [template]
 // CHECK:STDOUT:   %AdaptNotExtend: type = class_type @AdaptNotExtend [template]
 // CHECK:STDOUT:   %.2: type = tuple_type () [template]
 // CHECK:STDOUT:   %.2: type = tuple_type () [template]
 // CHECK:STDOUT:   %.3: type = ptr_type {} [template]
 // CHECK:STDOUT:   %.3: type = ptr_type {} [template]
-// CHECK:STDOUT:   %.4: type = ptr_type Adapted [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT: file {

+ 1 - 4
toolchain/check/testdata/class/extend_adapt.carbon

@@ -94,8 +94,7 @@ class StructAdapter {
 // CHECK:STDOUT:   %.1: type = unbound_element_type SomeClass, i32 [template]
 // CHECK:STDOUT:   %.1: type = unbound_element_type SomeClass, i32 [template]
 // CHECK:STDOUT:   %.2: type = struct_type {.a: i32, .b: 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:   %.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:   %.4: type = tuple_type () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT: file {
@@ -178,7 +177,6 @@ class StructAdapter {
 // CHECK:STDOUT:   %SomeClassAdapter: type = class_type @SomeClassAdapter [template]
 // CHECK:STDOUT:   %SomeClassAdapter: type = class_type @SomeClassAdapter [template]
 // CHECK:STDOUT:   %.2: type = tuple_type () [template]
 // CHECK:STDOUT:   %.2: type = tuple_type () [template]
 // CHECK:STDOUT:   %.3: type = ptr_type {} [template]
 // CHECK:STDOUT:   %.3: type = ptr_type {} [template]
-// CHECK:STDOUT:   %.4: type = ptr_type SomeClass [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT: file {
@@ -238,7 +236,6 @@ class StructAdapter {
 // CHECK:STDOUT:   %.2: type = struct_type {.a: i32, .b: i32} [template]
 // CHECK:STDOUT:   %.2: type = struct_type {.a: i32, .b: i32} [template]
 // CHECK:STDOUT:   %SomeClassAdapter: type = class_type @SomeClassAdapter [template]
 // CHECK:STDOUT:   %SomeClassAdapter: type = class_type @SomeClassAdapter [template]
 // CHECK:STDOUT:   %.3: type = ptr_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: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT: file {

+ 27 - 39
toolchain/check/testdata/class/init_adapt.carbon

@@ -4,9 +4,9 @@
 //
 //
 // AUTOUPDATE
 // AUTOUPDATE
 
 
-// --- fail_todo_init_adapt.carbon
+// --- init_adapt.carbon
 
 
-library "fail_todo_init_adapt" api;
+library "init_adapt" api;
 
 
 class C {
 class C {
   var a: i32;
   var a: i32;
@@ -19,34 +19,16 @@ class AdaptC {
 
 
 let a: C = {.a = 1, .b = 2};
 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;
 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;
 let c: C = b as C;
 
 
 fn MakeC() -> C;
 fn MakeC() -> C;
 
 
 fn MakeAdaptC() -> AdaptC;
 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;
 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;
 var e: C = MakeAdaptC() as C;
 
 
 // --- fail_not_implicit.carbon
 // --- fail_not_implicit.carbon
@@ -93,7 +75,7 @@ var d: AdaptC = MakeC();
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~
 var e: C = MakeAdaptC();
 var e: C = MakeAdaptC();
 
 
-// CHECK:STDOUT: --- fail_todo_init_adapt.carbon
+// CHECK:STDOUT: --- init_adapt.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %C: type = class_type @C [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
@@ -104,7 +86,6 @@ var e: C = MakeAdaptC();
 // CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.6: C = struct_value (%.4, %.5) [template]
 // CHECK:STDOUT:   %.6: C = struct_value (%.4, %.5) [template]
-// CHECK:STDOUT:   %.7: type = ptr_type C [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT: file {
@@ -134,26 +115,30 @@ var e: C = MakeAdaptC();
 // CHECK:STDOUT:   %.loc13_28.1: ref C = converted %.loc13_27.1, %.loc13_27.8
 // CHECK:STDOUT:   %.loc13_28.1: ref C = converted %.loc13_27.1, %.loc13_27.8
 // CHECK:STDOUT:   %.loc13_28.2: C = bind_value %.loc13_28.1
 // CHECK:STDOUT:   %.loc13_28.2: C = bind_value %.loc13_28.1
 // CHECK:STDOUT:   %a: C = bind_name a, %.loc13_28.2
 // CHECK:STDOUT:   %a: C = bind_name a, %.loc13_28.2
-// CHECK:STDOUT:   %AdaptC.ref.loc21_8: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:   %AdaptC.ref.loc15_8: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
 // CHECK:STDOUT:   %a.ref: C = name_ref a, %a
 // 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:   %AdaptC.ref.loc15_22: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:   %.loc15_19.1: AdaptC = as_compatible %a.ref
+// CHECK:STDOUT:   %.loc15_19.2: AdaptC = converted %a.ref, %.loc15_19.1
+// CHECK:STDOUT:   %b: AdaptC = bind_name b, %.loc15_19.2
+// CHECK:STDOUT:   %C.ref.loc17_8: type = name_ref C, %C.decl [template = constants.%C]
 // CHECK:STDOUT:   %b.ref: AdaptC = name_ref b, %b
 // 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:   %C.ref.loc17_17: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:   %.loc17_14.1: C = as_compatible %b.ref
+// CHECK:STDOUT:   %.loc17_14.2: C = converted %b.ref, %.loc17_14.1
+// CHECK:STDOUT:   %c: C = bind_name c, %.loc17_14.2
 // CHECK:STDOUT:   %MakeC: <function> = fn_decl @MakeC [template] {
 // CHECK:STDOUT:   %MakeC: <function> = fn_decl @MakeC [template] {
-// CHECK:STDOUT:     %C.ref.loc29: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:     %C.ref.loc19: type = name_ref C, %C.decl [template = constants.%C]
 // CHECK:STDOUT:     @MakeC.%return: ref C = var <return slot>
 // CHECK:STDOUT:     @MakeC.%return: ref C = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %MakeAdaptC: <function> = fn_decl @MakeAdaptC [template] {
 // CHECK:STDOUT:   %MakeAdaptC: <function> = fn_decl @MakeAdaptC [template] {
-// CHECK:STDOUT:     %AdaptC.ref.loc31: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:     %AdaptC.ref.loc21: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
 // CHECK:STDOUT:     @MakeAdaptC.%return: ref AdaptC = var <return slot>
 // CHECK:STDOUT:     @MakeAdaptC.%return: ref AdaptC = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %AdaptC.ref.loc37: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
+// CHECK:STDOUT:   %AdaptC.ref.loc23: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC]
 // CHECK:STDOUT:   %d.var: ref AdaptC = var d
 // CHECK:STDOUT:   %d.var: ref AdaptC = var d
 // CHECK:STDOUT:   %d: ref AdaptC = bind_name d, %d.var
 // 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:   %C.ref.loc25: type = name_ref C, %C.decl [template = constants.%C]
 // CHECK:STDOUT:   %e.var: ref C = var e
 // CHECK:STDOUT:   %e.var: ref C = var e
 // CHECK:STDOUT:   %e: ref C = bind_name e, %e.var
 // CHECK:STDOUT:   %e: ref C = bind_name e, %e.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
@@ -183,15 +168,19 @@ var e: C = MakeAdaptC();
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %MakeC.ref: <function> = name_ref MakeC, file.%MakeC [template = file.%MakeC]
 // 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:   %.loc23_5: ref AdaptC = splice_block file.%d.var {}
+// CHECK:STDOUT:   %.loc23_22: init C = call %MakeC.ref() to %.loc23_5
 // CHECK:STDOUT:   %AdaptC.ref: type = name_ref AdaptC, file.%AdaptC.decl [template = constants.%AdaptC]
 // CHECK:STDOUT:   %AdaptC.ref: type = name_ref AdaptC, file.%AdaptC.decl [template = constants.%AdaptC]
-// CHECK:STDOUT:   assign file.%d.var, <error>
+// CHECK:STDOUT:   %.loc23_25.1: init AdaptC = as_compatible %.loc23_22
+// CHECK:STDOUT:   %.loc23_25.2: init AdaptC = converted %.loc23_22, %.loc23_25.1
+// CHECK:STDOUT:   assign file.%d.var, %.loc23_25.2
 // CHECK:STDOUT:   %MakeAdaptC.ref: <function> = name_ref MakeAdaptC, file.%MakeAdaptC [template = file.%MakeAdaptC]
 // 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:   %.loc25_5: ref C = splice_block file.%e.var {}
+// CHECK:STDOUT:   %.loc25_22: init AdaptC = call %MakeAdaptC.ref() to %.loc25_5
 // CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
 // CHECK:STDOUT:   %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
-// CHECK:STDOUT:   assign file.%e.var, <error>
+// CHECK:STDOUT:   %.loc25_25.1: init C = as_compatible %.loc25_22
+// CHECK:STDOUT:   %.loc25_25.2: init C = converted %.loc25_22, %.loc25_25.1
+// CHECK:STDOUT:   assign file.%e.var, %.loc25_25.2
 // CHECK:STDOUT:   return
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -206,7 +195,6 @@ var e: C = MakeAdaptC();
 // CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.6: C = struct_value (%.4, %.5) [template]
 // CHECK:STDOUT:   %.6: C = struct_value (%.4, %.5) [template]
-// CHECK:STDOUT:   %.7: type = ptr_type C [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT: file {

+ 5 - 0
toolchain/lower/handle.cpp

@@ -60,6 +60,11 @@ auto HandleArrayInit(FunctionContext& context, SemIR::InstId inst_id,
   context.SetLocal(inst_id, context.GetValue(inst.dest_id));
   context.SetLocal(inst_id, context.GetValue(inst.dest_id));
 }
 }
 
 
+auto HandleAsCompatible(FunctionContext& context, SemIR::InstId inst_id,
+                        SemIR::AsCompatible inst) -> void {
+  context.SetLocal(inst_id, context.GetValue(inst.source_id));
+}
+
 auto HandleAssign(FunctionContext& context, SemIR::InstId /*inst_id*/,
 auto HandleAssign(FunctionContext& context, SemIR::InstId /*inst_id*/,
                   SemIR::Assign inst) -> void {
                   SemIR::Assign inst) -> void {
   auto storage_type_id = context.sem_ir().insts().Get(inst.lhs_id).type_id();
   auto storage_type_id = context.sem_ir().insts().Get(inst.lhs_id).type_id();

+ 63 - 6
toolchain/lower/testdata/class/adapt.carbon

@@ -4,23 +4,80 @@
 //
 //
 // AUTOUPDATE
 // AUTOUPDATE
 
 
+// --- adapt_class.carbon
+
+library "adapt_class" api;
+
 class PairOfInts {
 class PairOfInts {
   var a: i32;
   var a: i32;
   var b: i32;
   var b: i32;
+
+  fn Make() -> Self {
+    return {.a = 1, .b = 2};
+  }
 }
 }
 
 
 class PairAdapter {
 class PairAdapter {
   adapt PairOfInts;
   adapt PairOfInts;
+
+  fn Make() -> Self {
+    return PairOfInts.Make() as Self;
+  }
+
+  fn GetB[self: Self]() -> i32 {
+    let pi: PairOfInts = self as PairOfInts;
+    return pi.b;
+  }
+}
+
+fn Use() -> i32 {
+  var pa: PairAdapter = PairAdapter.Make();
+  return pa.GetB();
+}
+
+// --- adapt_int.carbon
+
+library "adapt_int" api;
+
+class Int {
+  adapt i32;
 }
 }
 
 
-fn MakePair() -> PairAdapter {
-  returned var v: PairAdapter;
-  return var;
+fn DoStuff(a: Int) -> Int {
+  return a;
 }
 }
 
 
-// CHECK:STDOUT: ; ModuleID = 'adapt.carbon'
-// CHECK:STDOUT: source_filename = "adapt.carbon"
+// CHECK:STDOUT: ; ModuleID = 'adapt_class.carbon'
+// CHECK:STDOUT: source_filename = "adapt_class.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: define void @MakePair(ptr sret({ i32, i32 }) %return) {
+// CHECK:STDOUT: define void @Make(ptr sret({ i32, i32 }) %return) {
+// CHECK:STDOUT:   %a = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 0
+// CHECK:STDOUT:   store i32 1, ptr %a, align 4
+// CHECK:STDOUT:   %b = getelementptr inbounds { i32, i32 }, ptr %return, i32 0, i32 1
+// CHECK:STDOUT:   store i32 2, ptr %b, align 4
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT:   ret void
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @Make.1(ptr sret({ i32, i32 }) %return) {
+// CHECK:STDOUT:   call void @Make(ptr %return)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @GetB(ptr %self) {
+// CHECK:STDOUT:   %b = getelementptr inbounds { i32, i32 }, ptr %self, i32 0, i32 1
+// CHECK:STDOUT:   %1 = load i32, ptr %b, align 4
+// CHECK:STDOUT:   ret i32 %1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @Use() {
+// CHECK:STDOUT:   %pa = alloca { i32, i32 }, align 8
+// CHECK:STDOUT:   call void @Make.1(ptr %pa)
+// CHECK:STDOUT:   %GetB = call i32 @GetB(ptr %pa)
+// CHECK:STDOUT:   ret i32 %GetB
+// CHECK:STDOUT: }
+// CHECK:STDOUT: ; ModuleID = 'adapt_int.carbon'
+// CHECK:STDOUT: source_filename = "adapt_int.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @DoStuff(i32 %a) {
+// CHECK:STDOUT:   ret i32 %a
+// CHECK:STDOUT: }

+ 2 - 1
toolchain/sem_ir/class.h

@@ -68,7 +68,8 @@ struct Class : public Printable<Class> {
   // The following members are set at the `}` of the class definition.
   // The following members are set at the `}` of the class definition.
 
 
   // The object representation type to use for this class. This is valid once
   // The object representation type to use for this class. This is valid once
-  // the class is defined.
+  // the class is defined. For an adapter, this is the non-adapter type that
+  // this class directly or transitively adapts.
   TypeId object_repr_id = TypeId::Invalid;
   TypeId object_repr_id = TypeId::Invalid;
 };
 };
 
 

+ 7 - 0
toolchain/sem_ir/file.cpp

@@ -226,6 +226,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
     case AddrPattern::Kind:
     case AddrPattern::Kind:
     case ArrayIndex::Kind:
     case ArrayIndex::Kind:
     case ArrayInit::Kind:
     case ArrayInit::Kind:
+    case AsCompatible::Kind:
     case Assign::Kind:
     case Assign::Kind:
     case AssociatedConstantDecl::Kind:
     case AssociatedConstantDecl::Kind:
     case AssociatedEntity::Kind:
     case AssociatedEntity::Kind:
@@ -483,6 +484,7 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
       case AddrPattern::Kind:
       case AddrPattern::Kind:
       case ArrayIndex::Kind:
       case ArrayIndex::Kind:
       case ArrayInit::Kind:
       case ArrayInit::Kind:
+      case AsCompatible::Kind:
       case Assign::Kind:
       case Assign::Kind:
       case AssociatedConstantDecl::Kind:
       case AssociatedConstantDecl::Kind:
       case AssociatedEntity::Kind:
       case AssociatedEntity::Kind:
@@ -587,6 +589,11 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
         continue;
         continue;
       }
       }
 
 
+      case CARBON_KIND(AsCompatible inst): {
+        inst_id = inst.source_id;
+        continue;
+      }
+
       case CARBON_KIND(BindAlias inst): {
       case CARBON_KIND(BindAlias inst): {
         inst_id = inst.value_id;
         inst_id = inst.value_id;
         continue;
         continue;

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -23,6 +23,7 @@ CARBON_SEM_IR_INST_KIND(AddrPattern)
 CARBON_SEM_IR_INST_KIND(ArrayIndex)
 CARBON_SEM_IR_INST_KIND(ArrayIndex)
 CARBON_SEM_IR_INST_KIND(ArrayInit)
 CARBON_SEM_IR_INST_KIND(ArrayInit)
 CARBON_SEM_IR_INST_KIND(ArrayType)
 CARBON_SEM_IR_INST_KIND(ArrayType)
+CARBON_SEM_IR_INST_KIND(AsCompatible)
 CARBON_SEM_IR_INST_KIND(Assign)
 CARBON_SEM_IR_INST_KIND(Assign)
 CARBON_SEM_IR_INST_KIND(AssociatedConstantDecl)
 CARBON_SEM_IR_INST_KIND(AssociatedConstantDecl)
 CARBON_SEM_IR_INST_KIND(AssociatedEntity)
 CARBON_SEM_IR_INST_KIND(AssociatedEntity)

+ 9 - 0
toolchain/sem_ir/typed_insts.h

@@ -150,6 +150,15 @@ struct ArrayType {
   TypeId element_type_id;
   TypeId element_type_id;
 };
 };
 
 
+// Perform a no-op conversion to a compatible type.
+struct AsCompatible {
+  static constexpr auto Kind =
+      InstKind::AsCompatible.Define<Parse::NodeId>("as_compatible");
+
+  TypeId type_id;
+  InstId source_id;
+};
+
 // Performs a source-level initialization or assignment of `lhs_id` from
 // Performs a source-level initialization or assignment of `lhs_id` from
 // `rhs_id`. This finishes initialization of `lhs_id` in the same way as
 // `rhs_id`. This finishes initialization of `lhs_id` in the same way as
 // `InitializeFrom`.
 // `InitializeFrom`.