From 545a5b3679df0535af19ffed4b0a37a048bed401 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 2 Nov 2023 13:27:01 -0700 Subject: [PATCH] Support initializing a class from a struct. (#3358) Co-authored-by: Jon Ross-Perkins --- toolchain/check/context.cpp | 1 + toolchain/check/convert.cpp | 115 ++++++++++++++---- toolchain/check/convert.h | 2 +- .../check/testdata/class/fail_init.carbon | 74 +++++++++++ .../class/fail_init_as_inplace.carbon | 73 +++++++++++ toolchain/check/testdata/class/init.carbon | 69 +++++++++++ toolchain/check/testdata/class/init_as.carbon | 53 ++++++++ .../check/testdata/class/init_nested.carbon | 79 ++++++++++++ .../testdata/struct/fail_assign_empty.carbon | 2 +- .../struct/fail_assign_to_empty.carbon | 2 +- .../struct/fail_too_few_values.carbon | 2 +- toolchain/lower/handle.cpp | 91 +++++++------- .../lower/testdata/struct/one_entry.carbon | 4 +- .../lower/testdata/tuple/one_entry.carbon | 4 +- toolchain/sem_ir/file.cpp | 3 + toolchain/sem_ir/inst_kind.def | 1 + toolchain/sem_ir/typed_insts.h | 9 ++ 17 files changed, 511 insertions(+), 73 deletions(-) create mode 100644 toolchain/check/testdata/class/fail_init.carbon create mode 100644 toolchain/check/testdata/class/fail_init_as_inplace.carbon create mode 100644 toolchain/check/testdata/class/init.carbon create mode 100644 toolchain/check/testdata/class/init_as.carbon create mode 100644 toolchain/check/testdata/class/init_nested.carbon diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 3e74c77672a0..92d6c73adb33 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -666,6 +666,7 @@ class TypeCompleter { case SemIR::Call::Kind: case SemIR::ClassDeclaration::Kind: case SemIR::ClassFieldAccess::Kind: + case SemIR::ClassInit::Kind: case SemIR::Dereference::Kind: case SemIR::Field::Kind: case SemIR::FunctionDeclaration::Kind: diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index e82318d5cc31..6385de3d138b 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -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 +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( + ConvertAggregateElement( 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( + 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( + 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( + 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()) { + auto value_type_inst = + sem_ir.insts().Get(sem_ir.GetTypeAllowBuiltinTypes(value_type_id)); + if (auto src_struct_type = value_type_inst.TryAs()) { + 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. diff --git a/toolchain/check/convert.h b/toolchain/check/convert.h index 46bde67ccadd..782f75954079 100644 --- a/toolchain/check/convert.h +++ b/toolchain/check/convert.h @@ -41,7 +41,7 @@ struct ConversionTarget { // For an initializer, the object being initialized. SemIR::InstId init_id = SemIR::InstId::Invalid; // For an initializer, a block of pending instructions that are needed to - // form the value of `target_id`, and that can be discarded if no + // form the value of `init_id`, and that can be discarded if no // initialization is needed. PendingBlock* init_block = nullptr; diff --git a/toolchain/check/testdata/class/fail_init.carbon b/toolchain/check/testdata/class/fail_init.carbon new file mode 100644 index 000000000000..5bfddb9d96a6 --- /dev/null +++ b/toolchain/check/testdata/class/fail_init.carbon @@ -0,0 +1,74 @@ +// 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 + +class Class { + var a: i32; + var b: i32; +} + +fn F() { + // CHECK:STDERR: fail_init.carbon:[[@LINE+3]]:10: ERROR: Cannot initialize class with 2 field(s) from struct with 1 field(s). + // CHECK:STDERR: {.a = 1} as Class; + // CHECK:STDERR: ^ + {.a = 1} as Class; + // CHECK:STDERR: fail_init.carbon:[[@LINE+3]]:18: ERROR: Missing value for field `b` in struct initialization. + // CHECK:STDERR: {.a = 1, .c = 2} as Class; + // CHECK:STDERR: ^ + {.a = 1, .c = 2} as Class; + // CHECK:STDERR: fail_init.carbon:[[@LINE+3]]:26: ERROR: Cannot initialize class with 2 field(s) from struct with 3 field(s). + // CHECK:STDERR: {.a = 1, .b = 2, .c = 3} as Class; + // CHECK:STDERR: ^ + {.a = 1, .b = 2, .c = 3} as Class; +} + +// CHECK:STDOUT: file "fail_init.carbon" { +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %.loc10: type = struct_type {.a: i32, .b: i32} +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc8_8.2: = field "a", member0 +// CHECK:STDOUT: %a: = bind_name "a", %.loc8_8.2 +// CHECK:STDOUT: %.loc9_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc9_8.2: = field "b", member1 +// CHECK:STDOUT: %b: = bind_name "b", %.loc9_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .a = %a +// CHECK:STDOUT: .b = %b +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc16_9: i32 = int_literal 1 +// CHECK:STDOUT: %.loc16_10.1: type = struct_type {.a: i32} +// CHECK:STDOUT: %.loc16_10.2: {.a: i32} = struct_literal (%.loc16_9) +// CHECK:STDOUT: %Class.ref.loc16: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %.loc10: type = ptr_type {.a: i32, .b: i32} +// CHECK:STDOUT: %.loc16_10.3: ref Class = temporary_storage +// CHECK:STDOUT: %.loc16_10.4: ref Class = temporary %.loc16_10.3, +// CHECK:STDOUT: %.loc20_9: i32 = int_literal 1 +// CHECK:STDOUT: %.loc20_17: i32 = int_literal 2 +// CHECK:STDOUT: %.loc20_18.1: type = struct_type {.a: i32, .c: i32} +// CHECK:STDOUT: %.loc20_18.2: {.a: i32, .c: i32} = struct_literal (%.loc20_9, %.loc20_17) +// CHECK:STDOUT: %Class.ref.loc20: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %.loc20_18.3: ref Class = temporary_storage +// CHECK:STDOUT: %.loc20_18.4: ref i32 = class_field_access %.loc20_18.3, member0 +// CHECK:STDOUT: %.loc20_18.5: init i32 = initialize_from %.loc20_9 to %.loc20_18.4 +// CHECK:STDOUT: %.loc20_18.6: ref Class = temporary %.loc20_18.3, +// CHECK:STDOUT: %.loc24_9: i32 = int_literal 1 +// CHECK:STDOUT: %.loc24_17: i32 = int_literal 2 +// CHECK:STDOUT: %.loc24_25: i32 = int_literal 3 +// CHECK:STDOUT: %.loc24_26.1: type = struct_type {.a: i32, .b: i32, .c: i32} +// CHECK:STDOUT: %.loc24_26.2: {.a: i32, .b: i32, .c: i32} = struct_literal (%.loc24_9, %.loc24_17, %.loc24_25) +// CHECK:STDOUT: %Class.ref.loc24: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %.loc24_26.3: ref Class = temporary_storage +// CHECK:STDOUT: %.loc24_26.4: ref Class = temporary %.loc24_26.3, +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_init_as_inplace.carbon b/toolchain/check/testdata/class/fail_init_as_inplace.carbon new file mode 100644 index 000000000000..a024a357b76d --- /dev/null +++ b/toolchain/check/testdata/class/fail_init_as_inplace.carbon @@ -0,0 +1,73 @@ +// 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 + +class Class { + var a: i32; + var b: i32; +} + +fn G(p: Class*); + +fn F() { + // TODO: This case should presumably work: `{...} as Class` should be an + // initializing expression, not a value expression. + // + // CHECK:STDERR: fail_init_as_inplace.carbon:[[@LINE+3]]:33: ERROR: Cannot copy value of type `Class`. + // CHECK:STDERR: var c: Class = {.a = 1, .b = 2} as Class; + // CHECK:STDERR: ^ + var c: Class = {.a = 1, .b = 2} as Class; + G(&c); +} + +// CHECK:STDOUT: file "fail_init_as_inplace.carbon" { +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %.loc10: type = struct_type {.a: i32, .b: i32} +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc8_8.2: = field "a", member0 +// CHECK:STDOUT: %a: = bind_name "a", %.loc8_8.2 +// CHECK:STDOUT: %.loc9_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc9_8.2: = field "b", member1 +// CHECK:STDOUT: %b: = bind_name "b", %.loc9_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .a = %a +// CHECK:STDOUT: .b = %b +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G(%p: Class*); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Class.ref.loc21_10: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %.loc10: type = ptr_type {.a: i32, .b: i32} +// CHECK:STDOUT: %c.var: ref Class = var "c" +// CHECK:STDOUT: %c: ref Class = bind_name "c", %c.var +// CHECK:STDOUT: %.loc21_24: i32 = int_literal 1 +// CHECK:STDOUT: %.loc21_32: i32 = int_literal 2 +// CHECK:STDOUT: %.loc21_33.1: {.a: i32, .b: i32} = struct_literal (%.loc21_24, %.loc21_32) +// CHECK:STDOUT: %Class.ref.loc21_38: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %.loc21_33.2: ref Class = temporary_storage +// CHECK:STDOUT: %.loc21_33.3: ref i32 = class_field_access %.loc21_33.2, member0 +// CHECK:STDOUT: %.loc21_33.4: init i32 = initialize_from %.loc21_24 to %.loc21_33.3 +// CHECK:STDOUT: %.loc21_33.5: ref i32 = class_field_access %.loc21_33.2, member1 +// CHECK:STDOUT: %.loc21_33.6: init i32 = initialize_from %.loc21_32 to %.loc21_33.5 +// CHECK:STDOUT: %.loc21_33.7: init Class = class_init %.loc21_33.1, (%.loc21_33.4, %.loc21_33.6) +// CHECK:STDOUT: %.loc21_33.8: ref Class = temporary %.loc21_33.2, %.loc21_33.7 +// CHECK:STDOUT: %.loc21_33.9: Class = bind_value %.loc21_33.8 +// CHECK:STDOUT: assign %c.var, +// CHECK:STDOUT: %G.ref: = name_reference "G", file.%G +// CHECK:STDOUT: %c.ref: ref Class = name_reference "c", %c +// CHECK:STDOUT: %.loc22_5: Class* = address_of %c.ref +// CHECK:STDOUT: %.loc22_4.1: type = tuple_type () +// CHECK:STDOUT: %.loc22_4.2: init () = call %G.ref(%.loc22_5) +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/init.carbon b/toolchain/check/testdata/class/init.carbon new file mode 100644 index 000000000000..c45341dc1e1a --- /dev/null +++ b/toolchain/check/testdata/class/init.carbon @@ -0,0 +1,69 @@ +// 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 + +class Class { + var n: i32; + var next: Class*; +} + +fn Make(n: i32, next: Class*) -> Class { + return {.n = n, .next = next}; +} + +fn MakeReorder(n: i32, next: Class*) -> Class { + return {.next = next, .n = n}; +} + +// CHECK:STDOUT: file "init.carbon" { +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %.loc10_1.1: type = struct_type {.n: i32, .next: Class*} +// CHECK:STDOUT: %.loc10_1.2: type = ptr_type {.n: i32, .next: Class*} +// CHECK:STDOUT: %Make: = fn_decl @Make +// CHECK:STDOUT: %MakeReorder: = fn_decl @MakeReorder +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc8_8.2: = field "n", member0 +// CHECK:STDOUT: %n: = bind_name "n", %.loc8_8.2 +// CHECK:STDOUT: %Class.ref: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %.loc9_18: type = ptr_type Class +// CHECK:STDOUT: %.loc9_11.1: type = unbound_field_type Class, Class* +// CHECK:STDOUT: %.loc9_11.2: = field "next", member1 +// CHECK:STDOUT: %next: = bind_name "next", %.loc9_11.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .n = %n +// CHECK:STDOUT: .next = %next +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Make(%n: i32, %next: Class*) -> %return: Class { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %n.ref: i32 = name_reference "n", %n +// CHECK:STDOUT: %next.ref: Class* = name_reference "next", %next +// CHECK:STDOUT: %.loc13_31.1: {.n: i32, .next: Class*} = struct_literal (%n.ref, %next.ref) +// CHECK:STDOUT: %.loc13_31.2: ref i32 = class_field_access %return, member0 +// CHECK:STDOUT: %.loc13_31.3: init i32 = initialize_from %n.ref to %.loc13_31.2 +// CHECK:STDOUT: %.loc13_31.4: ref Class* = class_field_access %return, member1 +// CHECK:STDOUT: %.loc13_31.5: init Class* = initialize_from %next.ref to %.loc13_31.4 +// CHECK:STDOUT: %.loc13_31.6: init Class = class_init %.loc13_31.1, (%.loc13_31.3, %.loc13_31.5) +// CHECK:STDOUT: return %.loc13_31.6 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @MakeReorder(%n: i32, %next: Class*) -> %return: Class { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %next.ref: Class* = name_reference "next", %next +// CHECK:STDOUT: %n.ref: i32 = name_reference "n", %n +// CHECK:STDOUT: %.loc17_31.1: type = struct_type {.next: Class*, .n: i32} +// CHECK:STDOUT: %.loc17_31.2: {.next: Class*, .n: i32} = struct_literal (%next.ref, %n.ref) +// CHECK:STDOUT: %.loc17_31.3: ref i32 = class_field_access %return, member1 +// CHECK:STDOUT: %.loc17_31.4: init i32 = initialize_from %n.ref to %.loc17_31.3 +// CHECK:STDOUT: %.loc17_31.5: ref Class* = class_field_access %return, member0 +// CHECK:STDOUT: %.loc17_31.6: init Class* = initialize_from %next.ref to %.loc17_31.5 +// CHECK:STDOUT: %.loc17_31.7: init Class = class_init %.loc17_31.2, (%.loc17_31.4, %.loc17_31.6) +// CHECK:STDOUT: return %.loc17_31.7 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/init_as.carbon b/toolchain/check/testdata/class/init_as.carbon new file mode 100644 index 000000000000..dea0bac7f87a --- /dev/null +++ b/toolchain/check/testdata/class/init_as.carbon @@ -0,0 +1,53 @@ +// 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 + +class Class { + var a: i32; + var b: i32; +} + +fn F() -> i32 { + return ({.a = 1, .b = 2} as Class).a; +} + +// CHECK:STDOUT: file "init_as.carbon" { +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %.loc10: type = struct_type {.a: i32, .b: i32} +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc8_8.2: = field "a", member0 +// CHECK:STDOUT: %a: = bind_name "a", %.loc8_8.2 +// CHECK:STDOUT: %.loc9_8.1: type = unbound_field_type Class, i32 +// CHECK:STDOUT: %.loc9_8.2: = field "b", member1 +// CHECK:STDOUT: %b: = bind_name "b", %.loc9_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .a = %a +// CHECK:STDOUT: .b = %b +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc13_17: i32 = int_literal 1 +// CHECK:STDOUT: %.loc13_25: i32 = int_literal 2 +// CHECK:STDOUT: %.loc13_26.1: {.a: i32, .b: i32} = struct_literal (%.loc13_17, %.loc13_25) +// CHECK:STDOUT: %Class.ref: type = name_reference "Class", file.%Class +// CHECK:STDOUT: %.loc10: type = ptr_type {.a: i32, .b: i32} +// CHECK:STDOUT: %.loc13_26.2: ref Class = temporary_storage +// CHECK:STDOUT: %.loc13_26.3: ref i32 = class_field_access %.loc13_26.2, member0 +// CHECK:STDOUT: %.loc13_26.4: init i32 = initialize_from %.loc13_17 to %.loc13_26.3 +// CHECK:STDOUT: %.loc13_26.5: ref i32 = class_field_access %.loc13_26.2, member1 +// CHECK:STDOUT: %.loc13_26.6: init i32 = initialize_from %.loc13_25 to %.loc13_26.5 +// CHECK:STDOUT: %.loc13_26.7: init Class = class_init %.loc13_26.1, (%.loc13_26.4, %.loc13_26.6) +// CHECK:STDOUT: %.loc13_26.8: ref Class = temporary %.loc13_26.2, %.loc13_26.7 +// CHECK:STDOUT: %.loc13_37.1: ref i32 = class_field_access %.loc13_26.8, member0 +// CHECK:STDOUT: %.loc13_37.2: i32 = bind_value %.loc13_37.1 +// CHECK:STDOUT: return %.loc13_37.2 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/init_nested.carbon b/toolchain/check/testdata/class/init_nested.carbon new file mode 100644 index 000000000000..839c9ebe35c3 --- /dev/null +++ b/toolchain/check/testdata/class/init_nested.carbon @@ -0,0 +1,79 @@ +// 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 + +class Inner { + var a: i32; + var b: i32; +} + +fn MakeInner() -> Inner; + +class Outer { + var c: Inner; + var d: Inner; +} + +fn MakeOuter() -> Outer { + return {.c = MakeInner(), .d = MakeInner()}; +} + +// CHECK:STDOUT: file "init_nested.carbon" { +// CHECK:STDOUT: class_declaration @Inner, () +// CHECK:STDOUT: %Inner: type = class_type @Inner +// CHECK:STDOUT: %.loc10_1.1: type = struct_type {.a: i32, .b: i32} +// CHECK:STDOUT: %.loc10_1.2: type = ptr_type {.a: i32, .b: i32} +// CHECK:STDOUT: %MakeInner: = fn_decl @MakeInner +// CHECK:STDOUT: class_declaration @Outer, () +// CHECK:STDOUT: %Outer: type = class_type @Outer +// CHECK:STDOUT: %.loc17_1.1: type = struct_type {.c: Inner, .d: Inner} +// CHECK:STDOUT: %.loc17_1.2: type = struct_type {.c: {.a: i32, .b: i32}*, .d: {.a: i32, .b: i32}*} +// CHECK:STDOUT: %.loc17_1.3: type = ptr_type {.c: {.a: i32, .b: i32}*, .d: {.a: i32, .b: i32}*} +// CHECK:STDOUT: %.loc14: type = ptr_type {.c: Inner, .d: Inner} +// CHECK:STDOUT: %MakeOuter: = fn_decl @MakeOuter +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Inner { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_field_type Inner, i32 +// CHECK:STDOUT: %.loc8_8.2: = field "a", member0 +// CHECK:STDOUT: %a: = bind_name "a", %.loc8_8.2 +// CHECK:STDOUT: %.loc9_8.1: type = unbound_field_type Inner, i32 +// CHECK:STDOUT: %.loc9_8.2: = field "b", member1 +// CHECK:STDOUT: %b: = bind_name "b", %.loc9_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .a = %a +// CHECK:STDOUT: .b = %b +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Outer { +// CHECK:STDOUT: %Inner.ref.loc15: type = name_reference "Inner", file.%Inner +// CHECK:STDOUT: %.loc15_8.1: type = unbound_field_type Outer, Inner +// CHECK:STDOUT: %.loc15_8.2: = field "c", member0 +// CHECK:STDOUT: %c: = bind_name "c", %.loc15_8.2 +// CHECK:STDOUT: %Inner.ref.loc16: type = name_reference "Inner", file.%Inner +// CHECK:STDOUT: %.loc16_8.1: type = unbound_field_type Outer, Inner +// CHECK:STDOUT: %.loc16_8.2: = field "d", member1 +// CHECK:STDOUT: %d: = bind_name "d", %.loc16_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .c = %c +// CHECK:STDOUT: .d = %d +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @MakeInner() -> %return: Inner; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @MakeOuter() -> %return: Outer { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %MakeInner.ref.loc20_16: = name_reference "MakeInner", file.%MakeInner +// CHECK:STDOUT: %.loc20_45.1: ref Inner = class_field_access %return, member0 +// CHECK:STDOUT: %.loc20_25: init Inner = call %MakeInner.ref.loc20_16() to %.loc20_45.1 +// CHECK:STDOUT: %MakeInner.ref.loc20_34: = name_reference "MakeInner", file.%MakeInner +// CHECK:STDOUT: %.loc20_45.2: ref Inner = class_field_access %return, member1 +// CHECK:STDOUT: %.loc20_43: init Inner = call %MakeInner.ref.loc20_34() to %.loc20_45.2 +// CHECK:STDOUT: %.loc20_45.3: {.c: Inner, .d: Inner} = struct_literal (%.loc20_25, %.loc20_43) +// CHECK:STDOUT: %.loc20_45.4: init Outer = class_init %.loc20_45.3, (%.loc20_25, %.loc20_43) +// CHECK:STDOUT: return %.loc20_45.4 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/struct/fail_assign_empty.carbon b/toolchain/check/testdata/struct/fail_assign_empty.carbon index bc113a71a5eb..bc64f7e6a3b9 100644 --- a/toolchain/check/testdata/struct/fail_assign_empty.carbon +++ b/toolchain/check/testdata/struct/fail_assign_empty.carbon @@ -4,7 +4,7 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:21: ERROR: Cannot initialize struct of 1 element(s) from struct with 0 element(s). +// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:21: ERROR: Cannot initialize struct with 1 field(s) from struct with 0 field(s). // CHECK:STDERR: var x: {.a: i32} = {}; // CHECK:STDERR: ^ var x: {.a: i32} = {}; diff --git a/toolchain/check/testdata/struct/fail_assign_to_empty.carbon b/toolchain/check/testdata/struct/fail_assign_to_empty.carbon index 3a86560a84d0..e1b0ddf2bcc3 100644 --- a/toolchain/check/testdata/struct/fail_assign_to_empty.carbon +++ b/toolchain/check/testdata/struct/fail_assign_to_empty.carbon @@ -4,7 +4,7 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_assign_to_empty.carbon:[[@LINE+3]]:20: ERROR: Cannot initialize struct of 0 element(s) from struct with 1 element(s). +// CHECK:STDERR: fail_assign_to_empty.carbon:[[@LINE+3]]:20: ERROR: Cannot initialize struct with 0 field(s) from struct with 1 field(s). // CHECK:STDERR: var x: {} = {.a = 1}; // CHECK:STDERR: ^ var x: {} = {.a = 1}; diff --git a/toolchain/check/testdata/struct/fail_too_few_values.carbon b/toolchain/check/testdata/struct/fail_too_few_values.carbon index 61c0f9b11251..56378ddab7e2 100644 --- a/toolchain/check/testdata/struct/fail_too_few_values.carbon +++ b/toolchain/check/testdata/struct/fail_too_few_values.carbon @@ -4,7 +4,7 @@ // // AUTOUPDATE -// CHECK:STDERR: fail_too_few_values.carbon:[[@LINE+3]]:36: ERROR: Cannot initialize struct of 2 element(s) from struct with 1 element(s). +// CHECK:STDERR: fail_too_few_values.carbon:[[@LINE+3]]:36: ERROR: Cannot initialize struct with 2 field(s) from struct with 1 field(s). // CHECK:STDERR: var x: {.a: i32, .b: i32} = {.a = 1}; // CHECK:STDERR: ^ var x: {.a: i32, .b: i32} = {.a = 1}; diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index 3bea437966ce..e4a616fbb42d 100644 --- a/toolchain/lower/handle.cpp +++ b/toolchain/lower/handle.cpp @@ -280,6 +280,39 @@ auto HandleClassFieldAccess(FunctionContext& context, SemIR::InstId inst_id, inst.index))); } +static auto EmitAggregateInitializer(FunctionContext& context, + SemIR::TypeId type_id, + SemIR::InstBlockId refs_id, + llvm::Twine name) -> llvm::Value* { + auto* llvm_type = context.GetType(type_id); + + switch ( + SemIR::GetInitializingRepresentation(context.sem_ir(), type_id).kind) { + case SemIR::InitializingRepresentation::None: + case SemIR::InitializingRepresentation::InPlace: + // TODO: Add a helper to poison a value slot. + return llvm::PoisonValue::get(llvm_type); + + case SemIR::InitializingRepresentation::ByCopy: { + auto refs = context.sem_ir().inst_blocks().Get(refs_id); + CARBON_CHECK(refs.size() == 1) + << "Unexpected size for aggregate with by-copy value representation"; + // TODO: Remove the LLVM StructType wrapper in this case, so we don't + // need this `insert_value` wrapping. + return context.builder().CreateInsertValue( + llvm::PoisonValue::get(llvm_type), context.GetLocal(refs[0]), {0}, + name); + } + } +} + +auto HandleClassInit(FunctionContext& context, SemIR::InstId inst_id, + SemIR::ClassInit inst) -> void { + context.SetLocal( + inst_id, EmitAggregateInitializer(context, inst.type_id, inst.elements_id, + "class.init")); +} + auto HandleDereference(FunctionContext& context, SemIR::InstId inst_id, SemIR::Dereference inst) -> void { context.SetLocal(inst_id, context.GetLocal(inst.pointer_id)); @@ -413,10 +446,10 @@ auto HandleStructLiteral(FunctionContext& /*context*/, // Emits the value representation for a struct or tuple whose elements are the // contents of `refs_id`. -auto EmitStructOrTupleValueRepresentation(FunctionContext& context, - SemIR::TypeId type_id, - SemIR::InstBlockId refs_id, - llvm::Twine name) -> llvm::Value* { +auto EmitAggregateValueRepresentation(FunctionContext& context, + SemIR::TypeId type_id, + SemIR::InstBlockId refs_id, + llvm::Twine name) -> llvm::Value* { auto value_rep = SemIR::GetValueRepresentation(context.sem_ir(), type_id); switch (value_rep.kind) { case SemIR::ValueRepresentation::Unknown: @@ -463,30 +496,16 @@ auto EmitStructOrTupleValueRepresentation(FunctionContext& context, auto HandleStructInit(FunctionContext& context, SemIR::InstId inst_id, SemIR::StructInit inst) -> void { - auto* llvm_type = context.GetType(inst.type_id); - - switch (SemIR::GetInitializingRepresentation(context.sem_ir(), inst.type_id) - .kind) { - case SemIR::InitializingRepresentation::None: - case SemIR::InitializingRepresentation::InPlace: - // TODO: Add a helper to poison a value slot. - context.SetLocal(inst_id, llvm::PoisonValue::get(llvm_type)); - break; - - case SemIR::InitializingRepresentation::ByCopy: { - context.SetLocal( - inst_id, EmitStructOrTupleValueRepresentation( - context, inst.type_id, inst.elements_id, "struct.init")); - break; - } - } + context.SetLocal( + inst_id, EmitAggregateInitializer(context, inst.type_id, inst.elements_id, + "struct.init")); } auto HandleStructValue(FunctionContext& context, SemIR::InstId inst_id, SemIR::StructValue inst) -> void { - context.SetLocal(inst_id, - EmitStructOrTupleValueRepresentation( - context, inst.type_id, inst.elements_id, "struct")); + context.SetLocal( + inst_id, EmitAggregateValueRepresentation(context, inst.type_id, + inst.elements_id, "struct")); } auto HandleStructTypeField(FunctionContext& /*context*/, @@ -521,29 +540,15 @@ auto HandleTupleLiteral(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/, auto HandleTupleInit(FunctionContext& context, SemIR::InstId inst_id, SemIR::TupleInit inst) -> void { - auto* llvm_type = context.GetType(inst.type_id); - - switch (SemIR::GetInitializingRepresentation(context.sem_ir(), inst.type_id) - .kind) { - case SemIR::InitializingRepresentation::None: - case SemIR::InitializingRepresentation::InPlace: - // TODO: Add a helper to poison a value slot. - context.SetLocal(inst_id, llvm::PoisonValue::get(llvm_type)); - break; - - case SemIR::InitializingRepresentation::ByCopy: { - context.SetLocal( - inst_id, EmitStructOrTupleValueRepresentation( - context, inst.type_id, inst.elements_id, "tuple.init")); - break; - } - } + context.SetLocal( + inst_id, EmitAggregateInitializer(context, inst.type_id, inst.elements_id, + "tuple.init")); } auto HandleTupleValue(FunctionContext& context, SemIR::InstId inst_id, SemIR::TupleValue inst) -> void { - context.SetLocal( - inst_id, EmitStructOrTupleValueRepresentation(context, inst.type_id, + context.SetLocal(inst_id, + EmitAggregateValueRepresentation(context, inst.type_id, inst.elements_id, "tuple")); } diff --git a/toolchain/lower/testdata/struct/one_entry.carbon b/toolchain/lower/testdata/struct/one_entry.carbon index 219b36ea586c..d4d7d14217a3 100644 --- a/toolchain/lower/testdata/struct/one_entry.carbon +++ b/toolchain/lower/testdata/struct/one_entry.carbon @@ -19,7 +19,7 @@ fn Run() -> i32 { // CHECK:STDOUT: %y = alloca { i32 }, align 8 // CHECK:STDOUT: %a = getelementptr inbounds { i32 }, ptr %x, i32 0, i32 0 // CHECK:STDOUT: %1 = load i32, ptr %a, align 4 -// CHECK:STDOUT: %2 = insertvalue { i32 } poison, i32 %1, 0 -// CHECK:STDOUT: store { i32 } %2, ptr %y, align 4 +// CHECK:STDOUT: %struct.init = insertvalue { i32 } poison, i32 %1, 0 +// CHECK:STDOUT: store { i32 } %struct.init, ptr %y, align 4 // CHECK:STDOUT: ret i32 0 // CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/tuple/one_entry.carbon b/toolchain/lower/testdata/tuple/one_entry.carbon index e2adaae1fa3b..2052322e691c 100644 --- a/toolchain/lower/testdata/tuple/one_entry.carbon +++ b/toolchain/lower/testdata/tuple/one_entry.carbon @@ -19,7 +19,7 @@ fn Run() -> i32 { // CHECK:STDOUT: %y = alloca { i32 }, align 8 // CHECK:STDOUT: %tuple.elem = getelementptr inbounds { i32 }, ptr %x, i32 0, i32 0 // CHECK:STDOUT: %1 = load i32, ptr %tuple.elem, align 4 -// CHECK:STDOUT: %2 = insertvalue { i32 } poison, i32 %1, 0 -// CHECK:STDOUT: store { i32 } %2, ptr %y, align 4 +// CHECK:STDOUT: %tuple.init = insertvalue { i32 } poison, i32 %1, 0 +// CHECK:STDOUT: store { i32 } %tuple.init, ptr %y, align 4 // CHECK:STDOUT: ret i32 0 // CHECK:STDOUT: } diff --git a/toolchain/sem_ir/file.cpp b/toolchain/sem_ir/file.cpp index d6804475bff2..60058501227c 100644 --- a/toolchain/sem_ir/file.cpp +++ b/toolchain/sem_ir/file.cpp @@ -198,6 +198,7 @@ static auto GetTypePrecedence(InstKind kind) -> int { case Call::Kind: case ClassDeclaration::Kind: case ClassFieldAccess::Kind: + case ClassInit::Kind: case Dereference::Kind: case Field::Kind: case FunctionDeclaration::Kind: @@ -399,6 +400,7 @@ auto File::StringifyTypeExpression(InstId outer_inst_id, case Call::Kind: case ClassDeclaration::Kind: case ClassFieldAccess::Kind: + case ClassInit::Kind: case CrossReference::Kind: case Dereference::Kind: case Field::Kind: @@ -567,6 +569,7 @@ auto GetExpressionCategory(const File& file, InstId inst_id) case ArrayInit::Kind: case Call::Kind: case InitializeFrom::Kind: + case ClassInit::Kind: case StructInit::Kind: case TupleInit::Kind: return ExpressionCategory::Initializing; diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index 5993ebeff707..82938d77118b 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/toolchain/sem_ir/inst_kind.def @@ -35,6 +35,7 @@ CARBON_SEM_IR_INST_KIND(Builtin) CARBON_SEM_IR_INST_KIND(Call) CARBON_SEM_IR_INST_KIND(ClassDeclaration) CARBON_SEM_IR_INST_KIND(ClassFieldAccess) +CARBON_SEM_IR_INST_KIND(ClassInit) CARBON_SEM_IR_INST_KIND(ClassType) CARBON_SEM_IR_INST_KIND(ConstType) CARBON_SEM_IR_INST_KIND(CrossReference) diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index 165b72aa53c9..774bdae3c217 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -218,6 +218,15 @@ struct ClassFieldAccess { MemberIndex index; }; +struct ClassInit { + static constexpr auto Kind = InstKind::ClassInit.Define("class_init"); + + Parse::Node parse_node; + TypeId type_id; + InstId src_id; + InstBlockId elements_id; +}; + struct ClassType { static constexpr auto Kind = InstKind::ClassType.Define("class_type");