From bf98c57b9a2bb30dc6f88c29fd89eb85c394c8f1 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Mon, 10 Aug 2026 09:52:28 +0000 Subject: [PATCH] [cling][AST] Strip elaborated keyword exposed by partial desugaring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GetPartiallyDesugaredTypeImpl removes the elaborated type keyword ('typename', 'struct', ...) of its input type: the keyword is pure syntax and must never end up in a normalized type name. Since the LLVM 22 migration removed ElaboratedType, the keyword lives on the type node itself, and desugaring can expose a *new* one that nothing strips. With ```c++ template struct Container { typedef typename std::vector seq_type; seq_type m_sequential; }; ``` the keyword sits on the TemplateSpecializationType that seq_type resolves to, so the normalized name of m_sequential came out as 'typename std::vector'. TStreamerInfo::BuildOld then fails to match it against the 'vector' stored in the file, sets the new type to -2 and skips the element, i.e. the member is silently not read. The leak became visible with 081af22bdc4, which attaches the reconstructed prefix to the TemplateSpecializationType; TypePrinter prints the keyword whenever it is not None. Only TemplateSpecializationType is affected; struct/enum elaborated typedefs already normalize correctly. Fixes #23055 🤖 Done with the help of AI --- .../test/TClingDataMemberInfoTests.cxx | 34 +++++++++++++++++++ interpreter/cling/lib/Utils/AST.cpp | 20 +++++++++++ interpreter/cling/test/Utils/Transform.C | 14 ++++++++ 3 files changed, 68 insertions(+) diff --git a/core/metacling/test/TClingDataMemberInfoTests.cxx b/core/metacling/test/TClingDataMemberInfoTests.cxx index d5f5a740cd13a..f77fa8050a39c 100644 --- a/core/metacling/test/TClingDataMemberInfoTests.cxx +++ b/core/metacling/test/TClingDataMemberInfoTests.cxx @@ -252,3 +252,37 @@ TEST(TClingDataMemberInfo, Offset) EXPECT_EQ(-1L, (ptrdiff_t)GeoManagerInfo->GetAddress()); #endif // R__USE_CXXMODULES and R__HAS_GEOM } + +// https://github.com/root-project/root/issues/23055 +// The 'typename' of a dependent alias is part of the spelling of the type, not +// of its identity: it must not end up in the normalized type name, which is +// what the I/O uses to match a member against the on-file description. +TEST(TClingDataMemberInfo, TypenameKeyword) +{ + gInterpreter->Declare(R"CODE( +#include +namespace ROOT23055 { +struct Track { + int value = 0; +}; +template +struct Container { + typedef typename std::vector seq_type; + seq_type fSequential; + std::vector::value_type> fNested; +}; +} +)CODE"); + + TClass *cl = TClass::GetClass("ROOT23055::Container"); + ASSERT_NE(cl, nullptr); + auto *members = cl->GetListOfDataMembers(); + + auto *sequential = (TDataMember *)members->FindObject("fSequential"); + ASSERT_NE(sequential, nullptr); + EXPECT_STREQ(sequential->GetTrueTypeName(), "vector"); + + auto *nested = (TDataMember *)members->FindObject("fNested"); + ASSERT_NE(nested, nullptr); + EXPECT_STREQ(nested->GetTrueTypeName(), "vector"); +} diff --git a/interpreter/cling/lib/Utils/AST.cpp b/interpreter/cling/lib/Utils/AST.cpp index b393bd4937db6..69acca26448ff 100644 --- a/interpreter/cling/lib/Utils/AST.cpp +++ b/interpreter/cling/lib/Utils/AST.cpp @@ -1545,6 +1545,26 @@ namespace utils { } } + // Part of the normalization is to remove the elaborated type keyword + // ('typename', 'struct', ...); it is pure syntax and must never show up in + // a normalized type name. The keyword of the *input* type is taken care of + // at the top of this function, but desugaring can expose a new one, as in + // typedef typename std::vector seq_type; + // where the keyword sits on the TemplateSpecializationType that the typedef + // resolves to. + if (const auto* TST = + dyn_cast(QT.getTypePtr())) { + if (TST->getKeyword() != ElaboratedTypeKeyword::None) { + Qualifiers quals = QT.getLocalQualifiers(); + QT = Ctx.getTemplateSpecializationType(ElaboratedTypeKeyword::None, + TST->getTemplateName(), + TST->template_arguments(), + /*CanonicalArgs=*/{}, + TST->getCanonicalTypeInternal()); + QT = Ctx.getQualifiedType(QT, quals); + } + } + // If we have a reference, array or pointer we still need to // desugar what they point to. if (isa(QT.getTypePtr()) || diff --git a/interpreter/cling/test/Utils/Transform.C b/interpreter/cling/test/Utils/Transform.C index 31bb42e0654e3..f1d18b582c24d 100644 --- a/interpreter/cling/test/Utils/Transform.C +++ b/interpreter/cling/test/Utils/Transform.C @@ -184,6 +184,14 @@ namespace NS4 { } typedef NS4::Inner::ConcreteTypedef GlobalAlias; +namespace NS5 { + class Track {}; + template class Container { + public: + typedef typename std::vector seq_type; + }; +} + .rawInput 0 const cling::LookupHelper& lookup = gCling->getLookupHelper(); @@ -538,3 +546,9 @@ QT = lookup.findType("const GlobalAlias&", diags); std::cout << Transform::GetPartiallyDesugaredType(Ctx, QT, transConfig).getAsString().c_str() << std::endl; // CHECK: NS4::Inner::TemplateClass & +// The elaborated type keyword ('typename' here) is part of the spelling of the +// typedef target and must not survive the normalization. +QT = lookup.findType("NS5::Container::seq_type", diags); +std::cout << Transform::GetPartiallyDesugaredType(Ctx, QT, transConfig).getAsString().c_str() << std::endl; +// CHECK: std::vector +