Add support for using C++ user-defined conversions via interop (#6646)

When performing an implicit conversion to or from a C++ class type, look
for a C++ implicit conversion, and if that conversion involves a
function call (to a constructor or conversion function), call that
function to perform the conversion.

Note that this is just a first pass at supporting implicit conversions.
There are a lot of other things that can happen in a C++ implicit
conversion, such as aggregate initialization or `std::initializer_list`
initialization that aren't handled here. In addition, we intentionally
leave all standard conversions to Carbon to perform, so that we will
reject conversions such as `i32 -> unsigned` that C++ would select but
Carbon considers to be invalid.

Also support `as` conversions. These are treated analogously, but
perform direct-initialization instead of copy-initialization, so they
also find `explicit` constructors and conversion functions.

In order to give good diagnostics, also track the original C++ source
location for imported C++ functions on the imported version of the
function.

Assisted-by: Gemini 3 Pro via Antigravity
This commit is contained in:
Richard Smith
2026-01-25 04:51:25 +00:00
committed by GitHub
parent 9836ba6e9c
commit 093d5072db
17 changed files with 1947 additions and 363 deletions
+14 -16
View File
@@ -183,10 +183,9 @@ import Cpp library "definition_single_data_member_value_param_type.h";
fn F() {
//@dump-sem-ir-begin
// CHECK:STDERR: fail_todo_import_definition_single_data_member_value_param_type.carbon:[[@LINE+7]]:11: error: cannot convert expression of type `{}` to `Cpp.S` with `as` [ConversionFailure]
// CHECK:STDERR: Cpp.foo({} as Cpp.S);
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR: fail_todo_import_definition_single_data_member_value_param_type.carbon:[[@LINE+4]]:11: note: type `{}` does not implement interface `Core.As(Cpp.S)` [MissingImplInMemberAccessNote]
// CHECK:STDERR: fail_todo_import_definition_single_data_member_value_param_type.carbon:[[@LINE+6]]:11: error: semantics TODO: `Unsupported initialization sequence:
// CHECK:STDERR: Normal sequence: list aggregate initialization [struct S]
// CHECK:STDERR: ` [SemanticsTodo]
// CHECK:STDERR: Cpp.foo({} as Cpp.S);
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
@@ -218,10 +217,9 @@ import Cpp library "definition_multiple_data_members_value_param_type.h";
fn F() {
//@dump-sem-ir-begin
// CHECK:STDERR: fail_todo_import_definition_multiple_data_members_value_param_type.carbon:[[@LINE+7]]:11: error: cannot convert expression of type `{}` to `Cpp.S` with `as` [ConversionFailure]
// CHECK:STDERR: Cpp.foo({} as Cpp.S);
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR: fail_todo_import_definition_multiple_data_members_value_param_type.carbon:[[@LINE+4]]:11: note: type `{}` does not implement interface `Core.As(Cpp.S)` [MissingImplInMemberAccessNote]
// CHECK:STDERR: fail_todo_import_definition_multiple_data_members_value_param_type.carbon:[[@LINE+6]]:11: error: semantics TODO: `Unsupported initialization sequence:
// CHECK:STDERR: Normal sequence: list aggregate initialization [struct S]
// CHECK:STDERR: ` [SemanticsTodo]
// CHECK:STDERR: Cpp.foo({} as Cpp.S);
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
@@ -651,12 +649,12 @@ fn F() {
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %Cpp.ref.loc15_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %Cpp.ref.loc14_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %foo.ref: %foo.cpp_overload_set.type = name_ref foo, imports.%foo.cpp_overload_set.value [concrete = constants.%foo.cpp_overload_set.value]
// CHECK:STDOUT: %.loc15_12: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
// CHECK:STDOUT: %Cpp.ref.loc15_17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %.loc14_12: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
// CHECK:STDOUT: %Cpp.ref.loc14_17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %S.ref: type = name_ref S, imports.%S.decl [concrete = constants.%S]
// CHECK:STDOUT: %.loc15_14: %S = converted %.loc15_12, <error> [concrete = <error>]
// CHECK:STDOUT: %.loc14_14: %S = converted %.loc14_12, <error> [concrete = <error>]
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -682,12 +680,12 @@ fn F() {
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %Cpp.ref.loc15_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %Cpp.ref.loc14_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %foo.ref: %foo.cpp_overload_set.type = name_ref foo, imports.%foo.cpp_overload_set.value [concrete = constants.%foo.cpp_overload_set.value]
// CHECK:STDOUT: %.loc15_12: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
// CHECK:STDOUT: %Cpp.ref.loc15_17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %.loc14_12: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
// CHECK:STDOUT: %Cpp.ref.loc14_17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %S.ref: type = name_ref S, imports.%S.decl [concrete = constants.%S]
// CHECK:STDOUT: %.loc15_14: %S = converted %.loc15_12, <error> [concrete = <error>]
// CHECK:STDOUT: %.loc14_14: %S = converted %.loc14_12, <error> [concrete = <error>]
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT: