From 88dac35ae8f49b95a7c6d8fc0041f1c6bb4830da Mon Sep 17 00:00:00 2001 From: Boaz Brickner Date: Wed, 24 Sep 2025 12:11:09 +0200 Subject: [PATCH] Add support for C++ member operators (#6112) Call `Sema::AddMemberOperatorCandidates()` to properly add candidates. For C++ member operator calls, use the first arg as self. C++ Interop Demo: ```c++ // my_number.h class MyNumber { public: explicit MyNumber(int value) : value_(value) {} int value() const { return value_; } auto operator++() -> MyNumber; private: int value_; }; ``` ```c++ // my_number.cpp #include "my_number.h" auto MyNumber::operator++() -> MyNumber { ++value_; return *this;; } ``` ```carbon // main.carbon library "Main"; import Core library "io"; import Cpp library "my_number.h"; fn Run() -> i32 { var num: Cpp.MyNumber = Cpp.MyNumber.MyNumber(14); Core.Print(num.value()); ++num; Core.Print(num.value()); return 0; } ``` ```shell $ clang -c my_number.cpp $ bazel-bin/toolchain/carbon compile main.carbon $ bazel-bin/toolchain/carbon link my_number.o main.o --output=demo $ ./demo 14 15 ``` Part of https://github.com/carbon-language/carbon-lang/issues/5995. --- toolchain/check/call.cpp | 30 ++-- toolchain/check/cpp/overload_resolution.cpp | 87 ++++++++++-- toolchain/check/cpp/overload_resolution.h | 25 +++- .../interop/cpp/function/operators.carbon | 131 +++++++++++------- 4 files changed, 186 insertions(+), 87 deletions(-) diff --git a/toolchain/check/call.cpp b/toolchain/check/call.cpp index ca751a4213da..9447dfa428b7 100644 --- a/toolchain/check/call.cpp +++ b/toolchain/check/call.cpp @@ -337,27 +337,17 @@ auto PerformCall(Context& context, SemIR::LocId loc_id, SemIR::InstId callee_id, } case CARBON_KIND(SemIR::CalleeCppOverloadSet overload): { - callee_id = PerformCppOverloadResolution(context, loc_id, - overload.cpp_overload_set_id, - overload.self_id, arg_ids); - auto overload_result = GetCallee(context.sem_ir(), callee_id); - CARBON_KIND_SWITCH(overload_result) { - case CARBON_KIND(SemIR::CalleeError _): { - return SemIR::ErrorInst::InstId; - } - case CARBON_KIND(SemIR::CalleeFunction fn): { - // Preserve the `self` argument from the original callee. - CARBON_CHECK(!fn.self_id.has_value()); - fn.self_id = overload.self_id; - return PerformCallToFunction(context, loc_id, callee_id, fn, arg_ids); - } - case CARBON_KIND(SemIR::CalleeCppOverloadSet _): { - CARBON_FATAL("overloads can't be recursive"); - } - case CARBON_KIND(SemIR::CalleeNonFunction _): { - CARBON_FATAL("overloads should produce functions"); - } + CppOverloadResolutionResult overload_result = + PerformCppOverloadResolution(context, loc_id, + overload.cpp_overload_set_id, + overload.self_id, arg_ids); + if (overload_result.callee_id == SemIR::ErrorInst::InstId) { + return SemIR::ErrorInst::InstId; } + CARBON_CHECK(overload_result.callee_function); + return PerformCallToFunction(context, loc_id, overload_result.callee_id, + *overload_result.callee_function, + overload_result.arg_ids); } } } diff --git a/toolchain/check/cpp/overload_resolution.cpp b/toolchain/check/cpp/overload_resolution.cpp index a3ebd59e8381..b05fa0429ba7 100644 --- a/toolchain/check/cpp/overload_resolution.cpp +++ b/toolchain/check/cpp/overload_resolution.cpp @@ -7,10 +7,12 @@ #include "clang/Basic/DiagnosticSema.h" #include "clang/Sema/Overload.h" #include "clang/Sema/Sema.h" +#include "toolchain/base/kind_switch.h" #include "toolchain/check/cpp/import.h" #include "toolchain/check/cpp/location.h" #include "toolchain/check/cpp/type_mapping.h" #include "toolchain/diagnostics/diagnostic_emitter.h" +#include "toolchain/sem_ir/function.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/typed_insts.h" @@ -56,29 +58,39 @@ static auto AddOverloadCandidataes(clang::Sema& sema, cast(template_decl->getDeclContext()), ExplicitTemplateArgs, self_type, self_classification, args, candidate_set, SuppressUserConversions, PartialOverloading); + } else if (method_decl->isOverloadedOperator()) { + sema.AddMemberOperatorCandidates(method_decl->getOverloadedOperator(), + candidate_set.getLocation(), args, + candidate_set); } else { sema.AddMethodCandidate(method_decl, found_decl, method_decl->getParent(), self_type, self_classification, args, candidate_set, SuppressUserConversions, PartialOverloading); } + } else if (template_decl) { + sema.AddTemplateOverloadCandidate( + template_decl, found_decl, ExplicitTemplateArgs, args, candidate_set, + SuppressUserConversions, PartialOverloading); } else { - if (template_decl) { - sema.AddTemplateOverloadCandidate( - template_decl, found_decl, ExplicitTemplateArgs, args, - candidate_set, SuppressUserConversions, PartialOverloading); - } else { - sema.AddOverloadCandidate(fn_decl, found_decl, args, candidate_set, - SuppressUserConversions, PartialOverloading); - } + sema.AddOverloadCandidate(fn_decl, found_decl, args, candidate_set, + SuppressUserConversions, PartialOverloading); } } } -auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id, - SemIR::CppOverloadSetId overload_set_id, - SemIR::InstId self_id, - llvm::ArrayRef arg_ids) +// Returns whether the decl is an operator member function. +static auto IsOperatorMethodDecl(clang::Decl* decl) -> bool { + auto* clang_method_decl = dyn_cast(decl); + return clang_method_decl && clang_method_decl->isOverloadedOperator(); +} + +// Resolve which function to call, or returns an error instruction if overload +// resolution failed. +static auto ResolveCalleeId(Context& context, SemIR::LocId loc_id, + SemIR::CppOverloadSetId overload_set_id, + SemIR::InstId self_id, + llvm::ArrayRef arg_ids) -> SemIR::InstId { // Register an annotation scope to flush any Clang diagnostics when we return. // This is important to ensure that Clang diagnostics are properly interleaved @@ -125,7 +137,10 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id, CARBON_CHECK(best_viable_fn->Function); sema.MarkFunctionReferenced(loc, best_viable_fn->Function); SemIR::InstId result = ImportCppFunctionDecl( - context, loc_id, best_viable_fn->Function, arg_exprs.size()); + context, loc_id, best_viable_fn->Function, + // If this is an operator method, the first arg will be used as self. + arg_exprs.size() - + (IsOperatorMethodDecl(best_viable_fn->Function) ? 1 : 0)); return result; } case clang::OverloadingResult::OR_No_Viable_Function: { @@ -154,4 +169,50 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id, } } +// Returns whether the function is an imported C++ operator member function. +static auto IsCppOperatorMethod(Context& context, SemIR::FunctionId function_id) + -> bool { + SemIR::ClangDeclId clang_decl_id = + context.functions().Get(function_id).clang_decl_id; + return clang_decl_id.has_value() && + IsOperatorMethodDecl( + context.clang_decls().Get(clang_decl_id).key.decl); +} + +auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id, + SemIR::CppOverloadSetId overload_set_id, + SemIR::InstId self_id, + llvm::ArrayRef arg_ids) + -> CppOverloadResolutionResult { + CppOverloadResolutionResult result = { + .callee_id = + ResolveCalleeId(context, loc_id, overload_set_id, self_id, arg_ids), + .arg_ids = arg_ids}; + SemIR::Callee callee = GetCallee(context.sem_ir(), result.callee_id); + CARBON_KIND_SWITCH(callee) { + case CARBON_KIND(SemIR::CalleeError _): { + result.callee_id = SemIR::ErrorInst::InstId; + return result; + } + case CARBON_KIND(SemIR::CalleeFunction fn): { + CARBON_CHECK(!fn.self_id.has_value()); + if (self_id.has_value()) { + // Preserve the `self` argument from the original callee. + fn.self_id = self_id; + } else if (IsCppOperatorMethod(context, fn.function_id)) { + // Adjust `self` and args for C++ overloaded operator methods. + fn.self_id = result.arg_ids.consume_front(); + } + result.callee_function = fn; + return result; + } + case CARBON_KIND(SemIR::CalleeCppOverloadSet _): { + CARBON_FATAL("overloads can't be recursive"); + } + case CARBON_KIND(SemIR::CalleeNonFunction _): { + CARBON_FATAL("overloads should produce functions"); + } + } +} + } // namespace Carbon::Check diff --git a/toolchain/check/cpp/overload_resolution.h b/toolchain/check/cpp/overload_resolution.h index 438f5fa2f611..cad8763caec7 100644 --- a/toolchain/check/cpp/overload_resolution.h +++ b/toolchain/check/cpp/overload_resolution.h @@ -6,15 +6,34 @@ #define CARBON_TOOLCHAIN_CHECK_CPP_OVERLOAD_RESOLUTION_H_ #include "toolchain/check/context.h" +#include "toolchain/sem_ir/function.h" #include "toolchain/sem_ir/ids.h" namespace Carbon::Check { +// The result of performing C++ overload resolution. +struct CppOverloadResolutionResult { + // The resolved callee id, or ErrorInst::InstId on error. + SemIR::InstId callee_id; + + // The resolved callee, which may be different from the result of + // `GetCalleeFunction()` to preserve self or set it to be the first argument + // for C++ member operators. Not set if overload resolution failed. + std::optional callee_function; + + // The arguments to pass to the callee, which may be different from the + // original arguments if the callee is a C++ member operator. + llvm::ArrayRef arg_ids; +}; + // Performs overloading resolution for a call to an overloaded C++ set. A set // with a single non-templated function goes through the same rules for // overloading resolution. Uses Clang to find the best viable function for the -// call. Returns the resolved function, or an error instruction if overload -// resolution failed. +// call. +// +// The callee function preserves the given self, if set. If not set, and the +// function is a a C++ member operator, self will be set to the first argument, +// which in turn will be removed from the given args. // // Note on non-overloaded functions: In C++, a single non-templated function is // also treated as an overloaded set and goes through the overload resolution to @@ -27,7 +46,7 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id, SemIR::CppOverloadSetId overload_set_id, SemIR::InstId self_id, llvm::ArrayRef arg_ids) - -> SemIR::InstId; + -> CppOverloadResolutionResult; } // namespace Carbon::Check diff --git a/toolchain/check/testdata/interop/cpp/function/operators.carbon b/toolchain/check/testdata/interop/cpp/function/operators.carbon index 500742b0cbbd..1feb86cd4a7a 100644 --- a/toolchain/check/testdata/interop/cpp/function/operators.carbon +++ b/toolchain/check/testdata/interop/cpp/function/operators.carbon @@ -677,10 +677,14 @@ fn F() { class C { public: + // Unary. + auto operator-() -> C; + + // Binary. auto operator+(C rhs) -> C; }; -// --- fail_todo_import_member_add_with.carbon +// --- import_member_add_with.carbon library "[[@TEST_NAME]]"; @@ -688,17 +692,9 @@ import Cpp library "member_add_with.h"; fn F() { //@dump-sem-ir-begin - let c1: Cpp.C = Cpp.C.C(); - let c2: Cpp.C = Cpp.C.C(); - // CHECK:STDERR: fail_todo_import_member_add_with.carbon:[[@LINE+8]]:22: error: no matching function for call to '' [CppInteropParseError] - // CHECK:STDERR: 18 | let c3: Cpp.C = c1 + c2; - // CHECK:STDERR: | ^ - // CHECK:STDERR: fail_todo_import_member_add_with.carbon:[[@LINE-9]]:10: in file included here [InCppInclude] - // CHECK:STDERR: ./member_add_with.h:4:8: note: candidate function not viable: requires single argument 'rhs', but 2 arguments were provided [CppInteropParseNote] - // CHECK:STDERR: 4 | auto operator+(C rhs) -> C; - // CHECK:STDERR: | ^ ~~~~~ - // CHECK:STDERR: - let c3: Cpp.C = c1 + c2; + var c1: Cpp.C = Cpp.C.C(); + var c2: Cpp.C = -c1; + var c3: Cpp.C = c1 + c2; //@dump-sem-ir-end } @@ -2652,17 +2648,21 @@ fn F() { // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: --- fail_todo_import_member_add_with.carbon +// CHECK:STDOUT: --- import_member_add_with.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] // CHECK:STDOUT: %C: type = class_type @C [concrete] // CHECK:STDOUT: %pattern_type.217: type = pattern_type %C [concrete] -// CHECK:STDOUT: %.d40: type = cpp_overload_set_type @C__carbon_thunk [concrete] +// CHECK:STDOUT: %.d40: type = cpp_overload_set_type @C.cpp_operator.1 [concrete] // CHECK:STDOUT: %empty_struct.e73: %.d40 = struct_value () [concrete] // CHECK:STDOUT: %ptr.d9e: type = ptr_type %C [concrete] // CHECK:STDOUT: %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete] // CHECK:STDOUT: %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete] +// CHECK:STDOUT: %operator-__carbon_thunk.type: type = fn_type @operator-__carbon_thunk [concrete] +// CHECK:STDOUT: %operator-__carbon_thunk: %operator-__carbon_thunk.type = struct_value () [concrete] +// CHECK:STDOUT: %operator+__carbon_thunk.type: type = fn_type @operator+__carbon_thunk [concrete] +// CHECK:STDOUT: %operator+__carbon_thunk: %operator+__carbon_thunk.type = struct_value () [concrete] // CHECK:STDOUT: %type_where: type = facet_type > [concrete] // CHECK:STDOUT: %facet_value: %type_where = facet_value %C, () [concrete] // CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.type.fc1: type = fn_type @AggregateT.as_type.as.Destroy.impl.Op, @AggregateT.as_type.as.Destroy.impl(%facet_value) [concrete] @@ -2675,74 +2675,103 @@ fn F() { // CHECK:STDOUT: import Cpp//... // CHECK:STDOUT: } // CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {} -// CHECK:STDOUT: %.40b: %.d40 = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.e73] +// CHECK:STDOUT: %.40b: %.d40 = cpp_overload_set_value @C.cpp_operator.1 [concrete = constants.%empty_struct.e73] // CHECK:STDOUT: %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] { // CHECK:STDOUT: // CHECK:STDOUT: } { // CHECK:STDOUT: // CHECK:STDOUT: } +// CHECK:STDOUT: %operator-__carbon_thunk.decl: %operator-__carbon_thunk.type = fn_decl @operator-__carbon_thunk [concrete = constants.%operator-__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: %operator+__carbon_thunk.decl: %operator+__carbon_thunk.type = fn_decl @operator+__carbon_thunk [concrete = constants.%operator+__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: name_binding_decl { // CHECK:STDOUT: %c1.patt: %pattern_type.217 = binding_pattern c1 [concrete] +// CHECK:STDOUT: %c1.var_patt: %pattern_type.217 = var_pattern %c1.patt [concrete] // CHECK:STDOUT: } +// CHECK:STDOUT: %c1.var: ref %C = var %c1.var_patt // CHECK:STDOUT: %Cpp.ref.loc8_19: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %C.ref.loc8_22: type = name_ref C, imports.%C.decl [concrete = constants.%C] // CHECK:STDOUT: %C.ref.loc8_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73] -// CHECK:STDOUT: %.loc8_27.1: ref %C = temporary_storage -// CHECK:STDOUT: %addr.loc8_27.1: %ptr.d9e = addr_of %.loc8_27.1 -// CHECK:STDOUT: %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_27.1) -// CHECK:STDOUT: %.loc8_27.2: init %C = in_place_init %C__carbon_thunk.call.loc8, %.loc8_27.1 +// CHECK:STDOUT: %.loc8_3.1: ref %C = splice_block %c1.var {} +// CHECK:STDOUT: %addr.loc8_27: %ptr.d9e = addr_of %.loc8_3.1 +// CHECK:STDOUT: %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_27) +// CHECK:STDOUT: %.loc8_27: init %C = in_place_init %C__carbon_thunk.call, %.loc8_3.1 +// CHECK:STDOUT: assign %c1.var, %.loc8_27 // CHECK:STDOUT: %.loc8_14: type = splice_block %C.ref.loc8_14 [concrete = constants.%C] { // CHECK:STDOUT: %Cpp.ref.loc8_11: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %C.ref.loc8_14: type = name_ref C, imports.%C.decl [concrete = constants.%C] // CHECK:STDOUT: } -// CHECK:STDOUT: %.loc8_27.3: ref %C = temporary %.loc8_27.1, %.loc8_27.2 -// CHECK:STDOUT: %.loc8_27.4: %C = bind_value %.loc8_27.3 -// CHECK:STDOUT: %c1: %C = bind_name c1, %.loc8_27.4 +// CHECK:STDOUT: %c1: ref %C = bind_name c1, %c1.var // CHECK:STDOUT: name_binding_decl { // CHECK:STDOUT: %c2.patt: %pattern_type.217 = binding_pattern c2 [concrete] +// CHECK:STDOUT: %c2.var_patt: %pattern_type.217 = var_pattern %c2.patt [concrete] // CHECK:STDOUT: } -// CHECK:STDOUT: %Cpp.ref.loc9_19: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] -// CHECK:STDOUT: %C.ref.loc9_22: type = name_ref C, imports.%C.decl [concrete = constants.%C] -// CHECK:STDOUT: %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73] -// CHECK:STDOUT: %.loc9_27.1: ref %C = temporary_storage -// CHECK:STDOUT: %addr.loc9_27.1: %ptr.d9e = addr_of %.loc9_27.1 -// CHECK:STDOUT: %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_27.1) -// CHECK:STDOUT: %.loc9_27.2: init %C = in_place_init %C__carbon_thunk.call.loc9, %.loc9_27.1 -// CHECK:STDOUT: %.loc9_14: type = splice_block %C.ref.loc9_14 [concrete = constants.%C] { -// CHECK:STDOUT: %Cpp.ref.loc9_11: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] -// CHECK:STDOUT: %C.ref.loc9_14: type = name_ref C, imports.%C.decl [concrete = constants.%C] +// CHECK:STDOUT: %c2.var: ref %C = var %c2.var_patt +// CHECK:STDOUT: %c1.ref.loc9: ref %C = name_ref c1, %c1 +// CHECK:STDOUT: %.loc9_3.1: ref %C = splice_block %c2.var {} +// CHECK:STDOUT: %addr.loc9_20: %ptr.d9e = addr_of %c1.ref.loc9 +// CHECK:STDOUT: %addr.loc9_19: %ptr.d9e = addr_of %.loc9_3.1 +// CHECK:STDOUT: %operator-__carbon_thunk.call: init %empty_tuple.type = call imports.%operator-__carbon_thunk.decl(%addr.loc9_20, %addr.loc9_19) +// CHECK:STDOUT: %.loc9_19: init %C = in_place_init %operator-__carbon_thunk.call, %.loc9_3.1 +// CHECK:STDOUT: assign %c2.var, %.loc9_19 +// CHECK:STDOUT: %.loc9_14: type = splice_block %C.ref.loc9 [concrete = constants.%C] { +// CHECK:STDOUT: %Cpp.ref.loc9: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] +// CHECK:STDOUT: %C.ref.loc9: type = name_ref C, imports.%C.decl [concrete = constants.%C] // CHECK:STDOUT: } -// CHECK:STDOUT: %.loc9_27.3: ref %C = temporary %.loc9_27.1, %.loc9_27.2 -// CHECK:STDOUT: %.loc9_27.4: %C = bind_value %.loc9_27.3 -// CHECK:STDOUT: %c2: %C = bind_name c2, %.loc9_27.4 +// CHECK:STDOUT: %c2: ref %C = bind_name c2, %c2.var // CHECK:STDOUT: name_binding_decl { // CHECK:STDOUT: %c3.patt: %pattern_type.217 = binding_pattern c3 [concrete] +// CHECK:STDOUT: %c3.var_patt: %pattern_type.217 = var_pattern %c3.patt [concrete] // CHECK:STDOUT: } -// CHECK:STDOUT: %c1.ref: %C = name_ref c1, %c1 -// CHECK:STDOUT: %c2.ref: %C = name_ref c2, %c2 -// CHECK:STDOUT: %.loc18: type = splice_block %C.ref.loc18 [concrete = constants.%C] { -// CHECK:STDOUT: %Cpp.ref.loc18: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] -// CHECK:STDOUT: %C.ref.loc18: type = name_ref C, imports.%C.decl [concrete = constants.%C] +// CHECK:STDOUT: %c3.var: ref %C = var %c3.var_patt +// CHECK:STDOUT: %c1.ref.loc10: ref %C = name_ref c1, %c1 +// CHECK:STDOUT: %c2.ref: ref %C = name_ref c2, %c2 +// CHECK:STDOUT: %.loc10_3.1: ref %C = splice_block %c3.var {} +// CHECK:STDOUT: %addr.loc10_19: %ptr.d9e = addr_of %c1.ref.loc10 +// CHECK:STDOUT: %.loc10_24.1: %C = bind_value %c2.ref +// CHECK:STDOUT: %.loc10_24.2: ref %C = value_as_ref %.loc10_24.1 +// CHECK:STDOUT: %addr.loc10_22.1: %ptr.d9e = addr_of %.loc10_24.2 +// CHECK:STDOUT: %addr.loc10_22.2: %ptr.d9e = addr_of %.loc10_3.1 +// CHECK:STDOUT: %operator+__carbon_thunk.call: init %empty_tuple.type = call imports.%operator+__carbon_thunk.decl(%addr.loc10_19, %addr.loc10_22.1, %addr.loc10_22.2) +// CHECK:STDOUT: %.loc10_22: init %C = in_place_init %operator+__carbon_thunk.call, %.loc10_3.1 +// CHECK:STDOUT: assign %c3.var, %.loc10_22 +// CHECK:STDOUT: %.loc10_14: type = splice_block %C.ref.loc10 [concrete = constants.%C] { +// CHECK:STDOUT: %Cpp.ref.loc10: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] +// CHECK:STDOUT: %C.ref.loc10: type = name_ref C, imports.%C.decl [concrete = constants.%C] // CHECK:STDOUT: } -// CHECK:STDOUT: %c3: %C = bind_name c3, [concrete = ] +// CHECK:STDOUT: %c3: ref %C = bind_name c3, %c3.var +// CHECK:STDOUT: %facet_value.loc10: %type_where = facet_value constants.%C, () [concrete = constants.%facet_value] +// CHECK:STDOUT: %.loc10_3.2: %type_where = converted constants.%C, %facet_value.loc10 [concrete = constants.%facet_value] +// CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.bound.loc10: = bound_method %c3.var, constants.%AggregateT.as_type.as.Destroy.impl.Op.6b9 +// CHECK:STDOUT: +// CHECK:STDOUT: %bound_method.loc10: = bound_method %c3.var, %AggregateT.as_type.as.Destroy.impl.Op.specific_fn.1 +// CHECK:STDOUT: %addr.loc10_3: %ptr.d9e = addr_of %c3.var +// CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.call.loc10: init %empty_tuple.type = call %bound_method.loc10(%addr.loc10_3) // CHECK:STDOUT: %facet_value.loc9: %type_where = facet_value constants.%C, () [concrete = constants.%facet_value] -// CHECK:STDOUT: %.loc9_27.5: %type_where = converted constants.%C, %facet_value.loc9 [concrete = constants.%facet_value] -// CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.bound.loc9: = bound_method %.loc9_27.3, constants.%AggregateT.as_type.as.Destroy.impl.Op.6b9 +// CHECK:STDOUT: %.loc9_3.2: %type_where = converted constants.%C, %facet_value.loc9 [concrete = constants.%facet_value] +// CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.bound.loc9: = bound_method %c2.var, constants.%AggregateT.as_type.as.Destroy.impl.Op.6b9 // CHECK:STDOUT: -// CHECK:STDOUT: %bound_method.loc9: = bound_method %.loc9_27.3, %AggregateT.as_type.as.Destroy.impl.Op.specific_fn.1 -// CHECK:STDOUT: %addr.loc9_27.2: %ptr.d9e = addr_of %.loc9_27.3 -// CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.call.loc9: init %empty_tuple.type = call %bound_method.loc9(%addr.loc9_27.2) +// CHECK:STDOUT: %bound_method.loc9: = bound_method %c2.var, %AggregateT.as_type.as.Destroy.impl.Op.specific_fn.2 +// CHECK:STDOUT: %addr.loc9_3: %ptr.d9e = addr_of %c2.var +// CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.call.loc9: init %empty_tuple.type = call %bound_method.loc9(%addr.loc9_3) // CHECK:STDOUT: %facet_value.loc8: %type_where = facet_value constants.%C, () [concrete = constants.%facet_value] -// CHECK:STDOUT: %.loc8_27.5: %type_where = converted constants.%C, %facet_value.loc8 [concrete = constants.%facet_value] -// CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.bound.loc8: = bound_method %.loc8_27.3, constants.%AggregateT.as_type.as.Destroy.impl.Op.6b9 +// CHECK:STDOUT: %.loc8_3.2: %type_where = converted constants.%C, %facet_value.loc8 [concrete = constants.%facet_value] +// CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.bound.loc8: = bound_method %c1.var, constants.%AggregateT.as_type.as.Destroy.impl.Op.6b9 // CHECK:STDOUT: -// CHECK:STDOUT: %bound_method.loc8: = bound_method %.loc8_27.3, %AggregateT.as_type.as.Destroy.impl.Op.specific_fn.2 -// CHECK:STDOUT: %addr.loc8_27.2: %ptr.d9e = addr_of %.loc8_27.3 -// CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.call.loc8: init %empty_tuple.type = call %bound_method.loc8(%addr.loc8_27.2) +// CHECK:STDOUT: %bound_method.loc8: = bound_method %c1.var, %AggregateT.as_type.as.Destroy.impl.Op.specific_fn.3 +// CHECK:STDOUT: %addr.loc8_3: %ptr.d9e = addr_of %c1.var +// CHECK:STDOUT: %AggregateT.as_type.as.Destroy.impl.Op.call.loc8: init %empty_tuple.type = call %bound_method.loc8(%addr.loc8_3) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: