Fix crash attempting constant evaluation of a constexpr constructor. (#7514)

We can't use a `CallExpr` to call a constructor; use a
`CXXConstructExpr` instead. While this fixes the crash and gets us past
the initial constant evaluation, we still can't map the constant value
back into Carbon, so this doesn't actually make constexpr constructors
work yet. But it does stop Clang from crashing.

Fixes #7498.
This commit is contained in:
Richard Smith
2026-07-16 15:17:08 +00:00
committed by GitHub
parent 703529fc55
commit 90f6654b48
2 changed files with 176 additions and 21 deletions
+31 -21
View File
@@ -226,22 +226,7 @@ auto EvalCppCall(Context& context, SemIR::LocId loc_id,
const auto& args = context.inst_blocks().Get(args_id);
auto* function_decl = cast<clang::FunctionDecl>(clang_decl.decl());
// Create expr for the function declaration.
auto* decl_ref_expr = clang::DeclRefExpr::Create(
context.ast_context(), /*QualifierLoc=*/clang::NestedNameSpecifierLoc(),
/*TemplateKWLoc=*/clang::SourceLocation(), function_decl,
/*RefersToEnclosingVariableOrCapture=*/false,
/*NameLoc=*/GetCppLocation(context, loc_id), function_decl->getType(),
clang::VK_LValue);
// Cast to a function pointer type.
auto function_ptr_type =
context.ast_context().getPointerType(function_decl->getType());
auto* implicit_cast_expr = clang::ImplicitCastExpr::Create(
context.ast_context(), function_ptr_type,
clang::CK_FunctionToPointerDecay, decl_ref_expr, nullptr,
clang::VK_PRValue, clang::FPOptionsOverride());
auto loc = GetCppLocation(context, loc_id);
// Convert the arguments to exprs.
clang::SmallVector<clang::Expr*> arg_exprs;
@@ -256,11 +241,36 @@ auto EvalCppCall(Context& context, SemIR::LocId loc_id,
}
// Create an expr to call the function.
auto* call_expr = clang::CallExpr::Create(
context.ast_context(), implicit_cast_expr, arg_exprs,
function_decl->getCallResultType(), clang::VK_PRValue,
/*RParenLoc=*/GetCppLocation(context, loc_id),
clang::FPOptionsOverride());
clang::Expr* call_expr;
if (auto* ctor = dyn_cast<clang::CXXConstructorDecl>(function_decl)) {
// Constructor: generate a direct constructor call expression.
call_expr = clang::CXXConstructExpr::Create(
context.ast_context(), function_decl->getCallResultType(), loc, ctor,
/*Elidable=*/false, arg_exprs, /*HadMultipleCandidates=*/false,
/*ListInitialization=*/false, /*StdInitListInitialization=*/false,
/*ZeroInitialization=*/false, clang::CXXConstructionKind::Complete,
clang::SourceRange(loc));
} else {
// Create expr for the function declaration.
auto* decl_ref_expr = clang::DeclRefExpr::Create(
context.ast_context(), /*QualifierLoc=*/clang::NestedNameSpecifierLoc(),
/*TemplateKWLoc=*/clang::SourceLocation(), function_decl,
/*RefersToEnclosingVariableOrCapture=*/false,
/*NameLoc=*/loc, function_decl->getType(), clang::VK_LValue);
// Cast to a function pointer type.
// TODO: For a non-static member function, we should create a member access.
auto function_ptr_type =
context.ast_context().getPointerType(function_decl->getType());
auto* implicit_cast_expr = clang::ImplicitCastExpr::Create(
context.ast_context(), function_ptr_type,
clang::CK_FunctionToPointerDecay, decl_ref_expr, nullptr,
clang::VK_PRValue, clang::FPOptionsOverride());
call_expr = clang::CallExpr::Create(
context.ast_context(), implicit_cast_expr, arg_exprs,
function_decl->getCallResultType(), clang::VK_PRValue,
/*RParenLoc=*/loc, clang::FPOptionsOverride());
}
// Evaluate the expr as a constant and map that to Carbon constant.
clang::SmallVector<clang::PartialDiagnosticAt> notes;
@@ -127,3 +127,148 @@ consteval int f(const int &r) { return r; }
// CHECK:STDERR: ^
// CHECK:STDERR:
var b: i32 = Cpp.f(a);
// --- fail_todo_constructor.carbon
library "[[@TEST_NAME]]";
import Cpp;
inline Cpp '''
struct C {
constexpr C(int a) : a(a) {}
int a;
};
''';
// TODO: This should probably be allowed.
eval fn F() -> i32 {
//@dump-sem-ir-begin
// CHECK:STDERR: fail_todo_constructor.carbon:[[@LINE+3]]:18: error: expression is runtime; expected constant [EvalRequiresConstantValue]
// CHECK:STDERR: let c: Cpp.C = 3;
// CHECK:STDERR: ^
let c: Cpp.C = 3;
return c.a;
//@dump-sem-ir-end
}
// CHECK:STDERR: fail_todo_constructor.carbon:[[@LINE+4]]:19: note: in call to F here [InCallToEvalFn]
// CHECK:STDERR: let a: array(i32, F()) = (1, 2, 3);
// CHECK:STDERR: ^~~
// CHECK:STDERR:
let a: array(i32, F()) = (1, 2, 3);
// CHECK:STDOUT: --- fail_todo_constructor.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete]
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
// CHECK:STDOUT: %N: Core.IntLiteral = symbolic_binding N, 0 [symbolic]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
// CHECK:STDOUT: %C: type = class_type @C [concrete]
// CHECK:STDOUT: %C.elem: type = unbound_element_type %C, %i32 [concrete]
// CHECK:STDOUT: %pattern_type.bbc: type = pattern_type %C [concrete]
// CHECK:STDOUT: %c.patt: %pattern_type.bbc = value_binding_pattern c [concrete]
// CHECK:STDOUT: %int_3.1ba: Core.IntLiteral = int_value 3 [concrete]
// CHECK:STDOUT: %ptr.0a2: 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: %ImplicitAs.type.914: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete]
// CHECK:STDOUT: %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.e74: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl.0e9(%To) [symbolic]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.845: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.e74 = struct_value () [symbolic]
// CHECK:STDOUT: %Copy.type: type = facet_type <@Copy> [concrete]
// CHECK:STDOUT: %ImplicitAs.impl_witness.a23: <witness> = impl_witness imports.%ImplicitAs.impl_witness_table.b3c, @Core.IntLiteral.as.ImplicitAs.impl.0e9(%int_32) [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.2ba: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl.0e9(%int_32) [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.e39: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.2ba = struct_value () [concrete]
// CHECK:STDOUT: %ImplicitAs.facet.9f1: %ImplicitAs.type.914 = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.a23) [concrete]
// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.type.740: type = fn_type @ImplicitAs.WithSelf.Convert, @ImplicitAs.WithSelf(%i32, %ImplicitAs.facet.9f1) [concrete]
// CHECK:STDOUT: %.1c5: type = fn_type_with_self_type %ImplicitAs.WithSelf.Convert.type.740, %ImplicitAs.facet.9f1 [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound: <bound method> = bound_method %int_3.1ba, %Core.IntLiteral.as.ImplicitAs.impl.Convert.e39 [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.e39, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete]
// CHECK:STDOUT: %bound_method: <bound method> = bound_method %int_3.1ba, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
// CHECK:STDOUT: %int_3.410: %i32 = int_value 3 [concrete]
// CHECK:STDOUT: %Int.as.Copy.impl.Op.type.ac8: type = fn_type @Int.as.Copy.impl.Op, @Int.as.Copy.impl(%N) [symbolic]
// CHECK:STDOUT: %Int.as.Copy.impl.Op.5e0: %Int.as.Copy.impl.Op.type.ac8 = struct_value () [symbolic]
// CHECK:STDOUT: %Copy.impl_witness.0e0: <witness> = impl_witness imports.%Copy.impl_witness_table.367, @Int.as.Copy.impl(%int_32) [concrete]
// CHECK:STDOUT: %Int.as.Copy.impl.Op.type.3f6: type = fn_type @Int.as.Copy.impl.Op, @Int.as.Copy.impl(%int_32) [concrete]
// CHECK:STDOUT: %Int.as.Copy.impl.Op.4f6: %Int.as.Copy.impl.Op.type.3f6 = struct_value () [concrete]
// CHECK:STDOUT: %Copy.facet: %Copy.type = facet_value %i32, (%Copy.impl_witness.0e0) [concrete]
// CHECK:STDOUT: %Copy.WithSelf.Op.type.d09: type = fn_type @Copy.WithSelf.Op, @Copy.WithSelf(%Copy.facet) [concrete]
// CHECK:STDOUT: %.7a6: type = fn_type_with_self_type %Copy.WithSelf.Op.type.d09, %Copy.facet [concrete]
// CHECK:STDOUT: %Int.as.Copy.impl.Op.specific_fn: <specific function> = specific_function %Int.as.Copy.impl.Op.4f6, @Int.as.Copy.impl.Op(%int_32) [concrete]
// CHECK:STDOUT: %C.cpp_destructor.type: type = fn_type @C.cpp_destructor [concrete]
// CHECK:STDOUT: %C.cpp_destructor: %C.cpp_destructor.type = struct_value () [concrete]
// CHECK:STDOUT: %C.Op.type: type = fn_type @C.Op [concrete]
// CHECK:STDOUT: %C.Op: %C.Op.type = struct_value () [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
// CHECK:STDOUT: .C = %C.decl
// CHECK:STDOUT: import Cpp//...
// CHECK:STDOUT: }
// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
// CHECK:STDOUT: %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: } {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import_ref.edf: @Core.IntLiteral.as.ImplicitAs.impl.0e9.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.e74) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.0e9.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.845)]
// CHECK:STDOUT: %ImplicitAs.impl_witness_table.b3c = impl_witness_table (%Core.import_ref.edf), @Core.IntLiteral.as.ImplicitAs.impl.0e9 [concrete]
// CHECK:STDOUT: %Core.import_ref.cd6: @Int.as.Copy.impl.%Int.as.Copy.impl.Op.type (%Int.as.Copy.impl.Op.type.ac8) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Int.as.Copy.impl.%Int.as.Copy.impl.Op (constants.%Int.as.Copy.impl.Op.5e0)]
// CHECK:STDOUT: %Copy.impl_witness_table.367 = impl_witness_table (%Core.import_ref.cd6), @Int.as.Copy.impl [concrete]
// CHECK:STDOUT: %C.cpp_destructor.decl: %C.cpp_destructor.type = fn_decl @C.cpp_destructor [concrete = constants.%C.cpp_destructor] {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: } {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() -> out %return.param: %i32 {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %int_3: Core.IntLiteral = int_value 3 [concrete = constants.%int_3.1ba]
// CHECK:STDOUT: %.loc19_18.1: ref %C = temporary_storage
// CHECK:STDOUT: %impl.elem0.loc19: %.1c5 = impl_witness_access constants.%ImplicitAs.impl_witness.a23, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.e39]
// CHECK:STDOUT: %bound_method.loc19_18.1: <bound method> = bound_method %int_3, %impl.elem0.loc19 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
// CHECK:STDOUT: %specific_fn.loc19: <specific function> = specific_function %impl.elem0.loc19, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
// CHECK:STDOUT: %bound_method.loc19_18.2: <bound method> = bound_method %int_3, %specific_fn.loc19 [concrete = constants.%bound_method]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc19_18.2(%int_3) [concrete = constants.%int_3.410]
// CHECK:STDOUT: %.loc19_18.2: %i32 = value_of_initializer %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_3.410]
// CHECK:STDOUT: %.loc19_18.3: %i32 = converted %int_3, %.loc19_18.2 [concrete = constants.%int_3.410]
// CHECK:STDOUT: %addr: %ptr.0a2 = addr_of %.loc19_18.1
// CHECK:STDOUT: %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%.loc19_18.3, %addr)
// CHECK:STDOUT: %.loc19_18.4: init %C to %.loc19_18.1 = mark_in_place_init %C__carbon_thunk.call
// CHECK:STDOUT: %.loc19_18.5: init %C = converted %int_3, %.loc19_18.4
// CHECK:STDOUT: %.loc19_18.6: ref %C = temporary %.loc19_18.1, %.loc19_18.5
// CHECK:STDOUT: %.loc19_18.7: %C = acquire_value %.loc19_18.6
// CHECK:STDOUT: %.loc19_13: type = splice_block %C.ref [concrete = constants.%C] {
// CHECK:STDOUT: %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
// CHECK:STDOUT: %C.ref: type = name_ref C, imports.%C.decl [concrete = constants.%C]
// CHECK:STDOUT: }
// CHECK:STDOUT: %c: %C = wrapper_binding c, %.loc19_18.7
// CHECK:STDOUT: name_binding_decl {
// CHECK:STDOUT: %c.patt: %pattern_type.bbc = value_binding_pattern c [concrete = constants.%c.patt]
// CHECK:STDOUT: }
// CHECK:STDOUT: %c.ref: %C = name_ref c, %c
// CHECK:STDOUT: %a.ref: %C.elem = name_ref a, @C.%.1 [concrete = @C.%.1]
// CHECK:STDOUT: %.loc20_11.1: ref %i32 = class_element_access %c.ref, element0
// CHECK:STDOUT: %.loc20_11.2: %i32 = acquire_value %.loc20_11.1
// CHECK:STDOUT: %impl.elem0.loc20: %.7a6 = impl_witness_access constants.%Copy.impl_witness.0e0, element0 [concrete = constants.%Int.as.Copy.impl.Op.4f6]
// CHECK:STDOUT: %bound_method.loc20_11.1: <bound method> = bound_method %.loc20_11.2, %impl.elem0.loc20
// CHECK:STDOUT: %specific_fn.loc20: <specific function> = specific_function %impl.elem0.loc20, @Int.as.Copy.impl.Op(constants.%int_32) [concrete = constants.%Int.as.Copy.impl.Op.specific_fn]
// CHECK:STDOUT: %bound_method.loc20_11.2: <bound method> = bound_method %.loc20_11.2, %specific_fn.loc20
// CHECK:STDOUT: %Int.as.Copy.impl.Op.call: init %i32 = call %bound_method.loc20_11.2(%.loc20_11.2)
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %C.Op.bound: <bound method> = bound_method %.loc19_18.6, constants.%C.Op
// CHECK:STDOUT: %Op.ref: %C.cpp_destructor.type = name_ref Op, imports.%C.cpp_destructor.decl [concrete = constants.%C.cpp_destructor]
// CHECK:STDOUT: %C.cpp_destructor.bound: <bound method> = bound_method %.loc19_18.6, %Op.ref
// CHECK:STDOUT: %C.cpp_destructor.call: init %empty_tuple.type = call %C.cpp_destructor.bound(%.loc19_18.6)
// CHECK:STDOUT: return %Int.as.Copy.impl.Op.call
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @__global_init() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT: