Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support builtin conversions of adapter classes #4655

Open
wants to merge 14 commits into
base: trunk
Choose a base branch
from
66 changes: 62 additions & 4 deletions toolchain/check/convert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "llvm/ADT/STLExtras.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/context.h"
#include "toolchain/check/diagnostic_helpers.h"
#include "toolchain/check/operator.h"
#include "toolchain/check/pattern_match.h"
#include "toolchain/diagnostics/format_providers.h"
Expand Down Expand Up @@ -722,7 +723,7 @@ static auto CanUseValueOfInitializer(const SemIR::File& sem_ir,
}

// Returns the non-adapter type that is compatible with the specified type.
static auto GetCompatibleBaseType(Context& context, SemIR::TypeId type_id)
static auto GetFoundationType(Context& context, SemIR::TypeId type_id)
-> SemIR::TypeId {
// If the type is an adapter, its object representation type is its compatible
// non-adapter type.
Expand Down Expand Up @@ -792,14 +793,71 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
return context.AddInst<SemIR::ValueOfInitializer>(
loc_id, {.type_id = value_type_id, .init_id = value_id});
}

// PerformBuiltinConversion converts each part of a tuple or struct, even
// when the types are the same. This is not done for classes since they have
// to define their conversions as part of their api.
//
// If a class adapts a tuple or struct, we convert each of its parts when
// there's no other conversion going on (the source and target types are the
// same). To do so, we have to insert a conversion of the value up to the
// foundation and back down, and a conversion of the initializing object if
// there is one.
//
// Implementation note: We do the conversion through a call to
// PerformBuiltinConversion() call rather than a Convert() call to avoid
// extraneous `converted` semir instructions on the adapted types, and as a
// shortcut to doing the explicit calls to walk the parts of the
// tuple/struct which happens inside PerformBuiltinConversion().
if (auto foundation_type_id = GetFoundationType(context, value_type_id);
foundation_type_id != value_type_id &&
(context.types().Is<SemIR::TupleType>(foundation_type_id) ||
context.types().Is<SemIR::StructType>(foundation_type_id))) {
auto foundation_value_id = context.AddInst<SemIR::AsCompatible>(
loc_id, {.type_id = foundation_type_id, .source_id = value_id});

auto foundation_init_id = target.init_id;
if (foundation_init_id != SemIR::InstId::Invalid) {
foundation_init_id = target.init_block->AddInst<SemIR::AsCompatible>(
loc_id,
{.type_id = foundation_type_id, .source_id = target.init_id});
}

{
// While the types are the same, the conversion can still fail if it
// performs a copy while converting the value to another category, and
// the type (or some part of it) is not copyable.
DiagnosticAnnotationScope annotate_diagnostics(
&context.emitter(), [&](auto& builder) {
CARBON_DIAGNOSTIC(InCopy, Note, "in copy of `{0}`", TypeOfInstId);
builder.Note(value_id, InCopy, value_id);
});

foundation_value_id =
PerformBuiltinConversion(context, loc_id, foundation_value_id,
{
.kind = target.kind,
.type_id = foundation_type_id,
.init_id = foundation_init_id,
.init_block = target.init_block,
});
if (foundation_value_id == SemIR::ErrorInst::SingletonInstId) {
return SemIR::ErrorInst::SingletonInstId;
}
}

return context.AddInst<SemIR::AsCompatible>(
loc_id,
{.type_id = target.type_id, .source_id = foundation_value_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) {
auto target_foundation_id = GetFoundationType(context, target.type_id);
auto value_foundation_id = GetFoundationType(context, value_type_id);
if (target_foundation_id == value_foundation_id) {
// For a struct or tuple literal, perform a category conversion if
// necessary.
if (SemIR::GetExprCategory(context.sem_ir(), value_id) ==
Expand Down
247 changes: 0 additions & 247 deletions toolchain/check/testdata/as/adapter_conversion.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -56,55 +56,6 @@ class D { adapt C; }

let d: D = {} as D;

// --- fail_init_class.carbon

library "[[@TEST_NAME]]";

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` [CopyOfUncopyableType]
// 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 "[[@TEST_NAME]]";

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+6]]:12: error: cannot convert from `{.x: Core.IntLiteral}` to `B` with `as` [ExplicitAsConversionFailure]
// CHECK:STDERR: var b: B = {.x = 1} as B;
// CHECK:STDERR: ^~~~~~~~~~~~~
// CHECK:STDERR: fail_adapt_init_from_struct.carbon:[[@LINE+3]]:12: note: type `{.x: Core.IntLiteral}` does not implement interface `Core.As(B)` [MissingImplInMemberAccessNote]
// 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 {
Expand Down Expand Up @@ -425,201 +376,3 @@ var b: B = {.x = 1} as B;
// CHECK:STDOUT: return
// 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: %int_32: Core.IntLiteral = int_value 32 [template]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [template]
// CHECK:STDOUT: %A.elem: type = unbound_element_type %A, %i32 [template]
// CHECK:STDOUT: %struct_type.x.y.1: type = struct_type {.x: %i32, .y: %i32} [template]
// CHECK:STDOUT: %complete_type.3: <witness> = complete_type_witness %struct_type.x.y.1 [template]
// CHECK:STDOUT: %B: type = class_type @B [template]
// CHECK:STDOUT: %int_1.1: Core.IntLiteral = int_value 1 [template]
// CHECK:STDOUT: %int_2.1: Core.IntLiteral = int_value 2 [template]
// CHECK:STDOUT: %struct_type.x.y.2: type = struct_type {.x: Core.IntLiteral, .y: Core.IntLiteral} [template]
// CHECK:STDOUT: %Convert.type.2: type = fn_type @Convert.1, @ImplicitAs(%i32) [template]
// CHECK:STDOUT: %Convert.type.10: type = fn_type @Convert.2, @impl.1(%int_32) [template]
// CHECK:STDOUT: %Convert.10: %Convert.type.10 = struct_value () [template]
// CHECK:STDOUT: %interface.19: <witness> = interface_witness (%Convert.10) [template]
// CHECK:STDOUT: %Convert.bound.1: <bound method> = bound_method %int_1.1, %Convert.10 [template]
// CHECK:STDOUT: %Convert.specific_fn.1: <specific function> = specific_function %Convert.bound.1, @Convert.2(%int_32) [template]
// CHECK:STDOUT: %int_1.2: %i32 = int_value 1 [template]
// CHECK:STDOUT: %Convert.bound.2: <bound method> = bound_method %int_2.1, %Convert.10 [template]
// CHECK:STDOUT: %Convert.specific_fn.2: <specific function> = specific_function %Convert.bound.2, @Convert.2(%int_32) [template]
// CHECK:STDOUT: %int_2.2: %i32 = int_value 2 [template]
// CHECK:STDOUT: %A.val: %A = struct_value (%int_1.2, %int_2.2) [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: .Int = %import_ref.1
// CHECK:STDOUT: .ImplicitAs = %import_ref.5
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Core = imports.%Core
// CHECK:STDOUT: .A = %A.decl
// CHECK:STDOUT: .B = %B.decl
// CHECK:STDOUT: .b_value = @__global_init.%b_value
// CHECK:STDOUT: .b_init = %b_init
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// 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_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: %A.elem = field_decl x, element0 [template]
// CHECK:STDOUT: %.loc6: %A.elem = field_decl y, element1 [template]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %struct_type.x.y.1 [template = constants.%complete_type.3]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%A
// CHECK:STDOUT: .x = %.loc5
// CHECK:STDOUT: .y = %.loc6
// CHECK:STDOUT: complete_type_witness = %complete_type
// 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.ref [template]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %struct_type.x.y.1 [template = constants.%complete_type.3]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%B
// CHECK:STDOUT: complete_type_witness = %complete_type
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @__global_init() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %int_1.loc13: Core.IntLiteral = int_value 1 [template = constants.%int_1.1]
// CHECK:STDOUT: %int_2.loc13: Core.IntLiteral = int_value 2 [template = constants.%int_2.1]
// CHECK:STDOUT: %.loc13_34.1: %struct_type.x.y.2 = struct_literal (%int_1.loc13, %int_2.loc13)
// CHECK:STDOUT: %A.ref.loc13: type = name_ref A, file.%A.decl [template = constants.%A]
// CHECK:STDOUT: %impl.elem0.loc13_34.1: %Convert.type.2 = interface_witness_access constants.%interface.19, element0 [template = constants.%Convert.10]
// CHECK:STDOUT: %Convert.bound.loc13_34.1: <bound method> = bound_method %int_1.loc13, %impl.elem0.loc13_34.1 [template = constants.%Convert.bound.1]
// CHECK:STDOUT: %Convert.specific_fn.loc13_34.1: <specific function> = specific_function %Convert.bound.loc13_34.1, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.1]
// CHECK:STDOUT: %int.convert_checked.loc13_34.1: init %i32 = call %Convert.specific_fn.loc13_34.1(%int_1.loc13) [template = constants.%int_1.2]
// CHECK:STDOUT: %.loc13_34.2: init %i32 = converted %int_1.loc13, %int.convert_checked.loc13_34.1 [template = constants.%int_1.2]
// CHECK:STDOUT: %.loc13_34.3: ref %A = temporary_storage
// CHECK:STDOUT: %.loc13_34.4: ref %i32 = class_element_access %.loc13_34.3, element0
// CHECK:STDOUT: %.loc13_34.5: init %i32 = initialize_from %.loc13_34.2 to %.loc13_34.4 [template = constants.%int_1.2]
// CHECK:STDOUT: %impl.elem0.loc13_34.2: %Convert.type.2 = interface_witness_access constants.%interface.19, element0 [template = constants.%Convert.10]
// CHECK:STDOUT: %Convert.bound.loc13_34.2: <bound method> = bound_method %int_2.loc13, %impl.elem0.loc13_34.2 [template = constants.%Convert.bound.2]
// CHECK:STDOUT: %Convert.specific_fn.loc13_34.2: <specific function> = specific_function %Convert.bound.loc13_34.2, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.2]
// CHECK:STDOUT: %int.convert_checked.loc13_34.2: init %i32 = call %Convert.specific_fn.loc13_34.2(%int_2.loc13) [template = constants.%int_2.2]
// CHECK:STDOUT: %.loc13_34.6: init %i32 = converted %int_2.loc13, %int.convert_checked.loc13_34.2 [template = constants.%int_2.2]
// CHECK:STDOUT: %.loc13_34.7: ref %i32 = class_element_access %.loc13_34.3, element1
// CHECK:STDOUT: %.loc13_34.8: init %i32 = initialize_from %.loc13_34.6 to %.loc13_34.7 [template = constants.%int_2.2]
// CHECK:STDOUT: %.loc13_34.9: init %A = class_init (%.loc13_34.5, %.loc13_34.8), %.loc13_34.3 [template = constants.%A.val]
// CHECK:STDOUT: %.loc13_34.10: ref %A = temporary %.loc13_34.3, %.loc13_34.9
// CHECK:STDOUT: %.loc13_36: ref %A = converted %.loc13_34.1, %.loc13_34.10
// CHECK:STDOUT: %B.ref.loc13: type = name_ref B, file.%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: %int_1.loc24: Core.IntLiteral = int_value 1 [template = constants.%int_1.1]
// CHECK:STDOUT: %int_2.loc24: Core.IntLiteral = int_value 2 [template = constants.%int_2.1]
// CHECK:STDOUT: %.loc24_33.1: %struct_type.x.y.2 = struct_literal (%int_1.loc24, %int_2.loc24)
// CHECK:STDOUT: %A.ref.loc24: type = name_ref A, file.%A.decl [template = constants.%A]
// CHECK:STDOUT: %impl.elem0.loc24_33.1: %Convert.type.2 = interface_witness_access constants.%interface.19, element0 [template = constants.%Convert.10]
// CHECK:STDOUT: %Convert.bound.loc24_33.1: <bound method> = bound_method %int_1.loc24, %impl.elem0.loc24_33.1 [template = constants.%Convert.bound.1]
// CHECK:STDOUT: %Convert.specific_fn.loc24_33.1: <specific function> = specific_function %Convert.bound.loc24_33.1, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.1]
// CHECK:STDOUT: %int.convert_checked.loc24_33.1: init %i32 = call %Convert.specific_fn.loc24_33.1(%int_1.loc24) [template = constants.%int_1.2]
// CHECK:STDOUT: %.loc24_33.2: init %i32 = converted %int_1.loc24, %int.convert_checked.loc24_33.1 [template = constants.%int_1.2]
// CHECK:STDOUT: %.loc24_33.3: ref %A = temporary_storage
// CHECK:STDOUT: %.loc24_33.4: ref %i32 = class_element_access %.loc24_33.3, element0
// CHECK:STDOUT: %.loc24_33.5: init %i32 = initialize_from %.loc24_33.2 to %.loc24_33.4 [template = constants.%int_1.2]
// CHECK:STDOUT: %impl.elem0.loc24_33.2: %Convert.type.2 = interface_witness_access constants.%interface.19, element0 [template = constants.%Convert.10]
// CHECK:STDOUT: %Convert.bound.loc24_33.2: <bound method> = bound_method %int_2.loc24, %impl.elem0.loc24_33.2 [template = constants.%Convert.bound.2]
// CHECK:STDOUT: %Convert.specific_fn.loc24_33.2: <specific function> = specific_function %Convert.bound.loc24_33.2, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.2]
// CHECK:STDOUT: %int.convert_checked.loc24_33.2: init %i32 = call %Convert.specific_fn.loc24_33.2(%int_2.loc24) [template = constants.%int_2.2]
// CHECK:STDOUT: %.loc24_33.6: init %i32 = converted %int_2.loc24, %int.convert_checked.loc24_33.2 [template = constants.%int_2.2]
// CHECK:STDOUT: %.loc24_33.7: ref %i32 = class_element_access %.loc24_33.3, element1
// CHECK:STDOUT: %.loc24_33.8: init %i32 = initialize_from %.loc24_33.6 to %.loc24_33.7 [template = constants.%int_2.2]
// CHECK:STDOUT: %.loc24_33.9: init %A = class_init (%.loc24_33.5, %.loc24_33.8), %.loc24_33.3 [template = constants.%A.val]
// CHECK:STDOUT: %.loc24_33.10: ref %A = temporary %.loc24_33.3, %.loc24_33.9
// CHECK:STDOUT: %.loc24_35: ref %A = converted %.loc24_33.1, %.loc24_33.10
// CHECK:STDOUT: %B.ref.loc24: 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: %int_32: Core.IntLiteral = int_value 32 [template]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [template]
// CHECK:STDOUT: %A.elem: type = unbound_element_type %A, %i32 [template]
// CHECK:STDOUT: %struct_type.x.1: type = struct_type {.x: %i32} [template]
// CHECK:STDOUT: %complete_type.3: <witness> = complete_type_witness %struct_type.x.1 [template]
// CHECK:STDOUT: %B: type = class_type @B [template]
// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [template]
// CHECK:STDOUT: %struct_type.x.2: type = struct_type {.x: Core.IntLiteral} [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: .Int = %import_ref.1
// CHECK:STDOUT: .As = %import_ref.5
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Core = imports.%Core
// CHECK:STDOUT: .A = %A.decl
// CHECK:STDOUT: .B = %B.decl
// CHECK:STDOUT: .b = %b
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// 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.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: %A.elem = field_decl x, element0 [template]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %struct_type.x.1 [template = constants.%complete_type.3]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%A
// CHECK:STDOUT: .x = %.loc5
// CHECK:STDOUT: complete_type_witness = %complete_type
// 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.ref [template]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %struct_type.x.1 [template = constants.%complete_type.3]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%B
// CHECK:STDOUT: complete_type_witness = %complete_type
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @__global_init() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [template = constants.%int_1]
// CHECK:STDOUT: %.loc21_19: %struct_type.x.2 = struct_literal (%int_1)
// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
// CHECK:STDOUT: %.loc21_21: %B = converted %.loc21_19, <error> [template = <error>]
// CHECK:STDOUT: assign file.%b.var, <error>
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT:
Loading
Loading