From bc38deb16cd3e2f00010f7c0405f903c5a1e15e7 Mon Sep 17 00:00:00 2001 From: Christopher Di Bella Date: Fri, 20 Mar 2026 15:03:52 -0700 Subject: [PATCH] adds witness support for associated types (#6937) This commit creates an instance for any associated types in an interface with a custom witness table. This unlocks interfaces designed for C++ interop that rely on arbitrary return types. For example, `CppUnsafeDeref` becomes usable as of this commit. This commit may have also implemented support for non-type associated constants, but since we're lacking a practical test case, they're still marked as TODO for the time being. --------- Co-authored-by: Dana Jansens Co-authored-by: Richard Smith --- core/prelude/operators/deref.carbon | 2 +- toolchain/check/custom_witness.cpp | 33 ++++- toolchain/check/impl_lookup.cpp | 3 +- .../check/testdata/interop/cpp/deref.carbon | 137 ++++++++++++++++-- 4 files changed, 157 insertions(+), 18 deletions(-) diff --git a/core/prelude/operators/deref.carbon b/core/prelude/operators/deref.carbon index c9c6ecbba1ab..369304a4b469 100644 --- a/core/prelude/operators/deref.carbon +++ b/core/prelude/operators/deref.carbon @@ -7,5 +7,5 @@ package Core library "prelude/operators/deref"; // TODO: Align with https://docs.carbon-lang.dev/docs/design/values.html#dereferencing-customization. interface CppUnsafeDeref { let Result:! type; - fn Op[self: Self]() -> ref Result; + fn Op[ref self: Self]() -> ref Result; } diff --git a/toolchain/check/custom_witness.cpp b/toolchain/check/custom_witness.cpp index 54372ae5e87c..7ce09a69ce0c 100644 --- a/toolchain/check/custom_witness.cpp +++ b/toolchain/check/custom_witness.cpp @@ -15,6 +15,7 @@ #include "toolchain/check/name_lookup.h" #include "toolchain/check/type.h" #include "toolchain/check/type_completion.h" +#include "toolchain/sem_ir/associated_constant.h" #include "toolchain/sem_ir/builtin_function_kind.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/typed_insts.h" @@ -247,10 +248,29 @@ auto BuildCustomWitness(Context& context, SemIR::LocId loc_id, /*defer_thunk_definition=*/false)); break; } - case SemIR::AssociatedConstantDecl::Kind: { - context.TODO(loc_id, - "Associated constant in interface with synthesized impl"); - return SemIR::ErrorInst::InstId; + case CARBON_KIND(SemIR::AssociatedConstantDecl decl): { + if (decl.type_id == SemIR::ErrorInst::TypeId) { + return SemIR::ErrorInst::InstId; + } + + // TODO: remove once we have a test-case for all associated constants. + // Special-case the ones we want to support in this if-statement, until + // we're able to account for everything. + if (decl.type_id != SemIR::TypeType::TypeId) { + context.TODO(loc_id, + "Associated constant of type other than `TypeType` in " + "synthesized impl"); + return SemIR::ErrorInst::InstId; + } + + auto type_id = context.insts().Get(value_id).type_id(); + CARBON_CHECK(type_id == SemIR::TypeType::TypeId || + type_id == SemIR::ErrorInst::TypeId); + auto impl_witness_associated_constant = + AddInst( + context, loc_id, {.type_id = type_id, .inst_id = value_id}); + entries.push_back(impl_witness_associated_constant); + break; } default: CARBON_CHECK(decl_id == SemIR::ErrorInst::InstId, @@ -263,7 +283,10 @@ auto BuildCustomWitness(Context& context, SemIR::LocId loc_id, // then a second after all associated functions, rather than building one in // each `StructValue`. Right now the code is written assuming at most one // function, though this CHECK can be removed as a temporary workaround. - CARBON_CHECK(entries.size() <= 1, + auto associated_functions = llvm::count_if(entries, [&](SemIR::InstId id) { + return context.insts().Get(id).kind() == SemIR::InstKind::FunctionDecl; + }); + CARBON_CHECK(associated_functions <= 1, "TODO: Support multiple associated functions"); return MakeCustomWitnessConstantInst(context, loc_id, diff --git a/toolchain/check/impl_lookup.cpp b/toolchain/check/impl_lookup.cpp index cb2df0c7146c..d821ef5c0894 100644 --- a/toolchain/check/impl_lookup.cpp +++ b/toolchain/check/impl_lookup.cpp @@ -380,7 +380,8 @@ static auto LookupImplWitnessInSelfFacetValue( self_facet_value_inst_id)) { auto witness_id = context.inst_blocks().Get(facet_value->witnesses_block_id)[index]; - if (context.insts().Is(witness_id)) { + if (context.insts().IsOneOf( + witness_id)) { return EvalImplLookupResult::MakeFinal(witness_id); } } diff --git a/toolchain/check/testdata/interop/cpp/deref.carbon b/toolchain/check/testdata/interop/cpp/deref.carbon index 47da0ef6e36a..c56dac7b5376 100644 --- a/toolchain/check/testdata/interop/cpp/deref.carbon +++ b/toolchain/check/testdata/interop/cpp/deref.carbon @@ -4,7 +4,7 @@ // // INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon // -// EXTRA-ARGS: --target=aarch64-unknown-linux --clang-arg=--std=c++20 +// EXTRA-ARGS: --clang-arg=-std=c++20 // // AUTOUPDATE // TIP: To test this file alone, run: @@ -12,7 +12,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/deref.carbon -// --- fail_todo_const_deref.carbon +// --- const_deref.carbon library "[[@TEST_NAME]]"; @@ -24,14 +24,12 @@ public: '''; fn TestDerefPass(var int_ptr: Cpp.ConstDeref) -> ref i32 { - // CHECK:STDERR: fail_todo_const_deref.carbon:[[@LINE+4]]:10: error: semantics TODO: `Associated constant in interface with synthesized impl` [SemanticsTodo] - // CHECK:STDERR: return int_ptr.(Core.CppUnsafeDeref.Op)(); - // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: + //@dump-sem-ir-begin return int_ptr.(Core.CppUnsafeDeref.Op)(); + //@dump-sem-ir-end } -// --- fail_todo_mutable_deref.carbon +// --- mutable_deref.carbon library "[[@TEST_NAME]]"; @@ -43,11 +41,9 @@ public: '''; fn TestDerefPass(var int_ptr: Cpp.MutableDeref) -> ref i32 { - // CHECK:STDERR: fail_todo_mutable_deref.carbon:[[@LINE+4]]:10: error: semantics TODO: `Associated constant in interface with synthesized impl` [SemanticsTodo] - // CHECK:STDERR: return int_ptr.(Core.CppUnsafeDeref.Op)(); - // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: + //@dump-sem-ir-begin return int_ptr.(Core.CppUnsafeDeref.Op)(); + //@dump-sem-ir-end } // --- fail_todo_multi_deref.carbon @@ -117,3 +113,122 @@ fn TestDerefFail(not_ptr: Cpp.NotAPtr) { // CHECK:STDERR: not_ptr.(Core.CppUnsafeDeref.Op)(); } + +// CHECK:STDOUT: --- const_deref.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %ConstDeref: type = class_type @ConstDeref [concrete] +// CHECK:STDOUT: %pattern_type.3fb: type = pattern_type %ConstDeref [concrete] +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %Int.type: type = generic_class_type @Int [concrete] +// CHECK:STDOUT: %Int.generic: %Int.type = struct_value () [concrete] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] +// CHECK:STDOUT: %CppUnsafeDeref.type: type = facet_type <@CppUnsafeDeref> [concrete] +// CHECK:STDOUT: %Self: %CppUnsafeDeref.type = symbolic_binding Self, 0 [symbolic] +// CHECK:STDOUT: %CppUnsafeDeref.WithSelf.Op.type.792: type = fn_type @CppUnsafeDeref.WithSelf.Op, @CppUnsafeDeref.WithSelf(%Self) [symbolic] +// CHECK:STDOUT: %CppUnsafeDeref.WithSelf.Op.a80: %CppUnsafeDeref.WithSelf.Op.type.792 = struct_value () [symbolic] +// CHECK:STDOUT: %CppUnsafeDeref.assoc_type: type = assoc_entity_type @CppUnsafeDeref [concrete] +// CHECK:STDOUT: %assoc1: %CppUnsafeDeref.assoc_type = assoc_entity element1, imports.%Core.import_ref.ad7 [concrete] +// CHECK:STDOUT: %ConstDeref.cpp_operator.type: type = fn_type @ConstDeref.cpp_operator [concrete] +// CHECK:STDOUT: %ConstDeref.cpp_operator: %ConstDeref.cpp_operator.type = struct_value () [concrete] +// CHECK:STDOUT: %operator_Star__carbon_thunk.type: type = fn_type @operator_Star__carbon_thunk [concrete] +// CHECK:STDOUT: %operator_Star__carbon_thunk: %operator_Star__carbon_thunk.type = struct_value () [concrete] +// CHECK:STDOUT: %ConstDeref.Op.type: type = fn_type @ConstDeref.Op [concrete] +// CHECK:STDOUT: %ConstDeref.Op: %ConstDeref.Op.type = struct_value () [concrete] +// CHECK:STDOUT: %custom_witness.5a5: = custom_witness (%i32, %ConstDeref.Op), @CppUnsafeDeref [concrete] +// CHECK:STDOUT: %CppUnsafeDeref.facet.5c0: %CppUnsafeDeref.type = facet_value %ConstDeref, (%custom_witness.5a5) [concrete] +// CHECK:STDOUT: %CppUnsafeDeref.WithSelf.Op.type.d8f: type = fn_type @CppUnsafeDeref.WithSelf.Op, @CppUnsafeDeref.WithSelf(%CppUnsafeDeref.facet.5c0) [concrete] +// CHECK:STDOUT: %.d81: type = fn_type_with_self_type %CppUnsafeDeref.WithSelf.Op.type.d8f, %CppUnsafeDeref.facet.5c0 [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [concrete] { +// CHECK:STDOUT: .Int = %Core.Int +// CHECK:STDOUT: .CppUnsafeDeref = %Core.CppUnsafeDeref +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.Int: %Int.type = import_ref Core//prelude/types/int, Int, loaded [concrete = constants.%Int.generic] +// CHECK:STDOUT: %Core.CppUnsafeDeref: type = import_ref Core//prelude/operators/deref, CppUnsafeDeref, loaded [concrete = constants.%CppUnsafeDeref.type] +// CHECK:STDOUT: %Core.import_ref.83d: %CppUnsafeDeref.assoc_type = import_ref Core//prelude/operators/deref, loc{{\d+_\d+}}, loaded [concrete = constants.%assoc1] +// CHECK:STDOUT: %Core.import_ref.ad7: @CppUnsafeDeref.WithSelf.%CppUnsafeDeref.WithSelf.Op.type (%CppUnsafeDeref.WithSelf.Op.type.792) = import_ref Core//prelude/operators/deref, loc{{\d+_\d+}}, loaded [symbolic = @CppUnsafeDeref.WithSelf.%CppUnsafeDeref.WithSelf.Op (constants.%CppUnsafeDeref.WithSelf.Op.a80)] +// CHECK:STDOUT: %ConstDeref.cpp_operator.decl: %ConstDeref.cpp_operator.type = fn_decl @ConstDeref.cpp_operator [concrete = constants.%ConstDeref.cpp_operator] { +// CHECK:STDOUT: %self.patt: %pattern_type.3fb = value_binding_pattern self [concrete] +// CHECK:STDOUT: %self.param_patt: %pattern_type.3fb = value_param_pattern %self.patt [concrete] +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: %self.param: %ConstDeref = value_param call_param0 +// CHECK:STDOUT: %self: %ConstDeref = value_binding self, %self.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %operator_Star__carbon_thunk.decl: %operator_Star__carbon_thunk.type = fn_decl @operator_Star__carbon_thunk [concrete = constants.%operator_Star__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @TestDerefPass(%int_ptr.param: ref %ConstDeref) -> ref %i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %int_ptr.ref: ref %ConstDeref = name_ref int_ptr, %int_ptr +// CHECK:STDOUT: %Core.ref: = name_ref Core, imports.%Core [concrete = imports.%Core] +// CHECK:STDOUT: %CppUnsafeDeref.ref: type = name_ref CppUnsafeDeref, imports.%Core.CppUnsafeDeref [concrete = constants.%CppUnsafeDeref.type] +// CHECK:STDOUT: %Op.ref.loc13_38: %CppUnsafeDeref.assoc_type = name_ref Op, imports.%Core.import_ref.83d [concrete = constants.%assoc1] +// CHECK:STDOUT: %impl_witness_assoc_constant: type = impl_witness_assoc_constant @ConstDeref.cpp_operator.%i32.2 [concrete = constants.%i32] +// CHECK:STDOUT: +// CHECK:STDOUT: %impl.elem1: %.d81 = impl_witness_access constants.%custom_witness.5a5, element1 [concrete = constants.%ConstDeref.Op] +// CHECK:STDOUT: %bound_method: = bound_method %int_ptr.ref, %impl.elem1 +// CHECK:STDOUT: %Op.ref.loc13_43: %ConstDeref.cpp_operator.type = name_ref Op, imports.%ConstDeref.cpp_operator.decl [concrete = constants.%ConstDeref.cpp_operator] +// CHECK:STDOUT: %ConstDeref.cpp_operator.bound: = bound_method %int_ptr.ref, %Op.ref.loc13_43 +// CHECK:STDOUT: %.loc13: %ConstDeref = acquire_value %int_ptr.ref +// CHECK:STDOUT: %operator_Star__carbon_thunk.call: ref %i32 = call imports.%operator_Star__carbon_thunk.decl(%.loc13) +// CHECK:STDOUT: return %operator_Star__carbon_thunk.call +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- mutable_deref.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %MutableDeref: type = class_type @MutableDeref [concrete] +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %Int.type: type = generic_class_type @Int [concrete] +// CHECK:STDOUT: %Int.generic: %Int.type = struct_value () [concrete] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] +// CHECK:STDOUT: %CppUnsafeDeref.type: type = facet_type <@CppUnsafeDeref> [concrete] +// CHECK:STDOUT: %Self: %CppUnsafeDeref.type = symbolic_binding Self, 0 [symbolic] +// CHECK:STDOUT: %CppUnsafeDeref.WithSelf.Op.type.792: type = fn_type @CppUnsafeDeref.WithSelf.Op, @CppUnsafeDeref.WithSelf(%Self) [symbolic] +// CHECK:STDOUT: %CppUnsafeDeref.WithSelf.Op.a80: %CppUnsafeDeref.WithSelf.Op.type.792 = struct_value () [symbolic] +// CHECK:STDOUT: %CppUnsafeDeref.assoc_type: type = assoc_entity_type @CppUnsafeDeref [concrete] +// CHECK:STDOUT: %assoc1: %CppUnsafeDeref.assoc_type = assoc_entity element1, imports.%Core.import_ref.ad7 [concrete] +// CHECK:STDOUT: %MutableDeref.cpp_operator.type: type = fn_type @MutableDeref.cpp_operator [concrete] +// CHECK:STDOUT: %MutableDeref.cpp_operator: %MutableDeref.cpp_operator.type = struct_value () [concrete] +// CHECK:STDOUT: %custom_witness.e20: = custom_witness (%i32, %MutableDeref.cpp_operator), @CppUnsafeDeref [concrete] +// CHECK:STDOUT: %CppUnsafeDeref.facet.3f6: %CppUnsafeDeref.type = facet_value %MutableDeref, (%custom_witness.e20) [concrete] +// CHECK:STDOUT: %CppUnsafeDeref.WithSelf.Op.type.cfd: type = fn_type @CppUnsafeDeref.WithSelf.Op, @CppUnsafeDeref.WithSelf(%CppUnsafeDeref.facet.3f6) [concrete] +// CHECK:STDOUT: %.c6d: type = fn_type_with_self_type %CppUnsafeDeref.WithSelf.Op.type.cfd, %CppUnsafeDeref.facet.3f6 [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [concrete] { +// CHECK:STDOUT: .Int = %Core.Int +// CHECK:STDOUT: .CppUnsafeDeref = %Core.CppUnsafeDeref +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.Int: %Int.type = import_ref Core//prelude/types/int, Int, loaded [concrete = constants.%Int.generic] +// CHECK:STDOUT: %Core.CppUnsafeDeref: type = import_ref Core//prelude/operators/deref, CppUnsafeDeref, loaded [concrete = constants.%CppUnsafeDeref.type] +// CHECK:STDOUT: %Core.import_ref.83d: %CppUnsafeDeref.assoc_type = import_ref Core//prelude/operators/deref, loc{{\d+_\d+}}, loaded [concrete = constants.%assoc1] +// CHECK:STDOUT: %Core.import_ref.ad7: @CppUnsafeDeref.WithSelf.%CppUnsafeDeref.WithSelf.Op.type (%CppUnsafeDeref.WithSelf.Op.type.792) = import_ref Core//prelude/operators/deref, loc{{\d+_\d+}}, loaded [symbolic = @CppUnsafeDeref.WithSelf.%CppUnsafeDeref.WithSelf.Op (constants.%CppUnsafeDeref.WithSelf.Op.a80)] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @TestDerefPass(%int_ptr.param: ref %MutableDeref) -> ref %i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %int_ptr.ref: ref %MutableDeref = name_ref int_ptr, %int_ptr +// CHECK:STDOUT: %Core.ref: = name_ref Core, imports.%Core [concrete = imports.%Core] +// CHECK:STDOUT: %CppUnsafeDeref.ref: type = name_ref CppUnsafeDeref, imports.%Core.CppUnsafeDeref [concrete = constants.%CppUnsafeDeref.type] +// CHECK:STDOUT: %Op.ref: %CppUnsafeDeref.assoc_type = name_ref Op, imports.%Core.import_ref.83d [concrete = constants.%assoc1] +// CHECK:STDOUT: %impl_witness_assoc_constant: type = impl_witness_assoc_constant @MutableDeref.cpp_operator.%i32.2 [concrete = constants.%i32] +// CHECK:STDOUT: %impl.elem1: %.c6d = impl_witness_access constants.%custom_witness.e20, element1 [concrete = constants.%MutableDeref.cpp_operator] +// CHECK:STDOUT: %bound_method: = bound_method %int_ptr.ref, %impl.elem1 +// CHECK:STDOUT: %MutableDeref.cpp_operator.call: ref %i32 = call %bound_method(%int_ptr.ref) +// CHECK:STDOUT: return %MutableDeref.cpp_operator.call +// CHECK:STDOUT: } +// CHECK:STDOUT: