Support initializing a class from a struct. (#3358)

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
This commit is contained in:
Richard Smith
2023-11-02 20:27:01 +00:00
committed by GitHub
co-authored by Jon Ross-Perkins
parent 737162cc8f
commit 545a5b3679
17 changed files with 511 additions and 73 deletions
+93 -22
View File
@@ -29,6 +29,7 @@ static auto FindReturnSlotForInitializer(SemIR::File& sem_ir,
default:
CARBON_FATAL() << "Initialization from unexpected inst " << init;
case SemIR::ClassInit::Kind:
case SemIR::StructInit::Kind:
case SemIR::TupleInit::Kind:
// TODO: Track a return slot for these initializers.
@@ -228,8 +229,9 @@ class CopyOnWriteBlock {
};
} // namespace
// Performs a conversion from a tuple to an array type. Does not perform a
// final conversion to the requested expression category.
// Performs a conversion from a tuple to an array type. This function only
// converts the type, and does not perform a final conversion to the requested
// expression category.
static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
SemIR::ArrayType array_type,
SemIR::InstId value_id, ConversionTarget target)
@@ -310,8 +312,9 @@ static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
sem_ir.inst_blocks().Add(inits)});
}
// Performs a conversion from a tuple to a tuple type. Does not perform a
// final conversion to the requested expression category.
// Performs a conversion from a tuple to a tuple type. This function only
// converts the type, and does not perform a final conversion to the requested
// expression category.
static auto ConvertTupleToTuple(Context& context, SemIR::TupleType src_type,
SemIR::TupleType dest_type,
SemIR::InstId value_id, ConversionTarget target)
@@ -383,12 +386,14 @@ static auto ConvertTupleToTuple(Context& context, SemIR::TupleType src_type,
new_block.id()});
}
// Performs a conversion from a struct to a struct type. Does not perform a
// final conversion to the requested expression category.
static auto ConvertStructToStruct(Context& context, SemIR::StructType src_type,
SemIR::StructType dest_type,
SemIR::InstId value_id,
ConversionTarget target) -> SemIR::InstId {
// Common implementation for ConvertStructToStruct and ConvertStructToClass.
template <typename TargetAccessInstT>
static auto ConvertStructToStructOrClass(Context& context,
SemIR::StructType src_type,
SemIR::StructType dest_type,
SemIR::InstId value_id,
ConversionTarget target, bool is_class)
-> SemIR::InstId {
auto& sem_ir = context.sem_ir();
auto src_elem_fields = sem_ir.inst_blocks().Get(src_type.fields_id);
auto dest_elem_fields = sem_ir.inst_blocks().Get(dest_type.fields_id);
@@ -412,11 +417,13 @@ static auto ConvertStructToStruct(Context& context, SemIR::StructType src_type,
// exist in the destination or vice versa in the diagnostic.
if (src_elem_fields.size() != dest_elem_fields.size()) {
CARBON_DIAGNOSTIC(StructInitElementCountMismatch, Error,
"Cannot initialize struct of {0} element(s) from struct "
"with {1} element(s).",
size_t, size_t);
context.emitter().Emit(value.parse_node(), StructInitElementCountMismatch,
dest_elem_fields.size(), src_elem_fields.size());
"Cannot initialize {0} with {1} field(s) from struct "
"with {2} field(s).",
llvm::StringLiteral, size_t, size_t);
context.emitter().Emit(
value.parse_node(), StructInitElementCountMismatch,
is_class ? llvm::StringLiteral("class") : llvm::StringLiteral("struct"),
dest_elem_fields.size(), src_elem_fields.size());
return SemIR::InstId::BuiltinError;
}
@@ -484,7 +491,7 @@ static auto ConvertStructToStruct(Context& context, SemIR::StructType src_type,
// TODO: This call recurses back into conversion. Switch to an iterative
// approach.
auto init_id =
ConvertAggregateElement<SemIR::StructAccess, SemIR::StructAccess>(
ConvertAggregateElement<SemIR::StructAccess, TargetAccessInstT>(
context, value.parse_node(), value_id, src_field.field_type_id,
literal_elems, inner_kind, target.init_id, dest_field.field_type_id,
target.init_block, src_field_index);
@@ -494,12 +501,63 @@ static auto ConvertStructToStruct(Context& context, SemIR::StructType src_type,
new_block.Set(i, init_id);
}
return is_init ? context.AddInst(SemIR::StructInit{value.parse_node(),
target.type_id, value_id,
new_block.id()})
: context.AddInst(SemIR::StructValue{value.parse_node(),
target.type_id, value_id,
new_block.id()});
if (is_class) {
CARBON_CHECK(is_init)
<< "Converting directly to a class value is not supported";
return context.AddInst(SemIR::ClassInit{value.parse_node(), target.type_id,
value_id, new_block.id()});
} else if (is_init) {
return context.AddInst(SemIR::StructInit{value.parse_node(), target.type_id,
value_id, new_block.id()});
} else {
return context.AddInst(SemIR::StructValue{
value.parse_node(), target.type_id, value_id, new_block.id()});
}
}
// Performs a conversion from a struct to a struct type. This function only
// converts the type, and does not perform a final conversion to the requested
// expression category.
static auto ConvertStructToStruct(Context& context, SemIR::StructType src_type,
SemIR::StructType dest_type,
SemIR::InstId value_id,
ConversionTarget target) -> SemIR::InstId {
return ConvertStructToStructOrClass<SemIR::StructAccess>(
context, src_type, dest_type, value_id, target, /*is_class=*/false);
}
// Performs a conversion from a struct to a class type. This function only
// converts the type, and does not perform a final conversion to the requested
// expression category.
static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
SemIR::ClassType dest_type,
SemIR::InstId value_id,
ConversionTarget target) -> SemIR::InstId {
PendingBlock target_block(context);
auto dest_struct_type = context.insts().GetAs<SemIR::StructType>(
context.sem_ir().GetTypeAllowBuiltinTypes(
context.classes().Get(dest_type.class_id).object_representation_id));
// If we're trying to create a class value, form a temporary for the value to
// point to.
bool need_temporary = !target.is_initializer();
if (need_temporary) {
target.kind = ConversionTarget::Initializer;
target.init_block = &target_block;
target.init_id = target_block.AddInst(SemIR::TemporaryStorage{
context.insts().Get(value_id).parse_node(), target.type_id});
}
auto result_id = ConvertStructToStructOrClass<SemIR::ClassFieldAccess>(
context, src_type, dest_struct_type, value_id, target, /*is_class=*/true);
if (need_temporary) {
target_block.InsertHere();
result_id = context.AddInst(
SemIR::Temporary{context.insts().Get(value_id).parse_node(),
target.type_id, target.init_id, result_id});
}
return result_id;
}
// Returns whether `category` is a valid expression category to produce as a
@@ -622,6 +680,19 @@ static auto PerformBuiltinConversion(Context& context, Parse::Node parse_node,
}
}
// A struct {.f_1: T_1, .f_2: T_2, ..., .f_n: T_n} converts to a class type
// if it converts to the struct type that is the class's representation type
// (a struct with the same fields as the class, plus a base field where
// relevant).
if (auto target_class_type = target_type_inst.TryAs<SemIR::ClassType>()) {
auto value_type_inst =
sem_ir.insts().Get(sem_ir.GetTypeAllowBuiltinTypes(value_type_id));
if (auto src_struct_type = value_type_inst.TryAs<SemIR::StructType>()) {
return ConvertStructToClass(context, *src_struct_type, *target_class_type,
value_id, target);
}
}
if (target.type_id == SemIR::TypeId::TypeType) {
// A tuple of types converts to type `type`.
// TODO: This should apply even for non-literal tuples.