Skip to content

Commit

Permalink
Support builtin conversions of adaptor classes
Browse files Browse the repository at this point in the history
When doing a builtin conversion of an adaptor class:
- if there's no conversion happening on the adaptor class itself,
- if the conversion is from and to the same adaptor class type,

Then we attempt the builtin conversion step on the foundation type as
well.

To do this, we must convert the value from its adaptor to its
foundation type. Then perform the conversion. And convert the converted
(foundation) value back to the adaptor class type.

This might sound funny since they are the same type, so there can be no
conversion happening. But in particular, this recursion allows copy
construction of the elements of a tuple or struct with more than one
element that is the foundation type of a class.

A struct or tuple with more than one element is no longer initialized
from a value by copy; its ValueRepr becomes a pointer. So it requires
a builtin conversion to perform the copy on its interior elements. This
already happens for these types in PerformBuiltinConversion, but now
will also be done for adaptors of these types.
  • Loading branch information
danakj committed Dec 9, 2024
1 parent cd1ecf1 commit 64bb4c7
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 44 deletions.
47 changes: 43 additions & 4 deletions toolchain/check/convert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,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 GetCompatibleFoundationType(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 @@ -782,9 +782,11 @@ 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) {
auto target_foundation_id =
GetCompatibleFoundationType(context, target.type_id);
auto value_foundation_id =
GetCompatibleFoundationType(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 Expand Up @@ -905,6 +907,43 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
}
}

// When converting between adaptor classes of the same type (we don't
// implicitly convert between different adaptors even if they adapt the same
// foundation type), and when no other conversion was available on the adaptor
// class itself, we do the builtin conversion step on their foundation type.
// To do so, we have to insert a conversion up to the foundation and back
// down.
if (value_type_id == target.type_id) {
auto foundation_value_type_id =
GetCompatibleFoundationType(context, value_type_id);
if (foundation_value_type_id != value_type_id) {
auto foundation_target_type_id =
GetCompatibleFoundationType(context, target.type_id);
CARBON_CHECK(foundation_value_type_id == foundation_target_type_id,
"Same type ids have different foundation type ids");

auto foundation_value_id = context.AddInst<SemIR::AsCompatible>(
loc_id, {.type_id = foundation_value_type_id, .source_id = value_id});
// We recurse here, but at most one time, since we have walked the full
// adapt chain to the root foundation type before recursing.
auto converted_value_id =
PerformBuiltinConversion(context, loc_id, foundation_value_id,
{
.kind = target.kind,
.type_id = foundation_target_type_id,
.init_id = target.init_id,
.init_block = target.init_block,
});
auto target_value_id = context.AddInst<SemIR::AsCompatible>(
loc_id, {.type_id = target.type_id, .source_id = converted_value_id});
// If no builtin conversion actually happened, we orphan the two
// AsCompatible instructions and don't refer to them. If a conversion did
// happen, then we return the result of the second AsCompatible.
return converted_value_id == foundation_value_id ? value_id
: target_value_id;
}
}

// No builtin conversion applies.
return value_id;
}
Expand Down
6 changes: 6 additions & 0 deletions toolchain/check/testdata/as/adapter_conversion.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,8 @@ var b: B = {.x = 1} as B;
// 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_46.1: ref %A = as_compatible %.loc13_42.2
// CHECK:STDOUT: %.loc13_46.2: ref %B = as_compatible %.loc13_46.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]
Expand Down Expand Up @@ -592,6 +594,8 @@ var b: B = {.x = 1} as B;
// 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_45.1: ref %A = as_compatible %.loc24_41.2
// CHECK:STDOUT: %.loc24_45.2: ref %B = as_compatible %.loc24_45.1
// CHECK:STDOUT: %.loc24_41.3: %B = bind_value %.loc24_41.2
// CHECK:STDOUT: assign file.%b_init.var, <error>
// CHECK:STDOUT: return
Expand Down Expand Up @@ -667,6 +671,8 @@ var b: B = {.x = 1} as B;
// 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: %.loc21_25.1: %A = as_compatible %.loc21_21 [template = <error>]
// CHECK:STDOUT: %.loc21_25.2: %B = as_compatible %.loc21_25.1 [template = <error>]
// CHECK:STDOUT: assign file.%b.var, <error>
// CHECK:STDOUT: return
// CHECK:STDOUT: }
Expand Down
Loading

0 comments on commit 64bb4c7

Please sign in to comment.