From 7fd62ff58dc1ad2e4d47098327e5461af2081c34 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 5 Dec 2025 13:07:07 -0800 Subject: [PATCH] Stop using `ImplWitness[Table]` for a C++ synthesized witness. (#6451) Add a `CppWitness` and use it instead of using `ImplWitness` with an `ImplId` and `SpecificId` of `None`. This witness can be substantially simpler because we never need a `SpecificId`. --- toolchain/check/cpp/impl_lookup.cpp | 52 ++++++++------- toolchain/check/eval_inst.cpp | 22 ++++++- .../testdata/interop/cpp/impls/copy.carbon | 66 +++++++++---------- toolchain/sem_ir/inst_kind.def | 1 + toolchain/sem_ir/inst_namer.cpp | 4 ++ toolchain/sem_ir/typed_insts.h | 23 ++++++- 6 files changed, 102 insertions(+), 66 deletions(-) diff --git a/toolchain/check/cpp/impl_lookup.cpp b/toolchain/check/cpp/impl_lookup.cpp index 60ce0ca9b4d2..b1278dc15a27 100644 --- a/toolchain/check/cpp/impl_lookup.cpp +++ b/toolchain/check/cpp/impl_lookup.cpp @@ -66,30 +66,30 @@ static auto BuildWitness(Context& context, SemIR::LocId loc_id, return SemIR::ErrorInst::InstId; } - // Prepare an empty witness table. - auto witness_table_id = - context.inst_blocks().AddUninitialized(assoc_entities.size()); - auto witness_table = context.inst_blocks().GetMutable(witness_table_id); - for (auto& witness_value_id : witness_table) { - witness_value_id = SemIR::InstId::ImplWitnessTablePlaceholder; - } + llvm::SmallVector entries; - // Build a witness. We use an `ImplWitness` with an `impl_id` of `None` to - // represent a synthesized witness. - // TODO: Stop using `ImplWitnessTable` here and add a distinct instruction - // that doesn't contain an `InstId` and supports deduplication. - auto witness_table_inst_id = AddInst( - context, loc_id, - {.elements_id = witness_table_id, .impl_id = SemIR::ImplId::None}); - auto witness_id = AddInst( - context, loc_id, - {.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId), - .witness_table_id = witness_table_inst_id, - .specific_id = SemIR::SpecificId::None}); + // Build a witness with the current contents of the witness table. This will + // grow as we progress through the impl. In theory this will build O(n^2) + // table entries, but in practice n <= 2, so that's OK. + // + // This is necessary because later associated entities may refer to earlier + // associated entities in their signatures. In particular, an associated + // result type may be used as the return type of an associated function. + // + // TODO: Consider building one witness after all associated constants, and + // then a second after all associated functions, rather than building one at + // each step. For now this doesn't really matter since we don't have more than + // one of each anyway. + auto make_witness = [&] { + return context.constant_values().GetInstId(EvalOrAddInst( + context, loc_id, + {.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId), + .elements_id = context.inst_blocks().Add(entries)})); + }; // Fill in the witness table. - for (const auto& [assoc_entity_id, value_id, witness_value_id] : - llvm::zip_equal(assoc_entities, values, witness_table)) { + for (const auto& [assoc_entity_id, value_id] : + llvm::zip_equal(assoc_entities, values)) { LoadImportRef(context, assoc_entity_id); auto decl_id = context.constant_values().GetInstId(SemIR::GetConstantValueInSpecific( @@ -104,11 +104,13 @@ static auto BuildWitness(Context& context, SemIR::LocId loc_id, // TODO: If a thunk is needed, this will build a different value each // time it's called, so we won't properly deduplicate repeated // witnesses. - witness_value_id = CheckAssociatedFunctionImplementation( + // TODO: Skip calling make_witness if this function signature doesn't + // involve `Self`. + entries.push_back(CheckAssociatedFunctionImplementation( context, context.types().GetAs(struct_value.type_id), - value_id, self_type_id, witness_id, - /*defer_thunk_definition=*/false); + value_id, self_type_id, make_witness(), + /*defer_thunk_definition=*/false)); break; } case SemIR::AssociatedConstantDecl::Kind: { @@ -123,7 +125,7 @@ static auto BuildWitness(Context& context, SemIR::LocId loc_id, } } - return witness_id; + return make_witness(); } static auto LookupCopyImpl(Context& context, SemIR::LocId loc_id, diff --git a/toolchain/check/eval_inst.cpp b/toolchain/check/eval_inst.cpp index 8c58d1f0feee..a063cb8e8910 100644 --- a/toolchain/check/eval_inst.cpp +++ b/toolchain/check/eval_inst.cpp @@ -316,6 +316,8 @@ auto EvalConstantInst(Context& context, SemIR::InstId inst_id, auto EvalConstantInst(Context& context, SemIR::InstId inst_id, SemIR::ImplWitnessAccess inst) -> ConstantEvalResult { + CARBON_DIAGNOSTIC(ImplAccessMemberBeforeSet, Error, + "accessing member from impl before it has a defined value"); if (auto witness = context.insts().TryGetAs(inst.witness_id)) { // This is PerformAggregateAccess followed by GetConstantValueInSpecific. @@ -334,12 +336,26 @@ auto EvalConstantInst(Context& context, SemIR::InstId inst_id, context.sem_ir(), witness->specific_id, element)); } } - CARBON_DIAGNOSTIC( - ImplAccessMemberBeforeSet, Error, - "accessing member from impl before it has a defined value"); + // If we get here, this impl witness table entry has not been populated yet, + // because the impl was referenced within its own definition. // TODO: Add note pointing to the impl declaration. context.emitter().Emit(inst_id, ImplAccessMemberBeforeSet); return ConstantEvalResult::Error; + } else if (auto cpp_witness = + context.insts().TryGetAs(inst.witness_id)) { + auto elements = context.inst_blocks().Get(cpp_witness->elements_id); + auto index = static_cast(inst.index.index); + // `elements` can be shorter than the number of associated entities while + // we're building the synthetic witness. + if (index < elements.size()) { + return ConstantEvalResult::Existing( + context.constant_values().Get(elements[index])); + } + // If we get here, this synthesized witness table entry has not been + // populated yet. + // TODO: Is this reachable? We have no test coverage for this diagnostic. + context.emitter().Emit(inst_id, ImplAccessMemberBeforeSet); + return ConstantEvalResult::Error; } else if (auto witness = context.insts().TryGetAs( inst.witness_id)) { // If the witness is symbolic but has a self type that is a FacetType, it diff --git a/toolchain/check/testdata/interop/cpp/impls/copy.carbon b/toolchain/check/testdata/interop/cpp/impls/copy.carbon index ea70bf95bff8..17acd1f5810d 100644 --- a/toolchain/check/testdata/interop/cpp/impls/copy.carbon +++ b/toolchain/check/testdata/interop/cpp/impls/copy.carbon @@ -200,11 +200,11 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* { // CHECK:STDOUT: %ptr.e47: type = ptr_type %Copyable [concrete] // CHECK:STDOUT: %Copyable__carbon_thunk.type: type = fn_type @Copyable__carbon_thunk [concrete] // CHECK:STDOUT: %Copyable__carbon_thunk: %Copyable__carbon_thunk.type = struct_value () [concrete] -// CHECK:STDOUT: %impl_witness.65e: = impl_witness @CopyCopyable.%impl_witness_table [concrete] -// CHECK:STDOUT: %Copy.facet.26f: %Copy.type = facet_value %Copyable, (%impl_witness.65e) [concrete] // CHECK:STDOUT: %Copyable.Op.type: type = fn_type @Copyable.Op [concrete] // CHECK:STDOUT: %Copyable.Op: %Copyable.Op.type = struct_value () [concrete] -// CHECK:STDOUT: %.667: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.26f [concrete] +// CHECK:STDOUT: %cpp_witness.524: = cpp_witness (%Copyable.Op) [concrete] +// CHECK:STDOUT: %Copy.facet.2cd: %Copy.type = facet_value %Copyable, (%cpp_witness.524) [concrete] +// CHECK:STDOUT: %.dc1: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.2cd [concrete] // CHECK:STDOUT: %ExplicitCopy: type = class_type @ExplicitCopy [concrete] // CHECK:STDOUT: %ExplicitCopy.ExplicitCopy.type: type = fn_type @ExplicitCopy.ExplicitCopy [concrete] // CHECK:STDOUT: %ExplicitCopy.ExplicitCopy: %ExplicitCopy.ExplicitCopy.type = struct_value () [concrete] @@ -213,11 +213,11 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* { // CHECK:STDOUT: %ptr.84c: type = ptr_type %ExplicitCopy [concrete] // CHECK:STDOUT: %ExplicitCopy__carbon_thunk.type: type = fn_type @ExplicitCopy__carbon_thunk [concrete] // CHECK:STDOUT: %ExplicitCopy__carbon_thunk: %ExplicitCopy__carbon_thunk.type = struct_value () [concrete] -// CHECK:STDOUT: %impl_witness.215: = impl_witness @CopyExplicitCopy.%impl_witness_table [concrete] -// CHECK:STDOUT: %Copy.facet.cfa: %Copy.type = facet_value %ExplicitCopy, (%impl_witness.215) [concrete] // CHECK:STDOUT: %ExplicitCopy.Op.type: type = fn_type @ExplicitCopy.Op [concrete] // CHECK:STDOUT: %ExplicitCopy.Op: %ExplicitCopy.Op.type = struct_value () [concrete] -// CHECK:STDOUT: %.a87: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.cfa [concrete] +// CHECK:STDOUT: %cpp_witness.b38: = cpp_witness (%ExplicitCopy.Op) [concrete] +// CHECK:STDOUT: %Copy.facet.27f: %Copy.type = facet_value %ExplicitCopy, (%cpp_witness.b38) [concrete] +// CHECK:STDOUT: %.82a: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.27f [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -246,10 +246,8 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* { // CHECK:STDOUT: fn @CopyCopyable(%c.param: %Copyable) -> %return.param: %Copyable { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %c.ref: %Copyable = name_ref c, %c -// CHECK:STDOUT: %impl_witness_table = impl_witness_table (%Copyable.Op.decl), invalid [concrete] -// CHECK:STDOUT: %impl_witness: = impl_witness %impl_witness_table [concrete = constants.%impl_witness.65e] // CHECK:STDOUT: -// CHECK:STDOUT: %impl.elem0: %.667 = impl_witness_access constants.%impl_witness.65e, element0 [concrete = constants.%Copyable.Op] +// CHECK:STDOUT: %impl.elem0: %.dc1 = impl_witness_access constants.%cpp_witness.524, element0 [concrete = constants.%Copyable.Op] // CHECK:STDOUT: %bound_method: = bound_method %c.ref, %impl.elem0 // CHECK:STDOUT: %.loc8_10.1: ref %Copyable = temporary_storage // CHECK:STDOUT: %Op.ref: %Copyable.Copyable.type = name_ref Op, imports.%Copyable.Copyable.decl [concrete = constants.%Copyable.Copyable] @@ -267,10 +265,8 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* { // CHECK:STDOUT: fn @CopyExplicitCopy(%c.param: %ExplicitCopy) -> %return.param: %ExplicitCopy { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %c.ref: %ExplicitCopy = name_ref c, %c -// CHECK:STDOUT: %impl_witness_table = impl_witness_table (%ExplicitCopy.Op.decl), invalid [concrete] -// CHECK:STDOUT: %impl_witness: = impl_witness %impl_witness_table [concrete = constants.%impl_witness.215] // CHECK:STDOUT: -// CHECK:STDOUT: %impl.elem0: %.a87 = impl_witness_access constants.%impl_witness.215, element0 [concrete = constants.%ExplicitCopy.Op] +// CHECK:STDOUT: %impl.elem0: %.82a = impl_witness_access constants.%cpp_witness.b38, element0 [concrete = constants.%ExplicitCopy.Op] // CHECK:STDOUT: %bound_method: = bound_method %c.ref, %impl.elem0 // CHECK:STDOUT: %.loc14_10.1: ref %ExplicitCopy = temporary_storage // CHECK:STDOUT: %Op.ref: %ExplicitCopy.ExplicitCopy.type = name_ref Op, imports.%ExplicitCopy.ExplicitCopy.decl [concrete = constants.%ExplicitCopy.ExplicitCopy] @@ -296,17 +292,19 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* { // CHECK:STDOUT: %ptr.as.Copy.impl.Op.type.75b: type = fn_type @ptr.as.Copy.impl.Op, @ptr.as.Copy.impl(%T.d9f) [symbolic] // CHECK:STDOUT: %ptr.as.Copy.impl.Op.692: %ptr.as.Copy.impl.Op.type.75b = struct_value () [symbolic] // CHECK:STDOUT: %Copyable: type = class_type @Copyable [concrete] -// CHECK:STDOUT: %impl_witness: = impl_witness @DoCopy.%impl_witness_table [concrete] -// CHECK:STDOUT: %Copy.facet.26f: %Copy.type.705 = facet_value %Copyable, (%impl_witness) [concrete] -// CHECK:STDOUT: %Copy.specific_fn: = specific_function %Copy, @Copy.loc6(%Copy.facet.26f) [concrete] -// CHECK:STDOUT: %Wrap.248: type = class_type @Wrap, @Wrap(%Copy.facet.26f) [concrete] -// CHECK:STDOUT: %ptr.510: type = ptr_type %Wrap.248 [concrete] -// CHECK:STDOUT: %Copy.impl_witness.cab: = impl_witness imports.%Copy.impl_witness_table.67d, @ptr.as.Copy.impl(%Wrap.248) [concrete] -// CHECK:STDOUT: %ptr.as.Copy.impl.Op.type.b1b: type = fn_type @ptr.as.Copy.impl.Op, @ptr.as.Copy.impl(%Wrap.248) [concrete] -// CHECK:STDOUT: %ptr.as.Copy.impl.Op.ea7: %ptr.as.Copy.impl.Op.type.b1b = struct_value () [concrete] -// CHECK:STDOUT: %Copy.facet.5be: %Copy.type.705 = facet_value %ptr.510, (%Copy.impl_witness.cab) [concrete] -// CHECK:STDOUT: %.e13: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.5be [concrete] -// CHECK:STDOUT: %ptr.as.Copy.impl.Op.specific_fn: = specific_function %ptr.as.Copy.impl.Op.ea7, @ptr.as.Copy.impl.Op(%Wrap.248) [concrete] +// CHECK:STDOUT: %Copyable.Op.type: type = fn_type @Copyable.Op [concrete] +// CHECK:STDOUT: %Copyable.Op: %Copyable.Op.type = struct_value () [concrete] +// CHECK:STDOUT: %cpp_witness.524: = cpp_witness (%Copyable.Op) [concrete] +// CHECK:STDOUT: %Copy.facet.2cd: %Copy.type.705 = facet_value %Copyable, (%cpp_witness.524) [concrete] +// CHECK:STDOUT: %Copy.specific_fn: = specific_function %Copy, @Copy.loc6(%Copy.facet.2cd) [concrete] +// CHECK:STDOUT: %Wrap.380: type = class_type @Wrap, @Wrap(%Copy.facet.2cd) [concrete] +// CHECK:STDOUT: %ptr.ca9: type = ptr_type %Wrap.380 [concrete] +// CHECK:STDOUT: %Copy.impl_witness.c72: = impl_witness imports.%Copy.impl_witness_table.67d, @ptr.as.Copy.impl(%Wrap.380) [concrete] +// CHECK:STDOUT: %ptr.as.Copy.impl.Op.type.4f5: type = fn_type @ptr.as.Copy.impl.Op, @ptr.as.Copy.impl(%Wrap.380) [concrete] +// CHECK:STDOUT: %ptr.as.Copy.impl.Op.c1c: %ptr.as.Copy.impl.Op.type.4f5 = struct_value () [concrete] +// CHECK:STDOUT: %Copy.facet.fd9: %Copy.type.705 = facet_value %ptr.ca9, (%Copy.impl_witness.c72) [concrete] +// CHECK:STDOUT: %.127: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.fd9 [concrete] +// CHECK:STDOUT: %ptr.as.Copy.impl.Op.specific_fn: = specific_function %ptr.as.Copy.impl.Op.c1c, @ptr.as.Copy.impl.Op(%Wrap.380) [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -318,27 +316,25 @@ fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %Copy.ref: %Copy.type.6f0 = name_ref Copy, file.%Copy.decl [concrete = constants.%Copy] // CHECK:STDOUT: %c.ref: %Copyable = name_ref c, %c -// CHECK:STDOUT: %impl_witness_table = impl_witness_table (%Copyable.Op.decl), invalid [concrete] -// CHECK:STDOUT: %impl_witness: = impl_witness %impl_witness_table [concrete = constants.%impl_witness] // CHECK:STDOUT: -// CHECK:STDOUT: %Copy.facet.loc12_16.1: %Copy.type.705 = facet_value constants.%Copyable, (constants.%impl_witness) [concrete = constants.%Copy.facet.26f] -// CHECK:STDOUT: %.loc12_16.1: %Copy.type.705 = converted constants.%Copyable, %Copy.facet.loc12_16.1 [concrete = constants.%Copy.facet.26f] -// CHECK:STDOUT: %Copy.facet.loc12_16.2: %Copy.type.705 = facet_value constants.%Copyable, (constants.%impl_witness) [concrete = constants.%Copy.facet.26f] -// CHECK:STDOUT: %.loc12_16.2: %Copy.type.705 = converted constants.%Copyable, %Copy.facet.loc12_16.2 [concrete = constants.%Copy.facet.26f] -// CHECK:STDOUT: %Copy.specific_fn: = specific_function %Copy.ref, @Copy.loc6(constants.%Copy.facet.26f) [concrete = constants.%Copy.specific_fn] +// CHECK:STDOUT: %Copy.facet.loc12_16.1: %Copy.type.705 = facet_value constants.%Copyable, (constants.%cpp_witness.524) [concrete = constants.%Copy.facet.2cd] +// CHECK:STDOUT: %.loc12_16.1: %Copy.type.705 = converted constants.%Copyable, %Copy.facet.loc12_16.1 [concrete = constants.%Copy.facet.2cd] +// CHECK:STDOUT: %Copy.facet.loc12_16.2: %Copy.type.705 = facet_value constants.%Copyable, (constants.%cpp_witness.524) [concrete = constants.%Copy.facet.2cd] +// CHECK:STDOUT: %.loc12_16.2: %Copy.type.705 = converted constants.%Copyable, %Copy.facet.loc12_16.2 [concrete = constants.%Copy.facet.2cd] +// CHECK:STDOUT: %Copy.specific_fn: = specific_function %Copy.ref, @Copy.loc6(constants.%Copy.facet.2cd) [concrete = constants.%Copy.specific_fn] // CHECK:STDOUT: // CHECK:STDOUT: %Copy.call: init %Copyable = call %Copy.specific_fn(%c.ref) to %.loc10_28 // CHECK:STDOUT: return %Copy.call to %return // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @EqualWitnesses(%p.param: %ptr.510) -> %ptr.510 { +// CHECK:STDOUT: fn @EqualWitnesses(%p.param: %ptr.ca9) -> %ptr.ca9 { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %p.ref: %ptr.510 = name_ref p, %p -// CHECK:STDOUT: %impl.elem0: %.e13 = impl_witness_access constants.%Copy.impl_witness.cab, element0 [concrete = constants.%ptr.as.Copy.impl.Op.ea7] +// CHECK:STDOUT: %p.ref: %ptr.ca9 = name_ref p, %p +// CHECK:STDOUT: %impl.elem0: %.127 = impl_witness_access constants.%Copy.impl_witness.c72, element0 [concrete = constants.%ptr.as.Copy.impl.Op.c1c] // CHECK:STDOUT: %bound_method.loc20_10.1: = bound_method %p.ref, %impl.elem0 -// CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @ptr.as.Copy.impl.Op(constants.%Wrap.248) [concrete = constants.%ptr.as.Copy.impl.Op.specific_fn] +// CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @ptr.as.Copy.impl.Op(constants.%Wrap.380) [concrete = constants.%ptr.as.Copy.impl.Op.specific_fn] // CHECK:STDOUT: %bound_method.loc20_10.2: = bound_method %p.ref, %specific_fn -// CHECK:STDOUT: %ptr.as.Copy.impl.Op.call: init %ptr.510 = call %bound_method.loc20_10.2(%p.ref) +// CHECK:STDOUT: %ptr.as.Copy.impl.Op.call: init %ptr.ca9 = call %bound_method.loc20_10.2(%p.ref) // CHECK:STDOUT: return %ptr.as.Copy.impl.Op.call to %return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index c29e9576a4d9..e6c93dcd3e86 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/toolchain/sem_ir/inst_kind.def @@ -55,6 +55,7 @@ CARBON_SEM_IR_INST_KIND(ConvertToValueAction) CARBON_SEM_IR_INST_KIND(Converted) CARBON_SEM_IR_INST_KIND(CppOverloadSetType) CARBON_SEM_IR_INST_KIND(CppOverloadSetValue) +CARBON_SEM_IR_INST_KIND(CppWitness) CARBON_SEM_IR_INST_KIND(CustomLayoutType) CARBON_SEM_IR_INST_KIND(Deref) CARBON_SEM_IR_INST_KIND(ErrorInst) diff --git a/toolchain/sem_ir/inst_namer.cpp b/toolchain/sem_ir/inst_namer.cpp index 8551cd27f80f..7024d22d1734 100644 --- a/toolchain/sem_ir/inst_namer.cpp +++ b/toolchain/sem_ir/inst_namer.cpp @@ -907,6 +907,10 @@ auto InstNamer::NamingContext::NameInst() -> void { AddInstName("const"); return; } + case CppWitness::Kind: { + AddInstName("cpp_witness"); + return; + } case CARBON_KIND(FacetAccessType inst): { auto name_id = SemIR::NameId::None; if (auto name = diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index db280b511b33..fbb2be2fdafa 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -754,6 +754,22 @@ struct CppOverloadSetValue { CppOverloadSetId overload_set_id; }; +// A witness synthesized for a C++ construct such as a constructor, conversion +// function, or overloaded operator. +struct CppWitness { + static constexpr auto Kind = InstKind::CppWitness.Define( + {.ir_name = "cpp_witness", + .constant_kind = InstConstantKind::Always, + // TODO: For dynamic dispatch, we might want to lower witness tables as + // constants. + .is_lowered = false}); + + // Always the type of the builtin `WitnessType` singleton instruction. + TypeId type_id; + // The witness table of instructions. + InstBlockId elements_id; +}; + // The type of the name of a generic class. The corresponding value is an empty // `StructValue`. struct GenericClassType { @@ -2066,9 +2082,10 @@ struct WhereExpr { InstBlockId requirements_id; }; -// The type of `ImplWitness` and `LookupImplWitness` instructions. The latter -// will evaluate at some point during specific computation into the former, and -// their types should not change in the process. +// The type of `ImplWitness`, `CppWitness`, and `LookupImplWitness` +// instructions. The latter will evaluate at some point during specific +// computation into one of the former two, and their types should not change in +// the process. // // Also the type of `RequireCompleteType` instructions. //