Map Carbon value expressions to const-qualified C++ prvalues. (#6880)

In C++ overload resolution, when mapping a Carbon value expression into
a C++ argument, produce a const-qualified argument where possible. This
has two effects:

* Overload resolution does not consider non-const-qualified member
functions to be viable for a prvalue self any more. This is desirable
since such functions are not actually callable with a prvalue self, and
permits overload resolution to pick a const-qualified overload instead.

* Overload resolution does not allow a Carbon value expression to be
passed to a C++ `T&&` parameter any more. This is desirable since it's
not correct to move from a value expression. Previously we allowed this
and moved from the value!
This commit is contained in:
Richard Smith
2026-03-11 03:42:19 +00:00
committed by GitHub
parent ba6257891e
commit 39fd358059
5 changed files with 164 additions and 166 deletions
@@ -283,7 +283,7 @@ void operator+(C, short);
''';
fn Test(c: Cpp.C) {
// CHECK:STDERR: fail_ambiguous.carbon:[[@LINE+10]]:5: error: use of overloaded operator '+' is ambiguous (with operand types 'C' and 'int') [CppInteropParseError]
// CHECK:STDERR: fail_ambiguous.carbon:[[@LINE+10]]:5: error: use of overloaded operator '+' is ambiguous (with operand types 'const C' and 'int') [CppInteropParseError]
// CHECK:STDERR: 21 | c + 1;
// CHECK:STDERR: | ^
// CHECK:STDERR: fail_ambiguous.carbon:[[@LINE-8]]:6: note: candidate function [CppInteropParseNote]
@@ -48,7 +48,7 @@ fn F() {
// CHECK:STDERR: 18 | Cpp.TakesLValue(s);
// CHECK:STDERR: | ^
// CHECK:STDERR: fail_param_lvalue_ref.carbon:[[@LINE-9]]:10: in file included here [InCppInclude]
// CHECK:STDERR: ./param_lvalue_ref.h:5:6: note: candidate function not viable: expects an lvalue for 1st argument [CppInteropParseNote]
// CHECK:STDERR: ./param_lvalue_ref.h:5:6: note: candidate function not viable: 1st argument ('const S') would lose const qualifier [CppInteropParseNote]
// CHECK:STDERR: 5 | auto TakesLValue(S&) -> void;
// CHECK:STDERR: | ^ ~~
// CHECK:STDERR:
@@ -111,21 +111,23 @@ fn F() {
//@dump-sem-ir-end
}
// --- todo_fail_param_value_arg_for_rvalue_ref.carbon
// --- fail_param_value_arg_for_rvalue_ref.carbon
library "[[@TEST_NAME]]";
import Cpp library "param_rvalue_ref.h";
fn F() {
//@dump-sem-ir-begin
// TODO: We should probably reject binding an rvalue reference to a value
// expression. If we don't reject, we should instead force a copy to be made,
// at least if the type has a pointer value representation, so that moving
// from the reference doesn't alter tne original value.
let s: Cpp.S = {};
// CHECK:STDERR: fail_param_value_arg_for_rvalue_ref.carbon:[[@LINE+8]]:20: error: no matching function for call to 'TakesRValue' [CppInteropParseError]
// CHECK:STDERR: 16 | Cpp.TakesRValue(s);
// CHECK:STDERR: | ^
// CHECK:STDERR: fail_param_value_arg_for_rvalue_ref.carbon:[[@LINE-7]]:10: in file included here [InCppInclude]
// CHECK:STDERR: ./param_rvalue_ref.h:5:6: note: candidate function not viable: 1st argument ('const S') would lose const qualifier [CppInteropParseNote]
// CHECK:STDERR: 5 | auto TakesRValue(S&&) -> void;
// CHECK:STDERR: | ^ ~~~
// CHECK:STDERR:
Cpp.TakesRValue(s);
//@dump-sem-ir-end
}
// --- fail_param_rvalue_ref.carbon
@@ -526,66 +528,6 @@ fn F() {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- todo_fail_param_value_arg_for_rvalue_ref.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
// CHECK:STDOUT: %S: type = class_type @S [concrete]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
// CHECK:STDOUT: %pattern_type.7da: type = pattern_type %S [concrete]
// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete]
// CHECK:STDOUT: %S.val: %S = struct_value () [concrete]
// CHECK:STDOUT: %TakesRValue.cpp_overload_set.type: type = cpp_overload_set_type @TakesRValue.cpp_overload_set [concrete]
// CHECK:STDOUT: %TakesRValue.cpp_overload_set.value: %TakesRValue.cpp_overload_set.type = cpp_overload_set_value @TakesRValue.cpp_overload_set [concrete]
// CHECK:STDOUT: %ptr.5c7: type = ptr_type %S [concrete]
// CHECK:STDOUT: %TakesRValue__carbon_thunk.type: type = fn_type @TakesRValue__carbon_thunk [concrete]
// CHECK:STDOUT: %TakesRValue__carbon_thunk: %TakesRValue__carbon_thunk.type = struct_value () [concrete]
// CHECK:STDOUT: %S.cpp_destructor.type: type = fn_type @S.cpp_destructor [concrete]
// CHECK:STDOUT: %S.cpp_destructor: %S.cpp_destructor.type = struct_value () [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
// CHECK:STDOUT: .S = %S.decl
// CHECK:STDOUT: .TakesRValue = %TakesRValue.cpp_overload_set.value
// CHECK:STDOUT: import Cpp//...
// CHECK:STDOUT: }
// CHECK:STDOUT: %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
// CHECK:STDOUT: %TakesRValue.cpp_overload_set.value: %TakesRValue.cpp_overload_set.type = cpp_overload_set_value @TakesRValue.cpp_overload_set [concrete = constants.%TakesRValue.cpp_overload_set.value]
// CHECK:STDOUT: %TakesRValue__carbon_thunk.decl: %TakesRValue__carbon_thunk.type = fn_decl @TakesRValue__carbon_thunk [concrete = constants.%TakesRValue__carbon_thunk] {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: } {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: name_binding_decl {
// CHECK:STDOUT: %s.patt: %pattern_type.7da = value_binding_pattern s [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT: %.loc12_19.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
// CHECK:STDOUT: %.loc12_13: type = splice_block %S.ref [concrete = constants.%S] {
// CHECK:STDOUT: %Cpp.ref.loc12: <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: }
// CHECK:STDOUT: %.loc12_19.2: ref %S = temporary_storage
// CHECK:STDOUT: %.loc12_19.3: init %S to %.loc12_19.2 = class_init () [concrete = constants.%S.val]
// CHECK:STDOUT: %.loc12_19.4: ref %S = temporary %.loc12_19.2, %.loc12_19.3
// CHECK:STDOUT: %.loc12_19.5: ref %S = converted %.loc12_19.1, %.loc12_19.4
// CHECK:STDOUT: %.loc12_19.6: %S = acquire_value %.loc12_19.5
// CHECK:STDOUT: %s: %S = value_binding s, %.loc12_19.6
// CHECK:STDOUT: %Cpp.ref.loc13: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %TakesRValue.ref: %TakesRValue.cpp_overload_set.type = name_ref TakesRValue, imports.%TakesRValue.cpp_overload_set.value [concrete = constants.%TakesRValue.cpp_overload_set.value]
// CHECK:STDOUT: %s.ref: %S = name_ref s, %s
// CHECK:STDOUT: %.loc13: ref %S = value_as_ref %s.ref
// CHECK:STDOUT: %addr: %ptr.5c7 = addr_of %.loc13
// CHECK:STDOUT: %TakesRValue__carbon_thunk.call: init %empty_tuple.type = call imports.%TakesRValue__carbon_thunk.decl(%addr)
// CHECK:STDOUT: %S.cpp_destructor.bound: <bound method> = bound_method %.loc12_19.4, constants.%S.cpp_destructor
// CHECK:STDOUT: %S.cpp_destructor.call: init %empty_tuple.type = call %S.cpp_destructor.bound(%.loc12_19.4)
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- fail_param_rvalue_ref.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {