diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx index d29dada..edc494f 100644 --- a/src/interop/interop_wrapper.cxx +++ b/src/interop/interop_wrapper.cxx @@ -74,7 +74,9 @@ static inline bool is_integral(std::string& s) { s = "1"; return true; } - return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) { + // allow a leading minus (negative literal) + auto begin = s.begin() + (s.size() > 1 && s[0] == '-' ? 1 : 0); + return !s.empty() && std::find_if(begin, s.end(), [](unsigned char c) { return !std::isdigit(c); }) == s.end(); } @@ -469,6 +471,22 @@ static bool is_identifier(std::string_view s) { std::all_of(s.begin() + 1, s.end(), is_valid_body); }; +// A template argument carried by name needs a CppInterOp that resolves the +// name; such a CppInterOp exports SupportsNamedTemplateArguments. +static bool supportsNamedTemplateArgs() { +#ifdef _WIN32 + return false; // no dlsym; enable once the pin guarantees the capability +#else + static const bool Supported = [] { + // CppInterOp is dlopen'ed RTLD_LOCAL; its exports need its own handle. + void* handle = dlopen(cppinterop_paths().Library.c_str(), + RTLD_LOCAL | RTLD_NOW | RTLD_NOLOAD); + return handle && dlsym(handle, "cppinterop_SupportsNamedTemplateArguments"); + }(); + return Supported; +#endif +} + // returns true if no new type was added. bool interop::AppendTypesSlow(const std::string& name, std::vector& types, @@ -503,6 +521,32 @@ bool interop::AppendTypesSlow(const std::string& name, // outside the query scope, e.g. `typedef Foo Bar;` at TU consulted // from a method on Foo). if (is_identifier(name)) { + // true/false are identifier-shaped value literals. + if (name == "true" || name == "false") { + types.emplace_back(Cpp::GetType("bool").data, + strdup(name == "true" ? "1" : "0")); + return false; + } + if (supportsNamedTemplateArgs()) { + TCppScope_t named = parent ? Cpp::GetNamed(name, parent) : nullptr; + if (!named) + named = Cpp::GetNamed(name); + // The identifier may name a non-type entity (constexpr variable, enum + // constant); pass its qualified name so Sema gets an expression, not the + // entity's type. + if (named && (Cpp::IsVariable(named) || Cpp::IsEnumConstant(named))) { + types.emplace_back( + Cpp::GetTypeFromScope(named).data, + strdup(Cpp::GetQualifiedCompleteName(named).c_str())); + return false; + } + // Template name (template-template arg): no type; carried by name. + if (named && Cpp::IsTemplate(named)) { + types.emplace_back( + nullptr, strdup(Cpp::GetQualifiedCompleteName(named).c_str())); + return false; + } + } TCppType_t type = parent ? Cpp::GetType(name, parent) : nullptr; if (!type) type = Cpp::GetType(name); @@ -572,16 +616,31 @@ bool interop::AppendTypesSlow(const std::string& name, } if (!type) { + // Qualified template name (template-template arg). + if (supportsNamedTemplateArgs()) { + if (TCppScope_t named = GetEnumFromCompleteName(i)) { + if (Cpp::IsTemplate(named)) { + types.emplace_back( + nullptr, strdup(Cpp::GetQualifiedCompleteName(named).c_str())); + continue; + } + } + } types.clear(); return true; } if (is_integral(i)) integral_value = strdup(i.c_str()); - if (TCppScope_t scope = GetEnumFromCompleteName(i)) + if (TCppScope_t scope = GetEnumFromCompleteName(i)) { if (Cpp::IsEnumConstant(scope)) integral_value = strdup(std::to_string(Cpp::GetEnumConstantValue(scope)).c_str()); + // A variable is a non-type argument; pass its name (see the identifier + // path). + else if (supportsNamedTemplateArgs() && Cpp::IsVariable(scope)) + integral_value = strdup(Cpp::GetQualifiedCompleteName(scope).c_str()); + } types.emplace_back(type.data, integral_value); } return false; diff --git a/test/support.py b/test/support.py index 1d75f8c..d9ed7c0 100644 --- a/test/support.py +++ b/test/support.py @@ -1,5 +1,6 @@ from __future__ import print_function +import ctypes import os import subprocess import sys @@ -142,3 +143,24 @@ def _jit_resolves_std_filesystem(): CAN_JIT_STD_FILESYSTEM = _jit_resolves_std_filesystem() IS_VALGRIND = True if os.getenv("IS_VALGRIND") else False + + +def _has_named_template_args(): + """Whether a template argument may name a constant. + + Look for the same CppInterOp export that cppjit gates on. A rejected + instantiation leaves interpreter state that changes later tests. + """ + + libname = { + "win32": "clangCppInterOp.dll", + "darwin": "libclangCppInterOp.dylib", + }.get(sys.platform, "libclangCppInterOp.so") + lib = os.path.join(os.path.dirname(cppjit.__file__), "interop", "lib", libname) + try: + return hasattr(ctypes.CDLL(lib), "cppinterop_SupportsNamedTemplateArguments") + except OSError: + return False + + +HAS_NAMED_TEMPLATE_ARGS = _has_named_template_args() diff --git a/test/test_templates.py b/test/test_templates.py index 127ec7f..0eb74a6 100644 --- a/test/test_templates.py +++ b/test/test_templates.py @@ -1,6 +1,7 @@ import py from pytest import mark, raises from support import ( + HAS_NAMED_TEMPLATE_ARGS, IS_CLANG_REPL, IS_CLING, IS_LINUX_ARM, @@ -84,6 +85,57 @@ def test02_non_type_template_args(self): assert cppjit.gbl.nt_templ_args[1]() == 1 assert cppjit.gbl.nt_templ_args[256]() == 256 + # negative literals are values, not types + assert cppjit.gbl.nt_templ_args[-1]() == -1 + assert cppjit.gbl.nt_templ_args[-256]() == -256 + + # true/false are identifier-shaped value literals + cppjit.cppdef("template bool nt_templ_bool() { return b; };") + assert cppjit.gbl.nt_templ_bool["true"]() is True + assert cppjit.gbl.nt_templ_bool["false"]() is False + + @mark.skipif( + not HAS_NAMED_TEMPLATE_ARGS, + reason="needs a CppInterOp that resolves named template arguments", + ) + def test02a_named_template_args(self): + """Use of template names and named constants as template arguments""" + + import cppjit + + cppjit.cppdef("""\ + template struct NtPlain {}; + constexpr int kNtThree = 3; + enum NtEnum { kNtFour = 4 }; + namespace ntarg { + template using Alias = NtPlain; + namespace inner { template struct Nested {}; } + template