From f921923b4b0dc89bd02e3c40c8e756b40587cd3d Mon Sep 17 00:00:00 2001 From: David Blaikie Date: Tue, 26 Nov 2024 10:16:25 -0800 Subject: [PATCH] lazy field index (#4514) We considered a couple of other options for this: * https://github.com/carbon-language/carbon-lang/pull/4515 Keep the `ElementIndex` numbering vptr-ignorant, and do +1 offsets as needed - seems subtle/easy to miss * https://github.com/carbon-language/carbon-lang/pull/4517 Always have a zeroth element in the object representation, make it zero-size in the case of no-vptr - @zygoloid was concerned this would add overhead especially to stateless objects used in type-trait-like things. But currently moving forward with this direction - of initializing field indexes with an invalid value until the end of the class definition, then assigning field indexes during construction of the class's object representation struct type. This direction might reinforce/help avoid premature access to the object representation before the class is complete, and give a single place where class layout is done (at class completion) if we want to add more options there, such as class layout optimizations, etc. This patch still has problems with object initialization (that #4515 does not have/does address) but does address normal `obj.member` access correctly. --------- Co-authored-by: Richard Smith --- toolchain/check/context.cpp | 9 + toolchain/check/context.h | 15 +- toolchain/check/handle_binding_pattern.cpp | 12 +- toolchain/check/handle_class.cpp | 89 +++++--- toolchain/check/handle_function.cpp | 4 +- .../class/fail_adapt_with_subobjects.carbon | 14 +- .../testdata/class/virtual_modifiers.carbon | 211 ++++++++++++++---- toolchain/lower/testdata/class/virtual.carbon | 117 ++++++++-- toolchain/sem_ir/ids.h | 5 + 9 files changed, 358 insertions(+), 118 deletions(-) diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index e01bb8cd5e74..8f4594c43dde 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -199,6 +199,15 @@ auto Context::ReplaceInstBeforeConstantUse(SemIR::InstId inst_id, FinishInst(inst_id, inst); } +auto Context::ReplaceInstPreservingConstantValue(SemIR::InstId inst_id, + SemIR::Inst inst) -> void { + auto old_const_id = sem_ir().constant_values().Get(inst_id); + sem_ir().insts().Set(inst_id, inst); + CARBON_VLOG("ReplaceInst: {0} -> {1}\n", inst_id, inst); + auto new_const_id = TryEvalInst(*this, inst_id, inst); + CARBON_CHECK(old_const_id == new_const_id); +} + auto Context::DiagnoseDuplicateName(SemIRLoc dup_def, SemIRLoc prev_def) -> void { CARBON_DIAGNOSTIC(NameDeclDuplicate, Error, diff --git a/toolchain/check/context.h b/toolchain/check/context.h index a66d527e03a7..fc7f3385631e 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -194,6 +194,11 @@ class Context { auto ReplaceInstBeforeConstantUse(SemIR::InstId inst_id, SemIR::Inst inst) -> void; + // Replaces the instruction `inst_id` with `inst`, not affecting location. + // The instruction is required to not change its constant value. + auto ReplaceInstPreservingConstantValue(SemIR::InstId inst_id, + SemIR::Inst inst) -> void; + // Sets only the parse node of an instruction. This is only used when setting // the parse node of an imported namespace. Versus // ReplaceInstBeforeConstantUse, it is safe to use after the namespace is used @@ -492,6 +497,10 @@ class Context { return struct_type_fields_stack_; } + auto field_decls_stack() -> ArrayStack& { + return field_decls_stack_; + } + auto decl_name_stack() -> DeclNameStack& { return decl_name_stack_; } auto decl_introducer_state_stack() -> DeclIntroducerStateStack& { @@ -648,10 +657,12 @@ class Context { // arguments. InstBlockStack args_type_info_stack_; - // The stack of StructTypeFields for in-progress StructTypeLiterals and Class - // object representations. + // The stack of StructTypeFields for in-progress StructTypeLiterals. ArrayStack struct_type_fields_stack_; + // The stack of FieldDecls for in-progress Class definitions. + ArrayStack field_decls_stack_; + // The stack used for qualified declaration name construction. DeclNameStack decl_name_stack_; diff --git a/toolchain/check/handle_binding_pattern.cpp b/toolchain/check/handle_binding_pattern.cpp index 5dc845637ec0..cd3a5e4049c3 100644 --- a/toolchain/check/handle_binding_pattern.cpp +++ b/toolchain/check/handle_binding_pattern.cpp @@ -128,15 +128,11 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, auto field_type_id = context.GetUnboundElementType( class_info.self_type_id, cast_type_id); auto field_id = context.AddInst( - binding_id, - {.type_id = field_type_id, - .name_id = name_id, - .index = SemIR::ElementIndex( - context.struct_type_fields_stack().PeekArray().size())}); + binding_id, {.type_id = field_type_id, + .name_id = name_id, + .index = SemIR::ElementIndex::Invalid}); + context.field_decls_stack().AppendToTop(field_id); - // Add a corresponding field to the object representation of the class. - context.struct_type_fields_stack().AppendToTop( - {.name_id = name_id, .type_id = cast_type_id}); context.node_stack().Push(node_id, field_id); break; } diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index dea13ca8d4c4..fe74b3f6249a 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -295,7 +295,7 @@ auto HandleParseNode(Context& context, Parse::ClassDefinitionStartId node_id) context.inst_block_stack().Push(); context.node_stack().Push(node_id, class_id); - context.struct_type_fields_stack().PushArray(); + context.field_decls_stack().PushArray(); // TODO: Handle the case where there's control flow in the class body. For // example: @@ -512,7 +512,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool { return true; } - if (!context.struct_type_fields_stack().PeekArray().empty()) { + if (!context.field_decls_stack().PeekArray().empty()) { // TODO: Add note that includes the first field location as an example. CARBON_DIAGNOSTIC( BaseDeclAfterFieldDecl, Error, @@ -523,6 +523,8 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool { auto base_info = CheckBaseType(context, base_type_node_id, base_type_expr_id); + // TODO: Should we diagnose if there are already any fields? + // The `base` value in the class scope has an unbound element type. Instance // binding will be performed when it's found by name lookup into an instance. auto field_type_id = @@ -530,8 +532,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool { class_info.base_id = context.AddInst( node_id, {.type_id = field_type_id, .base_type_id = base_info.type_id, - .index = SemIR::ElementIndex( - context.struct_type_fields_stack().PeekArray().size())}); + .index = SemIR::ElementIndex::Invalid}); if (base_info.type_id != SemIR::TypeId::Error) { auto base_class_info = context.classes().Get( @@ -539,12 +540,6 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool { class_info.is_dynamic |= base_class_info.is_dynamic; } - // Add a corresponding field to the object representation of the class. - // TODO: Consider whether we want to use `partial T` here. - // TODO: Should we diagnose if there are already any fields? - context.struct_type_fields_stack().AppendToTop( - {.name_id = SemIR::NameId::Base, .type_id = base_info.type_id}); - // Bind the name `base` in the class to the base field. context.decl_name_stack().AddNameOrDiagnoseDuplicate( context.decl_name_stack().MakeUnqualifiedName(node_id, @@ -567,8 +562,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool { // returns a corresponding complete type witness instruction. static auto CheckCompleteAdapterClassType(Context& context, Parse::NodeId node_id, - SemIR::ClassId class_id, - SemIR::StructTypeFieldsId fields_id) + SemIR::ClassId class_id) -> SemIR::InstId { const auto& class_info = context.classes().Get(class_id); if (class_info.base_id.is_valid()) { @@ -581,17 +575,14 @@ static auto CheckCompleteAdapterClassType(Context& context, return SemIR::InstId::BuiltinErrorInst; } - if (auto fields = context.struct_type_fields().Get(fields_id); - !fields.empty()) { - auto [first_field_inst_id, _] = context.LookupNameInExactScope( - node_id, fields.front().name_id, class_info.scope_id, - context.name_scopes().Get(class_info.scope_id)); + auto field_decls = context.field_decls_stack().PeekArray(); + if (!field_decls.empty()) { CARBON_DIAGNOSTIC(AdaptWithFields, Error, "adapter with fields"); CARBON_DIAGNOSTIC(AdaptWithFieldHere, Note, "first field declaration is here"); context.emitter() .Build(class_info.adapt_id, AdaptWithFields) - .Note(first_field_inst_id, AdaptWithFieldHere) + .Note(field_decls.front(), AdaptWithFieldHere) .Emit(); return SemIR::InstId::BuiltinErrorInst; } @@ -637,6 +628,31 @@ static auto CheckCompleteAdapterClassType(Context& context, {.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType), .object_repr_id = adapted_type_id}); } +static auto AddStructTypeFields( + Context& context, + llvm::SmallVector& struct_type_fields) + -> SemIR::StructTypeFieldsId { + for (auto field_decl_id : context.field_decls_stack().PeekArray()) { + auto field_decl = context.insts().GetAs(field_decl_id); + field_decl.index = + SemIR::ElementIndex{static_cast(struct_type_fields.size())}; + context.ReplaceInstPreservingConstantValue(field_decl_id, field_decl); + if (field_decl.type_id == SemIR::TypeId::Error) { + struct_type_fields.push_back( + {.name_id = field_decl.name_id, .type_id = SemIR::TypeId::Error}); + continue; + } + auto unbound_element_type = + context.sem_ir().types().GetAs( + field_decl.type_id); + struct_type_fields.push_back( + {.name_id = field_decl.name_id, + .type_id = unbound_element_type.element_type_id}); + } + auto fields_id = + context.struct_type_fields().AddCanonical(struct_type_fields); + return fields_id; +} // Checks that the specified finished class definition is valid and builds and // returns a corresponding complete type witness instruction. @@ -644,39 +660,47 @@ static auto CheckCompleteClassType(Context& context, Parse::NodeId node_id, SemIR::ClassId class_id) -> SemIR::InstId { auto& class_info = context.classes().Get(class_id); if (class_info.adapt_id.is_valid()) { - auto fields_id = context.struct_type_fields().AddCanonical( - context.struct_type_fields_stack().PeekArray()); - context.struct_type_fields_stack().PopArray(); - - return CheckCompleteAdapterClassType(context, node_id, class_id, fields_id); + return CheckCompleteAdapterClassType(context, node_id, class_id); } - bool defining_vtable_ptr = class_info.is_dynamic; + bool defining_vptr = class_info.is_dynamic; if (class_info.base_id.is_valid()) { auto base_info = context.insts().GetAs(class_info.base_id); // TODO: If the base class is template dependent, we will need to decide // whether to add a vptr as part of instantiation. if (auto* base_class_info = TryGetAsClass(context, base_info.base_type_id); base_class_info && base_class_info->is_dynamic) { - defining_vtable_ptr = false; + defining_vptr = false; } } - if (defining_vtable_ptr) { - context.struct_type_fields_stack().PrependToTop( + auto field_decls = context.field_decls_stack().PeekArray(); + llvm::SmallVector struct_type_fields; + struct_type_fields.reserve(defining_vptr + class_info.base_id.is_valid() + + field_decls.size()); + if (defining_vptr) { + struct_type_fields.push_back( {.name_id = SemIR::NameId::Vptr, .type_id = context.GetPointerType( context.GetBuiltinType(SemIR::BuiltinInstKind::VtableType))}); } - - auto fields_id = context.struct_type_fields().AddCanonical( - context.struct_type_fields_stack().PeekArray()); - context.struct_type_fields_stack().PopArray(); + if (class_info.base_id.is_valid()) { + auto base_decl = context.insts().GetAs(class_info.base_id); + base_decl.index = + SemIR::ElementIndex{static_cast(struct_type_fields.size())}; + context.ReplaceInstPreservingConstantValue(class_info.base_id, base_decl); + struct_type_fields.push_back( + {.name_id = SemIR::NameId::Base, + .type_id = context.insts() + .GetAs(class_info.base_id) + .base_type_id}); + } return context.AddInst( node_id, {.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType), - .object_repr_id = context.GetStructType(fields_id)}); + .object_repr_id = context.GetStructType( + AddStructTypeFields(context, struct_type_fields))}); } auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id) @@ -691,6 +715,7 @@ auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id) class_info.complete_type_witness_id = complete_type_witness_id; context.inst_block_stack().Pop(); + context.field_decls_stack().PopArray(); FinishGenericDefinition(context, class_info.generic_id); diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 0541dc5fe0f2..c7e62ac65db5 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -212,8 +212,8 @@ static auto BuildFunctionDecl(Context& context, parent_scope_inst) { if (auto class_decl = parent_scope_inst->TryAs()) { auto& class_info = context.classes().Get(class_decl->class_id); - CARBON_CHECK(virtual_modifier != SemIR::Function::VirtualModifier::Impl || - class_info.is_dynamic); + // TODO: If this is an `impl` function, check there's a matching base + // function that's impl or virtual. class_info.is_dynamic = true; } } diff --git a/toolchain/check/testdata/class/fail_adapt_with_subobjects.carbon b/toolchain/check/testdata/class/fail_adapt_with_subobjects.carbon index 79301649031a..f9d821907c99 100644 --- a/toolchain/check/testdata/class/fail_adapt_with_subobjects.carbon +++ b/toolchain/check/testdata/class/fail_adapt_with_subobjects.carbon @@ -121,7 +121,7 @@ class AdaptWithBaseAndFields { // CHECK:STDOUT: %.loc10_12.2: type = converted %int.make_type_signed, %.loc10_12.1 [template = constants.%i32] // CHECK:STDOUT: adapt_decl %i32 // CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base] -// CHECK:STDOUT: %.loc15: %.5 = base_decl %Base, element0 [template] +// CHECK:STDOUT: %.loc15: %.5 = base_decl %Base, element [template] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = constants.%AdaptWithBase @@ -171,7 +171,7 @@ class AdaptWithBaseAndFields { // CHECK:STDOUT: %int.make_type_signed.loc13: init type = call constants.%Int(%.loc13_10.1) [template = constants.%i32] // CHECK:STDOUT: %.loc13_10.2: type = value_of_initializer %int.make_type_signed.loc13 [template = constants.%i32] // CHECK:STDOUT: %.loc13_10.3: type = converted %int.make_type_signed.loc13, %.loc13_10.2 [template = constants.%i32] -// CHECK:STDOUT: %.loc13_8: %.2 = field_decl n, element0 [template] +// CHECK:STDOUT: %.loc13_8: %.2 = field_decl n, element [template] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = constants.%AdaptWithField @@ -188,17 +188,17 @@ class AdaptWithBaseAndFields { // CHECK:STDOUT: %int.make_type_signed.loc25: init type = call constants.%Int(%.loc25_10.1) [template = constants.%i32] // CHECK:STDOUT: %.loc25_10.2: type = value_of_initializer %int.make_type_signed.loc25 [template = constants.%i32] // CHECK:STDOUT: %.loc25_10.3: type = converted %int.make_type_signed.loc25, %.loc25_10.2 [template = constants.%i32] -// CHECK:STDOUT: %.loc25_8: %.3 = field_decl a, element0 [template] +// CHECK:STDOUT: %.loc25_8: %.3 = field_decl a, element [template] // CHECK:STDOUT: %.loc26_10.1: Core.IntLiteral = int_value 32 [template = constants.%.1] // CHECK:STDOUT: %int.make_type_signed.loc26: init type = call constants.%Int(%.loc26_10.1) [template = constants.%i32] // CHECK:STDOUT: %.loc26_10.2: type = value_of_initializer %int.make_type_signed.loc26 [template = constants.%i32] // CHECK:STDOUT: %.loc26_10.3: type = converted %int.make_type_signed.loc26, %.loc26_10.2 [template = constants.%i32] -// CHECK:STDOUT: %.loc26_8: %.3 = field_decl b, element1 [template] +// CHECK:STDOUT: %.loc26_8: %.3 = field_decl b, element [template] // CHECK:STDOUT: %.loc27_10.1: Core.IntLiteral = int_value 32 [template = constants.%.1] // CHECK:STDOUT: %int.make_type_signed.loc27: init type = call constants.%Int(%.loc27_10.1) [template = constants.%i32] // CHECK:STDOUT: %.loc27_10.2: type = value_of_initializer %int.make_type_signed.loc27 [template = constants.%i32] // CHECK:STDOUT: %.loc27_10.3: type = converted %int.make_type_signed.loc27, %.loc27_10.2 [template = constants.%i32] -// CHECK:STDOUT: %.loc27_8: %.3 = field_decl c, element2 [template] +// CHECK:STDOUT: %.loc27_8: %.3 = field_decl c, element [template] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = constants.%AdaptWithFields @@ -250,12 +250,12 @@ class AdaptWithBaseAndFields { // CHECK:STDOUT: // CHECK:STDOUT: class @AdaptWithBaseAndFields { // CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base] -// CHECK:STDOUT: %.loc7: %.4 = base_decl %Base, element0 [template] +// CHECK:STDOUT: %.loc7: %.4 = base_decl %Base, element [template] // CHECK:STDOUT: %.loc8_10.1: Core.IntLiteral = int_value 32 [template = constants.%.5] // CHECK:STDOUT: %int.make_type_signed: init type = call constants.%Int(%.loc8_10.1) [template = constants.%i32] // CHECK:STDOUT: %.loc8_10.2: type = value_of_initializer %int.make_type_signed [template = constants.%i32] // CHECK:STDOUT: %.loc8_10.3: type = converted %int.make_type_signed, %.loc8_10.2 [template = constants.%i32] -// CHECK:STDOUT: %.loc8_8: %.6 = field_decl n, element1 [template] +// CHECK:STDOUT: %.loc8_8: %.6 = field_decl n, element [template] // CHECK:STDOUT: %.loc15_10: %.1 = struct_literal () // CHECK:STDOUT: %.loc15_11: type = converted %.loc15_10, constants.%.1 [template = constants.%.1] // CHECK:STDOUT: adapt_decl %.1 diff --git a/toolchain/check/testdata/class/virtual_modifiers.carbon b/toolchain/check/testdata/class/virtual_modifiers.carbon index 9ddcfd6449cf..2a2eb3136614 100644 --- a/toolchain/check/testdata/class/virtual_modifiers.carbon +++ b/toolchain/check/testdata/class/virtual_modifiers.carbon @@ -52,13 +52,32 @@ fn F() { package InitMembers; base class Base { - virtual fn F(); var m1: i32; var m2: i32; + + virtual fn F(); } fn F() { - var v: Base = {.m2 = 3, .m1 = 5}; + var i: i32 = 3; + // TODO: These should initialize element1 (.m), not element0 (the vptr) + var b1: Base = {.m2 = i, .m1 = i}; + var b2: Base = {.m2 = 3, .m1 = 5}; + + // This one is good, though. + b1.m2 = 4; +} + +// --- todo_fail_impl_without_base_declaration.carbon + +package ImplWithoutBaseDeclaration; + +base class Base { +} + +class Derived { + extend base: Base; + impl fn F(); } // CHECK:STDOUT: --- modifiers.carbon @@ -253,31 +272,36 @@ fn F() { // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %Base: type = class_type @Base [template] -// CHECK:STDOUT: %F.type.1: type = fn_type @F.1 [template] -// CHECK:STDOUT: %F.1: %F.type.1 = struct_value () [template] // CHECK:STDOUT: %.1: Core.IntLiteral = int_value 32 [template] // CHECK:STDOUT: %Int.type: type = fn_type @Int [template] // CHECK:STDOUT: %Int: %Int.type = struct_value () [template] // CHECK:STDOUT: %i32: type = int_type signed, %.1 [template] // CHECK:STDOUT: %.2: type = unbound_element_type %Base, %i32 [template] +// CHECK:STDOUT: %F.type.1: type = fn_type @F.1 [template] +// CHECK:STDOUT: %F.1: %F.type.1 = struct_value () [template] // CHECK:STDOUT: %.3: type = ptr_type [template] // CHECK:STDOUT: %.4: type = struct_type {.: %.3, .m1: %i32, .m2: %i32} [template] // CHECK:STDOUT: %.5: = complete_type_witness %.4 [template] // CHECK:STDOUT: %F.type.2: type = fn_type @F.2 [template] // CHECK:STDOUT: %F.2: %F.type.2 = struct_value () [template] -// CHECK:STDOUT: %.7: Core.IntLiteral = int_value 3 [template] -// CHECK:STDOUT: %.8: Core.IntLiteral = int_value 5 [template] -// CHECK:STDOUT: %.9: type = struct_type {.m2: Core.IntLiteral, .m1: Core.IntLiteral} [template] +// CHECK:STDOUT: %.6: Core.IntLiteral = int_value 3 [template] // CHECK:STDOUT: %Convert.type.2: type = fn_type @Convert.1, @ImplicitAs(%i32) [template] // CHECK:STDOUT: %Convert.type.14: type = fn_type @Convert.2, @impl.1(%.1) [template] // CHECK:STDOUT: %Convert.14: %Convert.type.14 = struct_value () [template] -// CHECK:STDOUT: %.33: = interface_witness (%Convert.14) [template] -// CHECK:STDOUT: %.34: = bound_method %.8, %Convert.14 [template] -// CHECK:STDOUT: %.35: = specific_function %.34, @Convert.2(%.1) [template] -// CHECK:STDOUT: %.36: %i32 = int_value 5 [template] -// CHECK:STDOUT: %.37: = bound_method %.7, %Convert.14 [template] -// CHECK:STDOUT: %.38: = specific_function %.37, @Convert.2(%.1) [template] -// CHECK:STDOUT: %.39: %i32 = int_value 3 [template] +// CHECK:STDOUT: %.30: = interface_witness (%Convert.14) [template] +// CHECK:STDOUT: %.31: = bound_method %.6, %Convert.14 [template] +// CHECK:STDOUT: %.32: = specific_function %.31, @Convert.2(%.1) [template] +// CHECK:STDOUT: %.33: %i32 = int_value 3 [template] +// CHECK:STDOUT: %.35: type = struct_type {.m2: %i32, .m1: %i32} [template] +// CHECK:STDOUT: %.36: Core.IntLiteral = int_value 5 [template] +// CHECK:STDOUT: %.37: type = struct_type {.m2: Core.IntLiteral, .m1: Core.IntLiteral} [template] +// CHECK:STDOUT: %.38: = bound_method %.36, %Convert.14 [template] +// CHECK:STDOUT: %.39: = specific_function %.38, @Convert.2(%.1) [template] +// CHECK:STDOUT: %.40: %i32 = int_value 5 [template] +// CHECK:STDOUT: %.41: Core.IntLiteral = int_value 4 [template] +// CHECK:STDOUT: %.42: = bound_method %.41, %Convert.14 [template] +// CHECK:STDOUT: %.43: = specific_function %.42, @Convert.2(%.1) [template] +// CHECK:STDOUT: %.44: %i32 = int_value 4 [template] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -301,53 +325,146 @@ fn F() { // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Base { -// CHECK:STDOUT: %F.decl: %F.type.1 = fn_decl @F.1 [template = constants.%F.1] {} {} +// CHECK:STDOUT: %.loc5_11.1: Core.IntLiteral = int_value 32 [template = constants.%.1] +// CHECK:STDOUT: %int.make_type_signed.loc5: init type = call constants.%Int(%.loc5_11.1) [template = constants.%i32] +// CHECK:STDOUT: %.loc5_11.2: type = value_of_initializer %int.make_type_signed.loc5 [template = constants.%i32] +// CHECK:STDOUT: %.loc5_11.3: type = converted %int.make_type_signed.loc5, %.loc5_11.2 [template = constants.%i32] +// CHECK:STDOUT: %.loc5_9: %.2 = field_decl m1, element1 [template] // CHECK:STDOUT: %.loc6_11.1: Core.IntLiteral = int_value 32 [template = constants.%.1] // CHECK:STDOUT: %int.make_type_signed.loc6: init type = call constants.%Int(%.loc6_11.1) [template = constants.%i32] // CHECK:STDOUT: %.loc6_11.2: type = value_of_initializer %int.make_type_signed.loc6 [template = constants.%i32] // CHECK:STDOUT: %.loc6_11.3: type = converted %int.make_type_signed.loc6, %.loc6_11.2 [template = constants.%i32] -// CHECK:STDOUT: %.loc6_9: %.2 = field_decl m1, element0 [template] -// CHECK:STDOUT: %.loc7_11.1: Core.IntLiteral = int_value 32 [template = constants.%.1] -// CHECK:STDOUT: %int.make_type_signed.loc7: init type = call constants.%Int(%.loc7_11.1) [template = constants.%i32] -// CHECK:STDOUT: %.loc7_11.2: type = value_of_initializer %int.make_type_signed.loc7 [template = constants.%i32] -// CHECK:STDOUT: %.loc7_11.3: type = converted %int.make_type_signed.loc7, %.loc7_11.2 [template = constants.%i32] -// CHECK:STDOUT: %.loc7_9: %.2 = field_decl m2, element1 [template] -// CHECK:STDOUT: %.loc8: = complete_type_witness %.4 [template = constants.%.5] +// CHECK:STDOUT: %.loc6_9: %.2 = field_decl m2, element2 [template] +// CHECK:STDOUT: %F.decl: %F.type.1 = fn_decl @F.1 [template = constants.%F.1] {} {} +// CHECK:STDOUT: %.loc9: = complete_type_witness %.4 [template = constants.%.5] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = constants.%Base +// CHECK:STDOUT: .m1 = %.loc5_9 +// CHECK:STDOUT: .m2 = %.loc6_9 // CHECK:STDOUT: .F = %F.decl -// CHECK:STDOUT: .m1 = %.loc6_9 -// CHECK:STDOUT: .m2 = %.loc7_9 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: virtual fn @F.1(); // CHECK:STDOUT: // CHECK:STDOUT: fn @F.2() { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base] -// CHECK:STDOUT: %v.var: ref %Base = var v -// CHECK:STDOUT: %v: ref %Base = bind_name v, %v.var -// CHECK:STDOUT: %.loc11_24: Core.IntLiteral = int_value 3 [template = constants.%.7] -// CHECK:STDOUT: %.loc11_33: Core.IntLiteral = int_value 5 [template = constants.%.8] -// CHECK:STDOUT: %.loc11_34.1: %.9 = struct_literal (%.loc11_24, %.loc11_33) -// CHECK:STDOUT: %.loc11_34.2: %Convert.type.2 = interface_witness_access constants.%.33, element0 [template = constants.%Convert.14] -// CHECK:STDOUT: %.loc11_34.3: = bound_method %.loc11_33, %.loc11_34.2 [template = constants.%.34] -// CHECK:STDOUT: %.loc11_34.4: = specific_function %.loc11_34.3, @Convert.2(constants.%.1) [template = constants.%.35] -// CHECK:STDOUT: %int.convert_checked.loc11_34.1: init %i32 = call %.loc11_34.4(%.loc11_33) [template = constants.%.36] -// CHECK:STDOUT: %.loc11_34.5: init %i32 = converted %.loc11_33, %int.convert_checked.loc11_34.1 [template = constants.%.36] -// CHECK:STDOUT: %.loc11_34.6: ref %i32 = class_element_access %v.var, element2 -// CHECK:STDOUT: %.loc11_34.7: init %i32 = initialize_from %.loc11_34.5 to %.loc11_34.6 [template = constants.%.36] -// CHECK:STDOUT: %.loc11_34.8: %Convert.type.2 = interface_witness_access constants.%.33, element0 [template = constants.%Convert.14] -// CHECK:STDOUT: %.loc11_34.9: = bound_method %.loc11_24, %.loc11_34.8 [template = constants.%.37] -// CHECK:STDOUT: %.loc11_34.10: = specific_function %.loc11_34.9, @Convert.2(constants.%.1) [template = constants.%.38] -// CHECK:STDOUT: %int.convert_checked.loc11_34.2: init %i32 = call %.loc11_34.10(%.loc11_24) [template = constants.%.39] -// CHECK:STDOUT: %.loc11_34.11: init %i32 = converted %.loc11_24, %int.convert_checked.loc11_34.2 [template = constants.%.39] -// CHECK:STDOUT: %.loc11_34.12: ref %i32 = class_element_access %v.var, element1 -// CHECK:STDOUT: %.loc11_34.13: init %i32 = initialize_from %.loc11_34.11 to %.loc11_34.12 [template = constants.%.39] -// CHECK:STDOUT: %.loc11_34.14: init %Base = class_init (, %.loc11_34.7, %.loc11_34.13), %v.var [template = ] -// CHECK:STDOUT: %.loc11_35: init %Base = converted %.loc11_34.1, %.loc11_34.14 [template = ] -// CHECK:STDOUT: assign %v.var, %.loc11_35 +// CHECK:STDOUT: %.loc12_10.1: Core.IntLiteral = int_value 32 [template = constants.%.1] +// CHECK:STDOUT: %int.make_type_signed: init type = call constants.%Int(%.loc12_10.1) [template = constants.%i32] +// CHECK:STDOUT: %.loc12_10.2: type = value_of_initializer %int.make_type_signed [template = constants.%i32] +// CHECK:STDOUT: %.loc12_10.3: type = converted %int.make_type_signed, %.loc12_10.2 [template = constants.%i32] +// CHECK:STDOUT: %i.var: ref %i32 = var i +// CHECK:STDOUT: %i: ref %i32 = bind_name i, %i.var +// CHECK:STDOUT: %.loc12_16: Core.IntLiteral = int_value 3 [template = constants.%.6] +// CHECK:STDOUT: %.loc12_17.1: %Convert.type.2 = interface_witness_access constants.%.30, element0 [template = constants.%Convert.14] +// CHECK:STDOUT: %.loc12_17.2: = bound_method %.loc12_16, %.loc12_17.1 [template = constants.%.31] +// CHECK:STDOUT: %.loc12_17.3: = specific_function %.loc12_17.2, @Convert.2(constants.%.1) [template = constants.%.32] +// CHECK:STDOUT: %int.convert_checked.loc12: init %i32 = call %.loc12_17.3(%.loc12_16) [template = constants.%.33] +// CHECK:STDOUT: %.loc12_17.4: init %i32 = converted %.loc12_16, %int.convert_checked.loc12 [template = constants.%.33] +// CHECK:STDOUT: assign %i.var, %.loc12_17.4 +// CHECK:STDOUT: %Base.ref.loc14: type = name_ref Base, file.%Base.decl [template = constants.%Base] +// CHECK:STDOUT: %b1.var: ref %Base = var b1 +// CHECK:STDOUT: %b1: ref %Base = bind_name b1, %b1.var +// CHECK:STDOUT: %i.ref.loc14_25: ref %i32 = name_ref i, %i +// CHECK:STDOUT: %i.ref.loc14_34: ref %i32 = name_ref i, %i +// CHECK:STDOUT: %.loc14_35.1: %.35 = struct_literal (%i.ref.loc14_25, %i.ref.loc14_34) +// CHECK:STDOUT: %.loc14_34: %i32 = bind_value %i.ref.loc14_34 +// CHECK:STDOUT: %.loc14_35.2: ref %i32 = class_element_access %b1.var, element2 +// CHECK:STDOUT: %.loc14_35.3: init %i32 = initialize_from %.loc14_34 to %.loc14_35.2 +// CHECK:STDOUT: %.loc14_25: %i32 = bind_value %i.ref.loc14_25 +// CHECK:STDOUT: %.loc14_35.4: ref %i32 = class_element_access %b1.var, element1 +// CHECK:STDOUT: %.loc14_35.5: init %i32 = initialize_from %.loc14_25 to %.loc14_35.4 +// CHECK:STDOUT: %.loc14_35.6: init %Base = class_init (, %.loc14_35.3, %.loc14_35.5), %b1.var +// CHECK:STDOUT: %.loc14_36: init %Base = converted %.loc14_35.1, %.loc14_35.6 +// CHECK:STDOUT: assign %b1.var, %.loc14_36 +// CHECK:STDOUT: %Base.ref.loc15: type = name_ref Base, file.%Base.decl [template = constants.%Base] +// CHECK:STDOUT: %b2.var: ref %Base = var b2 +// CHECK:STDOUT: %b2: ref %Base = bind_name b2, %b2.var +// CHECK:STDOUT: %.loc15_25: Core.IntLiteral = int_value 3 [template = constants.%.6] +// CHECK:STDOUT: %.loc15_34: Core.IntLiteral = int_value 5 [template = constants.%.36] +// CHECK:STDOUT: %.loc15_35.1: %.37 = struct_literal (%.loc15_25, %.loc15_34) +// CHECK:STDOUT: %.loc15_35.2: %Convert.type.2 = interface_witness_access constants.%.30, element0 [template = constants.%Convert.14] +// CHECK:STDOUT: %.loc15_35.3: = bound_method %.loc15_34, %.loc15_35.2 [template = constants.%.38] +// CHECK:STDOUT: %.loc15_35.4: = specific_function %.loc15_35.3, @Convert.2(constants.%.1) [template = constants.%.39] +// CHECK:STDOUT: %int.convert_checked.loc15_35.1: init %i32 = call %.loc15_35.4(%.loc15_34) [template = constants.%.40] +// CHECK:STDOUT: %.loc15_35.5: init %i32 = converted %.loc15_34, %int.convert_checked.loc15_35.1 [template = constants.%.40] +// CHECK:STDOUT: %.loc15_35.6: ref %i32 = class_element_access %b2.var, element2 +// CHECK:STDOUT: %.loc15_35.7: init %i32 = initialize_from %.loc15_35.5 to %.loc15_35.6 [template = constants.%.40] +// CHECK:STDOUT: %.loc15_35.8: %Convert.type.2 = interface_witness_access constants.%.30, element0 [template = constants.%Convert.14] +// CHECK:STDOUT: %.loc15_35.9: = bound_method %.loc15_25, %.loc15_35.8 [template = constants.%.31] +// CHECK:STDOUT: %.loc15_35.10: = specific_function %.loc15_35.9, @Convert.2(constants.%.1) [template = constants.%.32] +// CHECK:STDOUT: %int.convert_checked.loc15_35.2: init %i32 = call %.loc15_35.10(%.loc15_25) [template = constants.%.33] +// CHECK:STDOUT: %.loc15_35.11: init %i32 = converted %.loc15_25, %int.convert_checked.loc15_35.2 [template = constants.%.33] +// CHECK:STDOUT: %.loc15_35.12: ref %i32 = class_element_access %b2.var, element1 +// CHECK:STDOUT: %.loc15_35.13: init %i32 = initialize_from %.loc15_35.11 to %.loc15_35.12 [template = constants.%.33] +// CHECK:STDOUT: %.loc15_35.14: init %Base = class_init (, %.loc15_35.7, %.loc15_35.13), %b2.var [template = ] +// CHECK:STDOUT: %.loc15_36: init %Base = converted %.loc15_35.1, %.loc15_35.14 [template = ] +// CHECK:STDOUT: assign %b2.var, %.loc15_36 +// CHECK:STDOUT: %b1.ref: ref %Base = name_ref b1, %b1 +// CHECK:STDOUT: %m2.ref: %.2 = name_ref m2, @Base.%.loc6_9 [template = @Base.%.loc6_9] +// CHECK:STDOUT: %.loc18_5: ref %i32 = class_element_access %b1.ref, element2 +// CHECK:STDOUT: %.loc18_11: Core.IntLiteral = int_value 4 [template = constants.%.41] +// CHECK:STDOUT: %.loc18_9.1: %Convert.type.2 = interface_witness_access constants.%.30, element0 [template = constants.%Convert.14] +// CHECK:STDOUT: %.loc18_9.2: = bound_method %.loc18_11, %.loc18_9.1 [template = constants.%.42] +// CHECK:STDOUT: %.loc18_9.3: = specific_function %.loc18_9.2, @Convert.2(constants.%.1) [template = constants.%.43] +// CHECK:STDOUT: %int.convert_checked.loc18: init %i32 = call %.loc18_9.3(%.loc18_11) [template = constants.%.44] +// CHECK:STDOUT: %.loc18_9.4: init %i32 = converted %.loc18_11, %int.convert_checked.loc18 [template = constants.%.44] +// CHECK:STDOUT: assign %.loc18_5, %.loc18_9.4 // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: --- todo_fail_impl_without_base_declaration.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Base: type = class_type @Base [template] +// CHECK:STDOUT: %.1: type = struct_type {} [template] +// CHECK:STDOUT: %.2: = complete_type_witness %.1 [template] +// CHECK:STDOUT: %Derived: type = class_type @Derived [template] +// CHECK:STDOUT: %.4: type = unbound_element_type %Derived, %Base [template] +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: %.5: type = ptr_type [template] +// CHECK:STDOUT: %.6: type = struct_type {.: %.5, .base: %Base} [template] +// CHECK:STDOUT: %.7: = complete_type_witness %.6 [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .Base = %Base.decl +// CHECK:STDOUT: .Derived = %Derived.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %Base.decl: type = class_decl @Base [template = constants.%Base] {} {} +// CHECK:STDOUT: %Derived.decl: type = class_decl @Derived [template = constants.%Derived] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Base { +// CHECK:STDOUT: %.loc5: = complete_type_witness %.1 [template = constants.%.2] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%Base +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Derived { +// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base.decl [template = constants.%Base] +// CHECK:STDOUT: %.loc8: %.4 = base_decl %Base, element1 [template] +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {} +// CHECK:STDOUT: %.loc10: = complete_type_witness %.6 [template = constants.%.7] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%Derived +// CHECK:STDOUT: .base = %.loc8 +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: extend %Base.ref +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl fn @F(); +// CHECK:STDOUT: diff --git a/toolchain/lower/testdata/class/virtual.carbon b/toolchain/lower/testdata/class/virtual.carbon index 78a1d15f5540..68a8dfa6cab9 100644 --- a/toolchain/lower/testdata/class/virtual.carbon +++ b/toolchain/lower/testdata/class/virtual.carbon @@ -8,35 +8,75 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/class/virtual.carbon +// --- classes.carbon + +package Classes; + base class Base { } + base class Intermediate { extend base: Base; virtual fn Fn(); } + class Derived { extend base: Intermediate; impl fn Fn(); } +// --- create.carbon + +package Create; + +import Classes; + fn Create() { - var b: Base; - var i: Intermediate; - var d: Derived; + var b: Classes.Base; + var i: Classes.Intermediate; + var d: Classes.Derived; } -fn Use(v: Intermediate*) { +fn Use(v: Classes.Intermediate*) { v->Fn(); } -// CHECK:STDOUT: ; ModuleID = 'virtual.carbon' -// CHECK:STDOUT: source_filename = "virtual.carbon" +// --- member_init.carbon + +package MemberInit; + +base class Base { + var m: i32; + virtual fn Fn(); +} + +fn Fn() { + var i: i32 = 3; + // TODO: make `i` initialize `m` here (it currently initializes the vptr instead) + var v: Base = {.m = i}; + // TODO: fix crash here + // var v: Base = {.m = 3}; + v.m = 5; +} + +// CHECK:STDOUT: ; ModuleID = 'classes.carbon' +// CHECK:STDOUT: source_filename = "classes.carbon" // CHECK:STDOUT: -// CHECK:STDOUT: declare void @_CFn.Intermediate.Main() +// CHECK:STDOUT: declare void @_CFn.Intermediate.Classes() // CHECK:STDOUT: -// CHECK:STDOUT: declare void @_CFn.Derived.Main() +// CHECK:STDOUT: declare void @_CFn.Derived.Classes() // CHECK:STDOUT: -// CHECK:STDOUT: define void @_CCreate.Main() !dbg !4 { +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1} +// CHECK:STDOUT: !llvm.dbg.cu = !{!2} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "classes.carbon", directory: "") +// CHECK:STDOUT: ; ModuleID = 'create.carbon' +// CHECK:STDOUT: source_filename = "create.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CCreate.Create() !dbg !4 { // CHECK:STDOUT: entry: // CHECK:STDOUT: %b.var = alloca {}, align 8, !dbg !7 // CHECK:STDOUT: %i.var = alloca { ptr, {} }, align 8, !dbg !8 @@ -44,9 +84,46 @@ fn Use(v: Intermediate*) { // CHECK:STDOUT: ret void, !dbg !10 // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: define void @_CUse.Main(ptr %v) !dbg !11 { +// CHECK:STDOUT: define void @_CUse.Create(ptr %v) !dbg !11 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: call void @_CFn.Intermediate.Main(), !dbg !12 +// CHECK:STDOUT: call void @_CFn.Intermediate.Classes(), !dbg !12 +// CHECK:STDOUT: ret void, !dbg !13 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CFn.Intermediate.Classes() +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1} +// CHECK:STDOUT: !llvm.dbg.cu = !{!2} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "create.carbon", directory: "") +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Create", linkageName: "_CCreate.Create", scope: null, file: !3, line: 6, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !5 = !DISubroutineType(types: !6) +// CHECK:STDOUT: !6 = !{} +// CHECK:STDOUT: !7 = !DILocation(line: 7, column: 7, scope: !4) +// CHECK:STDOUT: !8 = !DILocation(line: 8, column: 7, scope: !4) +// CHECK:STDOUT: !9 = !DILocation(line: 9, column: 7, scope: !4) +// CHECK:STDOUT: !10 = !DILocation(line: 6, column: 1, scope: !4) +// CHECK:STDOUT: !11 = distinct !DISubprogram(name: "Use", linkageName: "_CUse.Create", scope: null, file: !3, line: 12, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !12 = !DILocation(line: 13, column: 3, scope: !11) +// CHECK:STDOUT: !13 = !DILocation(line: 12, column: 1, scope: !11) +// CHECK:STDOUT: ; ModuleID = 'member_init.carbon' +// CHECK:STDOUT: source_filename = "member_init.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CFn.Base.MemberInit() +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CFn.MemberInit() !dbg !4 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %i.var = alloca i32, align 4, !dbg !7 +// CHECK:STDOUT: store i32 3, ptr %i.var, align 4, !dbg !8 +// CHECK:STDOUT: %v.var = alloca { ptr, i32 }, align 8, !dbg !9 +// CHECK:STDOUT: %.loc12_23 = load i32, ptr %i.var, align 4, !dbg !10 +// CHECK:STDOUT: %.loc12_24.2.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !11 +// CHECK:STDOUT: store i32 %.loc12_23, ptr %.loc12_24.2.m, align 4, !dbg !11 +// CHECK:STDOUT: %.loc15_4.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !12 +// CHECK:STDOUT: store i32 5, ptr %.loc15_4.m, align 4, !dbg !12 // CHECK:STDOUT: ret void, !dbg !13 // CHECK:STDOUT: } // CHECK:STDOUT: @@ -56,14 +133,14 @@ fn Use(v: Intermediate*) { // CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) -// CHECK:STDOUT: !3 = !DIFile(filename: "virtual.carbon", directory: "") -// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Create", linkageName: "_CCreate.Main", scope: null, file: !3, line: 22, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !3 = !DIFile(filename: "member_init.carbon", directory: "") +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Fn", linkageName: "_CFn.MemberInit", scope: null, file: !3, line: 9, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !5 = !DISubroutineType(types: !6) // CHECK:STDOUT: !6 = !{} -// CHECK:STDOUT: !7 = !DILocation(line: 23, column: 7, scope: !4) -// CHECK:STDOUT: !8 = !DILocation(line: 24, column: 7, scope: !4) -// CHECK:STDOUT: !9 = !DILocation(line: 25, column: 7, scope: !4) -// CHECK:STDOUT: !10 = !DILocation(line: 22, column: 1, scope: !4) -// CHECK:STDOUT: !11 = distinct !DISubprogram(name: "Use", linkageName: "_CUse.Main", scope: null, file: !3, line: 28, type: !5, spFlags: DISPFlagDefinition, unit: !2) -// CHECK:STDOUT: !12 = !DILocation(line: 29, column: 3, scope: !11) -// CHECK:STDOUT: !13 = !DILocation(line: 28, column: 1, scope: !11) +// CHECK:STDOUT: !7 = !DILocation(line: 10, column: 7, scope: !4) +// CHECK:STDOUT: !8 = !DILocation(line: 10, column: 3, scope: !4) +// CHECK:STDOUT: !9 = !DILocation(line: 12, column: 7, scope: !4) +// CHECK:STDOUT: !10 = !DILocation(line: 12, column: 23, scope: !4) +// CHECK:STDOUT: !11 = !DILocation(line: 12, column: 17, scope: !4) +// CHECK:STDOUT: !12 = !DILocation(line: 15, column: 3, scope: !4) +// CHECK:STDOUT: !13 = !DILocation(line: 9, column: 1, scope: !4) diff --git a/toolchain/sem_ir/ids.h b/toolchain/sem_ir/ids.h index 9fcc48d49cc5..c0e25d15ae2c 100644 --- a/toolchain/sem_ir/ids.h +++ b/toolchain/sem_ir/ids.h @@ -805,12 +805,17 @@ constexpr TypeBlockId TypeBlockId::Empty = TypeBlockId(0); struct ElementIndex : public IndexBase, public Printable { using IndexBase::IndexBase; + // An explicitly invalid ID. + static const ElementIndex Invalid; + auto Print(llvm::raw_ostream& out) const -> void { out << "element"; IndexBase::Print(out); } }; +constexpr ElementIndex ElementIndex::Invalid = ElementIndex(InvalidIndex); + // The ID of a library name. This is either a string literal or `default`. struct LibraryNameId : public IdBase, public Printable { using DiagnosticType = DiagnosticTypeInfo;