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`.