From 90f6654b481e7dcb3943e2cbead71d1b8b473197 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 16 Jul 2026 08:17:08 -0700 Subject: [PATCH] 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. --- toolchain/check/cpp/constant.cpp | 52 ++++--- .../cpp/function/import/constexpr.carbon | 145 ++++++++++++++++++ 2 files changed, 176 insertions(+), 21 deletions(-) diff --git a/toolchain/check/cpp/constant.cpp b/toolchain/check/cpp/constant.cpp index 3602cc9c0cb2..519c2d43f129 100644 --- a/toolchain/check/cpp/constant.cpp +++ b/toolchain/check/cpp/constant.cpp @@ -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_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 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(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 notes; diff --git a/toolchain/check/testdata/interop/cpp/function/import/constexpr.carbon b/toolchain/check/testdata/interop/cpp/function/import/constexpr.carbon index e1779468f9dd..52759af87d59 100644 --- a/toolchain/check/testdata/interop/cpp/function/import/constexpr.carbon +++ b/toolchain/check/testdata/interop/cpp/function/import/constexpr.carbon @@ -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: = 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 %int_3.1ba, %Core.IntLiteral.as.ImplicitAs.impl.Convert.e39 [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.e39, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete] +// CHECK:STDOUT: %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: = 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 %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 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: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// 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: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// 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 %int_3, %impl.elem0.loc19 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound] +// CHECK:STDOUT: %specific_fn.loc19: = 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 %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: = 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 %.loc20_11.2, %impl.elem0.loc20 +// CHECK:STDOUT: %specific_fn.loc20: = 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 %.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: +// CHECK:STDOUT: %C.Op.bound: = 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 %.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: +// CHECK:STDOUT: } +// CHECK:STDOUT: