From 462bcd9f6e75abed827045c948c7fee8ee0bd8de Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 17 Apr 2024 20:11:27 +0200 Subject: [PATCH] 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> --- toolchain/check/context.cpp | 16 +- toolchain/check/convert.cpp | 49 +- toolchain/check/eval.cpp | 3 + toolchain/check/handle_class.cpp | 23 +- .../testdata/as/adapter_conversion.carbon | 448 ++++++++++++++++++ toolchain/check/testdata/class/adapt.carbon | 1 - .../check/testdata/class/extend_adapt.carbon | 5 +- .../check/testdata/class/init_adapt.carbon | 66 ++- toolchain/lower/handle.cpp | 5 + toolchain/lower/testdata/class/adapt.carbon | 69 ++- toolchain/sem_ir/class.h | 3 +- toolchain/sem_ir/file.cpp | 7 + toolchain/sem_ir/inst_kind.def | 1 + toolchain/sem_ir/typed_insts.h | 9 + 14 files changed, 640 insertions(+), 65 deletions(-) create mode 100644 toolchain/check/testdata/as/adapter_conversion.carbon diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index c2483595d952..bd4a7de2a536 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -957,6 +957,7 @@ class TypeCompleter { case SemIR::AddrPattern::Kind: case SemIR::ArrayIndex::Kind: case SemIR::ArrayInit::Kind: + case SemIR::AsCompatible::Kind: case SemIR::Assign::Kind: case SemIR::AssociatedConstantDecl::Kind: case SemIR::AssociatedEntity::Kind: @@ -1031,13 +1032,18 @@ class TypeCompleter { return BuildTupleTypeValueRepr(type_id, tuple_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: 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: { // TODO: Should we model the value representation as a witness? diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index ae0a8004035e..fc0c334c339b 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -14,6 +14,7 @@ #include "toolchain/sem_ir/copy_on_write_block.h" #include "toolchain/sem_ir/file.h" #include "toolchain/sem_ir/inst.h" +#include "toolchain/sem_ir/typed_insts.h" namespace Carbon::Check { @@ -26,6 +27,10 @@ static auto FindReturnSlotForInitializer(SemIR::File& sem_ir, while (true) { SemIR::Inst init_untyped = sem_ir.insts().Get(init_id); CARBON_KIND_SWITCH(init_untyped) { + case CARBON_KIND(SemIR::AsCompatible init): { + init_id = init.source_id; + continue; + } case CARBON_KIND(SemIR::Converted init): { init_id = init.result_id; 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(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, SemIR::InstId value_id, 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 // converts to Ui. if (auto target_tuple_type = target_type_inst.TryAs()) { @@ -752,8 +793,12 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id, if (auto target_class_type = target_type_inst.TryAs()) { if (auto src_struct_type = sem_ir.types().TryGetAs(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. diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 0b843931e14f..64c7376903f4 100644 --- a/toolchain/check/eval.cpp +++ b/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); // 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): { return context.constant_values().Get(typed_inst.value_id); } diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index ec90b790ee16..cded323e3d04 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -457,13 +457,22 @@ auto HandleClassDefinition(Context& context, .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(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(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(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 { class_info.object_repr_id = context.GetStructType(fields_id); diff --git a/toolchain/check/testdata/as/adapter_conversion.carbon b/toolchain/check/testdata/as/adapter_conversion.carbon new file mode 100644 index 000000000000..9c9f45580b2f --- /dev/null +++ b/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 [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 [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: = field_decl x, element0 [template] +// CHECK:STDOUT: %.loc6: = field_decl y, element1 [template] +// CHECK:STDOUT: %Make: = 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 +// 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: = 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 [template] { +// CHECK:STDOUT: .Core = %Core +// CHECK:STDOUT: .A = %A.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core: = 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 [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 [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 [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 [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: = field_decl x, element0 [template] +// CHECK:STDOUT: %.loc6: = 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, +// 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 [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 [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: = 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, +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/adapt.carbon b/toolchain/check/testdata/class/adapt.carbon index c80a9949b279..75b99d946b8e 100644 --- a/toolchain/check/testdata/class/adapt.carbon +++ b/toolchain/check/testdata/class/adapt.carbon @@ -99,7 +99,6 @@ fn F(a: AdaptNotExtend) { // 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 { diff --git a/toolchain/check/testdata/class/extend_adapt.carbon b/toolchain/check/testdata/class/extend_adapt.carbon index 43818cdd95ac..b62dd07636e1 100644 --- a/toolchain/check/testdata/class/extend_adapt.carbon +++ b/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: %.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: %.4: type = tuple_type () [template] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -178,7 +177,6 @@ class StructAdapter { // 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 { @@ -238,7 +236,6 @@ class StructAdapter { // 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 { diff --git a/toolchain/check/testdata/class/init_adapt.carbon b/toolchain/check/testdata/class/init_adapt.carbon index c72cff81aeaa..a685ae43156d 100644 --- a/toolchain/check/testdata/class/init_adapt.carbon +++ b/toolchain/check/testdata/class/init_adapt.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE -// --- fail_todo_init_adapt.carbon +// --- init_adapt.carbon -library "fail_todo_init_adapt" api; +library "init_adapt" api; class C { var a: i32; @@ -19,34 +19,16 @@ class AdaptC { 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 @@ -93,7 +75,7 @@ var d: AdaptC = MakeC(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~ var e: C = MakeAdaptC(); -// CHECK:STDOUT: --- fail_todo_init_adapt.carbon +// CHECK:STDOUT: --- init_adapt.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { // 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: %.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 { @@ -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.2: C = bind_value %.loc13_28.1 // 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: %AdaptC.ref.loc21_22: type = name_ref AdaptC, %AdaptC.decl [template = constants.%AdaptC] -// CHECK:STDOUT: %b: AdaptC = bind_name b, -// 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: %C.ref.loc27_17: type = name_ref C, %C.decl [template = constants.%C] -// CHECK:STDOUT: %c: C = bind_name c, +// 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: = 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 // CHECK:STDOUT: } // CHECK:STDOUT: %MakeAdaptC: = 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 // 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: 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: ref C = bind_name e, %e.var // CHECK:STDOUT: } @@ -183,15 +168,19 @@ var e: C = MakeAdaptC(); // CHECK:STDOUT: fn @__global_init() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %MakeC.ref: = 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: assign file.%d.var, +// 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: = 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: assign file.%e.var, +// 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: } // CHECK:STDOUT: @@ -206,7 +195,6 @@ var e: C = MakeAdaptC(); // 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 { diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index 93e585ede376..ac3c40603305 100644 --- a/toolchain/lower/handle.cpp +++ b/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)); } +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*/, SemIR::Assign inst) -> void { auto storage_type_id = context.sem_ir().insts().Get(inst.lhs_id).type_id(); diff --git a/toolchain/lower/testdata/class/adapt.carbon b/toolchain/lower/testdata/class/adapt.carbon index 95cd5fbaedf4..87d1c69919e6 100644 --- a/toolchain/lower/testdata/class/adapt.carbon +++ b/toolchain/lower/testdata/class/adapt.carbon @@ -4,23 +4,80 @@ // // AUTOUPDATE +// --- adapt_class.carbon + +library "adapt_class" api; + class PairOfInts { var a: i32; var b: i32; + + fn Make() -> Self { + return {.a = 1, .b = 2}; + } } class PairAdapter { 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 MakePair() -> PairAdapter { - returned var v: PairAdapter; - return var; +fn Use() -> i32 { + var pa: PairAdapter = PairAdapter.Make(); + return pa.GetB(); } -// CHECK:STDOUT: ; ModuleID = 'adapt.carbon' -// CHECK:STDOUT: source_filename = "adapt.carbon" +// --- adapt_int.carbon + +library "adapt_int" api; + +class Int { + adapt i32; +} + +fn DoStuff(a: Int) -> Int { + return a; +} + +// CHECK:STDOUT: ; ModuleID = 'adapt_class.carbon' +// CHECK:STDOUT: source_filename = "adapt_class.carbon" // 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: } +// 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: } diff --git a/toolchain/sem_ir/class.h b/toolchain/sem_ir/class.h index b8c63d3b1adb..05ae96398aa8 100644 --- a/toolchain/sem_ir/class.h +++ b/toolchain/sem_ir/class.h @@ -68,7 +68,8 @@ struct Class : public Printable { // 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 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; }; diff --git a/toolchain/sem_ir/file.cpp b/toolchain/sem_ir/file.cpp index 7c9422ac735d..d2d6e9ac0bc3 100644 --- a/toolchain/sem_ir/file.cpp +++ b/toolchain/sem_ir/file.cpp @@ -226,6 +226,7 @@ static auto GetTypePrecedence(InstKind kind) -> int { case AddrPattern::Kind: case ArrayIndex::Kind: case ArrayInit::Kind: + case AsCompatible::Kind: case Assign::Kind: case AssociatedConstantDecl::Kind: case AssociatedEntity::Kind: @@ -483,6 +484,7 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir, case AddrPattern::Kind: case ArrayIndex::Kind: case ArrayInit::Kind: + case AsCompatible::Kind: case Assign::Kind: case AssociatedConstantDecl::Kind: case AssociatedEntity::Kind: @@ -587,6 +589,11 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory { continue; } + case CARBON_KIND(AsCompatible inst): { + inst_id = inst.source_id; + continue; + } + case CARBON_KIND(BindAlias inst): { inst_id = inst.value_id; continue; diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index d1f4d1aa40d1..7e855cb2cfd1 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/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(ArrayInit) CARBON_SEM_IR_INST_KIND(ArrayType) +CARBON_SEM_IR_INST_KIND(AsCompatible) CARBON_SEM_IR_INST_KIND(Assign) CARBON_SEM_IR_INST_KIND(AssociatedConstantDecl) CARBON_SEM_IR_INST_KIND(AssociatedEntity) diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index 9a90947d5c76..0b627d9dcddb 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -150,6 +150,15 @@ struct ArrayType { TypeId element_type_id; }; +// Perform a no-op conversion to a compatible type. +struct AsCompatible { + static constexpr auto Kind = + InstKind::AsCompatible.Define("as_compatible"); + + TypeId type_id; + InstId source_id; +}; + // Performs a source-level initialization or assignment of `lhs_id` from // `rhs_id`. This finishes initialization of `lhs_id` in the same way as // `InitializeFrom`.