From 80a3dc83bc0c4bf951079f27762966edc915d3d9 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 4 Dec 2024 13:38:19 -0800 Subject: [PATCH] Fix impl lookup to properly look in the IR containing a transitively-imported entity. (#4625) --- toolchain/check/handle_function.cpp | 3 +- toolchain/check/impl_lookup.cpp | 7 +- toolchain/check/import.cpp | 6 +- toolchain/check/import_ref.cpp | 65 ++-- toolchain/check/import_ref.h | 7 +- .../testdata/impl/lookup/transitive.carbon | 307 ++++++++++++++++++ 6 files changed, 353 insertions(+), 42 deletions(-) create mode 100644 toolchain/check/testdata/impl/lookup/transitive.carbon diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 0c1caa059140..6fd5f6fdc039 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -131,8 +131,7 @@ static auto TryMergeRedecl(Context& context, Parse::AnyFunctionDeclId node_id, break; } case SemIR::ImportRefLoaded::Kind: { - auto import_ir_inst = - GetCanonicalImportIRInst(context, &context.sem_ir(), prev_id); + auto import_ir_inst = GetCanonicalImportIRInst(context, prev_id); // Verify the decl so that things like aliases are name conflicts. const auto* import_ir = diff --git a/toolchain/check/impl_lookup.cpp b/toolchain/check/impl_lookup.cpp index 43743303aaa1..e0dc94187ccd 100644 --- a/toolchain/check/impl_lookup.cpp +++ b/toolchain/check/impl_lookup.cpp @@ -25,10 +25,9 @@ static auto FindAssociatedImportIRs(Context& context, if (!decl_id.is_valid()) { return; } - auto loc_id = context.insts().GetLocId(decl_id); - if (loc_id.is_import_ir_inst_id()) { - result.push_back( - context.import_ir_insts().Get(loc_id.import_ir_inst_id()).ir_id); + if (auto ir_id = GetCanonicalImportIRInst(context, decl_id).ir_id; + ir_id.is_valid()) { + result.push_back(ir_id); } }; diff --git a/toolchain/check/import.cpp b/toolchain/check/import.cpp index c41ff906c180..747a0de859df 100644 --- a/toolchain/check/import.cpp +++ b/toolchain/check/import.cpp @@ -284,8 +284,7 @@ static auto AddImportRefOrMerge(Context& context, SemIR::ImportIRId ir_id, } auto inst_id = parent_scope.names[insert.value()].inst_id; - auto prev_ir_inst = - GetCanonicalImportIRInst(context, &context.sem_ir(), inst_id); + auto prev_ir_inst = GetCanonicalImportIRInst(context, inst_id); VerifySameCanonicalImportIRInst(context, inst_id, prev_ir_inst, ir_id, &import_sem_ir, import_inst_id); } @@ -594,8 +593,7 @@ auto ImportNameFromOtherPackage( // When there's a name collision, they need to either be the same canonical // instruction, or we'll diagnose. if (!canonical_result_inst) { - canonical_result_inst = - GetCanonicalImportIRInst(context, &context.sem_ir(), result_id); + canonical_result_inst = GetCanonicalImportIRInst(context, result_id); } VerifySameCanonicalImportIRInst(context, result_id, *canonical_result_inst, import_ir_id, import_ir.sem_ir, diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index 84952a1e90dc..95a2b506a2ba 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -93,38 +93,47 @@ static auto AddLoadedImportRef(Context& context, SemIR::TypeId type_id, return inst_id; } -auto GetCanonicalImportIRInst(Context& context, const SemIR::File* cursor_ir, - SemIR::InstId cursor_inst_id) +static auto GetCanonicalImportIRInst(Context& context, + const SemIR::File* cursor_ir, + SemIR::InstId cursor_inst_id) -> SemIR::ImportIRInst { while (true) { - auto inst = cursor_ir->insts().Get(cursor_inst_id); - CARBON_KIND_SWITCH(inst) { - case CARBON_KIND(SemIR::ExportDecl bind_export): { - cursor_inst_id = bind_export.value_id; - continue; - } - case SemIR::ImportRefLoaded::Kind: - case SemIR::ImportRefUnloaded::Kind: { - auto import_ref = inst.As(); - auto import_ir_inst = - cursor_ir->import_ir_insts().Get(import_ref.import_ir_inst_id); - cursor_ir = cursor_ir->import_irs().Get(import_ir_inst.ir_id).sem_ir; - cursor_inst_id = import_ir_inst.inst_id; - continue; - } - default: { - auto ir_id = SemIR::ImportIRId::Invalid; - if (cursor_ir != &context.sem_ir()) { - // This uses AddImportIR in case it was indirectly found, which can - // happen with two or more steps of exports. - ir_id = AddImportIR(context, {.decl_id = SemIR::InstId::Invalid, - .is_export = false, - .sem_ir = cursor_ir}); - } - return {.ir_id = ir_id, .inst_id = cursor_inst_id}; - } + // Step through an instruction with an imported location to the imported + // instruction. + auto loc_id = cursor_ir->insts().GetLocId(cursor_inst_id); + if (loc_id.is_import_ir_inst_id()) { + auto import_ir_inst = + cursor_ir->import_ir_insts().Get(loc_id.import_ir_inst_id()); + cursor_ir = cursor_ir->import_irs().Get(import_ir_inst.ir_id).sem_ir; + cursor_inst_id = import_ir_inst.inst_id; + continue; } + + // Step through export declarations to their exported value. + if (auto export_decl = + cursor_ir->insts().TryGetAs(cursor_inst_id)) { + cursor_inst_id = export_decl->value_id; + continue; + } + + // Reached a non-imported entity. + break; } + + auto ir_id = SemIR::ImportIRId::Invalid; + if (cursor_ir != &context.sem_ir()) { + // This uses AddImportIR in case it was indirectly found, which can + // happen with two or more steps of exports. + ir_id = AddImportIR(context, {.decl_id = SemIR::InstId::Invalid, + .is_export = false, + .sem_ir = cursor_ir}); + } + return {.ir_id = ir_id, .inst_id = cursor_inst_id}; +} + +auto GetCanonicalImportIRInst(Context& context, SemIR::InstId inst_id) + -> SemIR::ImportIRInst { + return GetCanonicalImportIRInst(context, &context.sem_ir(), inst_id); } auto VerifySameCanonicalImportIRInst(Context& context, SemIR::InstId prev_id, diff --git a/toolchain/check/import_ref.h b/toolchain/check/import_ref.h index e8dabd6e9ee9..4bbc62396872 100644 --- a/toolchain/check/import_ref.h +++ b/toolchain/check/import_ref.h @@ -24,10 +24,9 @@ auto AddImportIR(Context& context, SemIR::ImportIR import_ir) auto AddImportRef(Context& context, SemIR::ImportIRInst import_ir_inst, SemIR::EntityNameId entity_name_id) -> SemIR::InstId; -// Returns the canonical IR inst for an entity. Returns an invalid ir_id for the -// current IR. -auto GetCanonicalImportIRInst(Context& context, const SemIR::File* cursor_ir, - SemIR::InstId cursor_inst_id) +// Returns the canonical IR inst for an entity. Returns an `ImportIRInst` with +// an invalid ir_id for an entity that was not imported. +auto GetCanonicalImportIRInst(Context& context, SemIR::InstId inst_id) -> SemIR::ImportIRInst; // Verifies a new instruction is the same as a previous instruction. diff --git a/toolchain/check/testdata/impl/lookup/transitive.carbon b/toolchain/check/testdata/impl/lookup/transitive.carbon new file mode 100644 index 000000000000..1694fc3b2454 --- /dev/null +++ b/toolchain/check/testdata/impl/lookup/transitive.carbon @@ -0,0 +1,307 @@ +// 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 +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/lookup/transitive.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/lookup/transitive.carbon + +// --- i.carbon + +library "[[@TEST_NAME]]"; + +interface I { fn F(); } + +// --- c.carbon + +library "[[@TEST_NAME]]"; + +import library "i"; + +class C {} +impl C as I { fn F() {} } + +// --- get.carbon + +library "[[@TEST_NAME]]"; + +import library "c"; + +fn Get() -> C; + +// --- call.carbon + +library "[[@TEST_NAME]]"; + +import library "get"; +import library "i"; + +fn Call() { + // We find C in "get", but should look in "c" instead. + Get().(I.F)(); +} + +// CHECK:STDOUT: --- i.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %I.type: type = facet_type <@I> [template] +// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic] +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: %F.assoc_type: type = assoc_entity_type %I.type, %F.type [template] +// CHECK:STDOUT: %assoc0: %F.assoc_type = assoc_entity element0, @I.%F.decl [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: .I = %I.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %I.decl: type = interface_decl @I [template = constants.%I.type] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @I { +// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self] +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {} +// CHECK:STDOUT: %assoc0: %F.assoc_type = assoc_entity element0, %F.decl [template = constants.%assoc0] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = %Self +// CHECK:STDOUT: .F = %assoc0 +// CHECK:STDOUT: witness = (%F.decl) +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: generic fn @F(@I.%Self: %I.type) { +// CHECK:STDOUT: +// CHECK:STDOUT: fn(); +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: specific @F(constants.%Self) {} +// CHECK:STDOUT: +// CHECK:STDOUT: --- c.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template] +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [template] +// CHECK:STDOUT: %I.type: type = facet_type <@I> [template] +// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic] +// CHECK:STDOUT: %F.type.1: type = fn_type @F.1 [template] +// CHECK:STDOUT: %F.1: %F.type.1 = struct_value () [template] +// CHECK:STDOUT: %F.type.2: type = fn_type @F.2 [template] +// CHECK:STDOUT: %F.2: %F.type.2 = struct_value () [template] +// CHECK:STDOUT: %I.facet: %I.type = facet_value %C, %C [template] +// CHECK:STDOUT: %interface: = interface_witness (%F.1) [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %import_ref.1: type = import_ref Main//i, inst+3, loaded [template = constants.%I.type] +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref.2 = import_ref Main//i, inst+5, unloaded +// CHECK:STDOUT: %import_ref.3 = import_ref Main//i, inst+12, unloaded +// CHECK:STDOUT: %import_ref.4: %F.type.2 = import_ref Main//i, inst+7, loaded [template = constants.%F.2] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .I = imports.%import_ref.1 +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .C = %C.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %default.import = import +// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} {} +// CHECK:STDOUT: impl_decl @impl [template] {} { +// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [template = constants.%C] +// CHECK:STDOUT: %I.ref: type = name_ref I, imports.%import_ref.1 [template = constants.%I.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @I [from "i.carbon"] { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = imports.%import_ref.2 +// CHECK:STDOUT: .F = imports.%import_ref.3 +// CHECK:STDOUT: witness = (imports.%import_ref.4) +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @impl: %C.ref as %I.ref { +// CHECK:STDOUT: %F.decl: %F.type.1 = fn_decl @F.1 [template = constants.%F.1] {} {} +// CHECK:STDOUT: %interface: = interface_witness (%F.decl) [template = constants.%interface] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: witness = %interface +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [template = constants.%complete_type] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%C +// CHECK:STDOUT: complete_type_witness = %complete_type +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.1() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: generic fn @F.2(constants.%Self: %I.type) [from "i.carbon"] { +// CHECK:STDOUT: +// CHECK:STDOUT: fn(); +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: specific @F.2(constants.%Self) {} +// CHECK:STDOUT: +// CHECK:STDOUT: specific @F.2(constants.%I.facet) {} +// CHECK:STDOUT: +// CHECK:STDOUT: --- get.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template] +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [template] +// CHECK:STDOUT: %Get.type: type = fn_type @Get [template] +// CHECK:STDOUT: %Get: %Get.type = struct_value () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %import_ref.1: type = import_ref Main//c, inst+5, loaded [template = constants.%C] +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref.2: = import_ref Main//c, inst+8, loaded [template = constants.%complete_type] +// CHECK:STDOUT: %import_ref.3 = import_ref Main//c, inst+6, unloaded +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .C = imports.%import_ref.1 +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .Get = %Get.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %default.import = import +// CHECK:STDOUT: %Get.decl: %Get.type = fn_decl @Get [template = constants.%Get] { +// CHECK:STDOUT: %return.patt: %C = return_slot_pattern +// CHECK:STDOUT: %return.param_patt: %C = out_param_pattern %return.patt, runtime_param0 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %C.ref: type = name_ref C, imports.%import_ref.1 [template = constants.%C] +// CHECK:STDOUT: %return.param: ref %C = out_param runtime_param0 +// CHECK:STDOUT: %return: ref %C = return_slot %return.param +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C [from "c.carbon"] { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = imports.%import_ref.3 +// CHECK:STDOUT: complete_type_witness = imports.%import_ref.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Get() -> %C; +// CHECK:STDOUT: +// CHECK:STDOUT: --- call.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Call.type: type = fn_type @Call [template] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [template] +// CHECK:STDOUT: %Call: %Call.type = struct_value () [template] +// CHECK:STDOUT: %Get.type: type = fn_type @Get [template] +// CHECK:STDOUT: %Get: %Get.type = struct_value () [template] +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template] +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [template] +// CHECK:STDOUT: %I.type: type = facet_type <@I> [template] +// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic] +// CHECK:STDOUT: %F.type.1: type = fn_type @F.1 [template] +// CHECK:STDOUT: %F.assoc_type: type = assoc_entity_type %I.type, %F.type.1 [template] +// CHECK:STDOUT: %assoc0: %F.assoc_type = assoc_entity element0, imports.%import_ref.8 [template] +// CHECK:STDOUT: %F.type.2: type = fn_type @F.2 [template] +// CHECK:STDOUT: %F.2: %F.type.2 = struct_value () [template] +// CHECK:STDOUT: %interface: = interface_witness (%F.2) [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %import_ref.1: %Get.type = import_ref Main//get, inst+16, loaded [template = constants.%Get] +// CHECK:STDOUT: %import_ref.2: type = import_ref Main//i, inst+3, loaded [template = constants.%I.type] +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref.3: = import_ref Main//get, inst+9, loaded [template = constants.%complete_type] +// CHECK:STDOUT: %import_ref.4 = import_ref Main//get, inst+10, unloaded +// CHECK:STDOUT: %import_ref.5 = import_ref Main//i, inst+5, unloaded +// CHECK:STDOUT: %import_ref.6: %F.assoc_type = import_ref Main//i, inst+12, loaded [template = constants.%assoc0] +// CHECK:STDOUT: %import_ref.7 = import_ref Main//i, inst+7, unloaded +// CHECK:STDOUT: %import_ref.9: type = import_ref Main//c, inst+10, loaded [template = constants.%C] +// CHECK:STDOUT: %import_ref.10: type = import_ref Main//c, inst+17, loaded [template = constants.%I.type] +// CHECK:STDOUT: %import_ref.11: = import_ref Main//c, inst+27, loaded [template = constants.%interface] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Get = imports.%import_ref.1 +// CHECK:STDOUT: .I = imports.%import_ref.2 +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .Call = %Call.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %default.import = import +// CHECK:STDOUT: %Call.decl: %Call.type = fn_decl @Call [template = constants.%Call] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @I [from "i.carbon"] { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = imports.%import_ref.5 +// CHECK:STDOUT: .F = imports.%import_ref.6 +// CHECK:STDOUT: witness = (imports.%import_ref.7) +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @impl: imports.%import_ref.9 as imports.%import_ref.10 [from "c.carbon"] { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: witness = imports.%import_ref.11 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C [from "get.carbon"] { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = imports.%import_ref.4 +// CHECK:STDOUT: complete_type_witness = imports.%import_ref.3 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Call() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Get.ref: %Get.type = name_ref Get, imports.%import_ref.1 [template = constants.%Get] +// CHECK:STDOUT: %.loc9: ref %C = temporary_storage +// CHECK:STDOUT: %Get.call: init %C = call %Get.ref() to %.loc9 +// CHECK:STDOUT: %I.ref: type = name_ref I, imports.%import_ref.2 [template = constants.%I.type] +// CHECK:STDOUT: %F.ref: %F.assoc_type = name_ref F, imports.%import_ref.6 [template = constants.%assoc0] +// CHECK:STDOUT: %impl.elem0: %F.type.1 = interface_witness_access constants.%interface, element0 [template = constants.%F.2] +// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %impl.elem0() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Get() -> %C [from "get.carbon"]; +// CHECK:STDOUT: +// CHECK:STDOUT: generic fn @F.1(constants.%Self: %I.type) [from "i.carbon"] { +// CHECK:STDOUT: +// CHECK:STDOUT: fn(); +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.2() [from "c.carbon"]; +// CHECK:STDOUT: +// CHECK:STDOUT: specific @F.1(constants.%Self) {} +// CHECK:STDOUT: