diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 0c3fee7e4..b6953a755 100644 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -2618,6 +2618,18 @@ void get_type_as_string(QualType QT, std::string& type_name, ASTContext& C, PrintingPolicy Policy) { // TODO: Implement cling desugaring from utils::AST // cling::utils::Transform::GetPartiallyDesugaredType() + // Desugar template type alias specializations (e.g. std::enable_if_t, + // std::remove_cvref_t). Their printed form can carry expression-level + // template arguments (variable-template references, SFINAE predicates) + // that PrintingPolicy::FullyQualifiedName does not propagate into, so the + // emitted text may reference identifiers like `is_constructible_v` without + // the `std::` qualifier and fail to compile in the wrapper. Regular + // typedefs (e.g. std::string) keep their sugared name. + while (const auto* TST = QT->getAs()) { + if (!TST->isTypeAlias()) + break; + QT = TST->desugar(); + } if (!QT->isTypedefNameType() || QT->isBuiltinType()) QT = QT.getDesugaredType(C); Policy.Suppress_Elab = true; diff --git a/unittests/CppInterOp/FunctionReflectionTest.cpp b/unittests/CppInterOp/FunctionReflectionTest.cpp index c67c4ee46..e280bbb1b 100644 --- a/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -2289,6 +2289,48 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { EXPECT_FALSE(Cpp::IsLambdaClass(Cpp::GetFunctionReturnType(bar))); } +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_WrapAliasTemplateReturnType) { + // Regression test for cppyy issue + // https://github.com/compiler-research/cppyy/issues/218. Building a wrapper + // for a function template whose return type is a template type alias + // specialisation (here std::enable_if_t, std::any>) + // used to fail to compile: the printed return type kept the sugared alias + // form, and Clang's FullyQualifiedName printing policy does not propagate + // into the expression-level non-type template argument, so the emitted + // wrapper referenced `is_constructible_v` without the `std::` qualifier. The + // fix in get_type_as_string desugars template type alias specialisations + // before printing. + // + // Triggering Sema to keep the non-type template argument as an unevaluated + // expression (rather than folding it to a constant) requires the recursive + // SFINAE shape used by libstdc++'s std::any ctors. Hand-rolled trait classes + // get folded eagerly, so the test reaches the bug via std::make_any rather + // than a stdlib-free construction. + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + + std::vector interpreter_args = {"-std=c++17", "-include", "new"}; + TestFixture::CreateInterpreter(interpreter_args); + Interp->process(R"( + #include + class MyClass {}; + )"); + + // Instantiate the variadic make_any with two pointer template args. + // libstdc++'s overload set also contains an initializer_list form + // that SFINAEs out here. + Cpp::TCppFunction_t spec = Cpp::InstantiateTemplateFunctionFromString( + "std::make_any"); + ASSERT_TRUE(spec) + << "Sema failed to substitute std::make_any"; + + // Without the fix, MakeFunctionCallable fails to compile the wrapper + // and returns a JitCall of kUnknown. + Cpp::JitCall JC = Cpp::MakeFunctionCallable(spec); + EXPECT_EQ(JC.getKind(), Cpp::JitCall::kGenericCall); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsConstMethod) { std::vector Decls, SubDecls; std::string code = R"(