From a2f913ca1c4c70018e91d552b3bc16c2b7dca279 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Sun, 28 Jun 2026 10:50:26 +0200 Subject: [PATCH] Resolve templated type names as a whole in GetScope GetScope used to split a templated name like "std::array" into the template "std::array" plus the argument list "float, 3", then feed that list to the all-types __Cppyy_AppendTypesSlow trampoline and instantiate the template from the result. That cannot represent non-type template arguments: the trampoline takes only types, so an argument such as the `3` in std::array forces a guaranteed compile failure. The failure is normally swallowed, but for arguments the string-parsing fallback can't recover (e.g. an enum constant printed as "(EOp)0"), the type fails to resolve and the resulting interpreter error state breaks the next, unrelated JIT call wrapper. Resolve the full type expression through the trampoline instead and read its scope back with GetScopeFromType, so non-type arguments are handled and no failing instantiation is emitted. The trampoline declares its variable in the global scope, so a name written relative to a parent scope (e.g. "vector" looked up in std) would no longer be found. Honor the parent that AppendTypesSlow already accepts: when the name doesn't resolve as given, retry the trampoline declaration with the parent's qualification prepended. Naming a type as a template argument does not instantiate it, so the resolved specialization may be declared but undefined. Earlier the InstantiateTemplate call completed it; restore that guarantee with an explicit IsComplete, since callers expect a complete scope (e.g. to walk its base classes) and would otherwise hit an assertion on an incomplete class. --- clingwrapper/src/clingwrapper.cxx | 63 +++++++++++++++++-------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/clingwrapper/src/clingwrapper.cxx b/clingwrapper/src/clingwrapper.cxx index cf06a8c6..b2a22d34 100644 --- a/clingwrapper/src/clingwrapper.cxx +++ b/clingwrapper/src/clingwrapper.cxx @@ -529,15 +529,24 @@ bool Cppyy::AppendTypesSlow(const std::string& name, if (!struct_count) Cpp::Declare(code.c_str(), /*silent=*/true); // initialize the trampoline - std::string var = "__Cppyy_s" + std::to_string(struct_count++); - if (!Cpp::Declare(("__Cppyy_AppendTypesSlow<" + resolved_name + "> " + var +";\n").c_str(), /*silent=*/true)) { - std::lock_guard Lock(InterOpMutex); - TCppType_t varN = - Cpp::GetVariableType(Cpp::GetNamed(var.c_str(), /*parent=*/nullptr)); - TCppScope_t instance_class = Cpp::GetScopeFromType(varN); - size_t oldSize = types.size(); - Cpp::GetClassTemplateInstantiationArgs(instance_class, types); - return oldSize == types.size(); + // The trampoline declares its variable in the global scope, so a name + // written relative to a parent (e.g. "vector" looked up in std) + // won't resolve. Try the name as given, then qualified by the parent. + std::vector candidates = {resolved_name}; + if (parent && parent != Cpp::GetGlobalScope() && + (Cppyy::IsNamespace(parent) || Cppyy::IsClass(parent))) + candidates.push_back(Cpp::GetQualifiedCompleteName(parent) + "::" + resolved_name); + + for (const std::string& candidate : candidates) { + std::string var = "__Cppyy_s" + std::to_string(struct_count++); + if (!Cpp::Declare(("__Cppyy_AppendTypesSlow<" + candidate + "> " + var + ";\n").c_str(), /*silent=*/true)) { + TCppType_t varN = + Cpp::GetVariableType(Cpp::GetNamed(var.c_str(), /*parent=*/nullptr)); + TCppScope_t instance_class = Cpp::GetScopeFromType(varN); + size_t oldSize = types.size(); + Cpp::GetClassTemplateInstantiationArgs(instance_class, types); + return oldSize == types.size(); + } } // We split each individual types based on , and resolve it @@ -687,25 +696,23 @@ Cppyy::TCppScope_t Cppyy::GetScope(const std::string& name, // FIXME: avoid string parsing here if (name.find('<') != std::string::npos) { - // Templated Type; May need instantiation - size_t start = name.find('<'); - size_t end = name.rfind('>'); - std::string params = name.substr(start + 1, end - start - 1); - - std::string pure_name = name.substr(0, start); - Cppyy::TCppScope_t scope = Cpp::GetScope(pure_name, parent_scope); - if (!scope && (!parent_scope || parent_scope == Cpp::GetGlobalScope())) - scope = Cpp::GetScopeFromCompleteName(pure_name); - - if (Cppyy::IsTemplate(scope)) { - std::vector templ_params; - InterOpMutex.unlock(); // unlock to allow AppendTypesSlow - if (!Cppyy::AppendTypesSlow(params, templ_params)) { - std::lock_guard Lock(InterOpMutex); - return Cpp::InstantiateTemplate(scope, templ_params.data(), - templ_params.size(), - /*instantiate_body=*/false); - } + // Templated type; may need instantiation. Resolve the whole type + // expression (e.g. "std::array") and read back its scope. + // Splitting off the argument list and resolving it directly cannot + // represent non-type arguments such as the `3` in std::array. + std::vector types; + InterOpMutex.unlock(); // unlock to allow AppendTypesSlow + bool added_new_type = !Cppyy::AppendTypesSlow(name, types, /*parent=*/parent_scope); + std::lock_guard Lock(InterOpMutex); + if (added_new_type && types.size() == 1) { + TCppScope_t scope = Cpp::GetScopeFromType(types[0].m_Type); + // Naming the type as a template argument above does not instantiate + // it, so the specialization may still be declared-but-undefined. + // Force its definition: callers expect a complete scope, e.g. to + // walk its base classes. + if (scope) + Cpp::IsComplete(scope); + return scope; } } return nullptr;