[clang][reflection] Discriminate same-headed member-template reflections of a specialization in NTTP mangling - #301
Open
Cfretz244 wants to merge 3 commits into
Conversation
…ions in NTTP mangling A reflection of a template (ReflectionKind::Template) mangled as the template's NAME only. Class/variable/alias templates cannot be overloaded, so a name suffices for them -- but function templates overload, and two same-named siblings (e.g. absl raw_hash_map's operator[] and its SFINAE-false lifetimebound pack twin) produced byte-identical manglings. Any template dispatcher taking such a reflection as an NTTP -- e.g. template <typename T, std::meta::info tmpl> void level2(); instantiated per member from a `template for` over members_of(^^T) -- therefore got ONE mangled name for its two specializations. The AST-level specializations are correct and distinct; CodeGen then silently folds the linkonce_odr definitions by mangled name, and a single body serves both call sites. No diagnostic at any point. Fix: for FunctionTemplateDecl reflections, append a '$'-bracketed ODR hash of the template parameter list + the templated declaration pattern. The ODR hash is cross-TU-stable by design (modules use it to compare decls across TUs), so legitimate linkonce_odr merging is preserved. A structural mangling of the pattern's function type was tried first and abandoned: real-world dependent pattern types (lifetimebound SFINAE, noexcept(...) referencing parameters) embed ParmVarDecl-referencing expressions that mangleFunctionParam cannot encode outside a function-declaration context. The regression test asserts at RUNTIME that the two dispatcher instantiations are distinct functions with their own bodies; static_asserts cannot catch the fold because the AST is always correct. Fixes llvm#286. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ting unreachable
members_of over a namespace enumerates deduction guides like any other
member; lifting that list into define_static_array makes each element
reflection a template argument of the backing array specialization, so guide
reflections MUST mangle. mangleReflection's Template case encoded a
reflected template via mangleTemplateName -> mangleUnqualifiedName, which has
no case for CXXDeductionGuideName -- llvm_unreachable("Can't mangle a
deduction guide name!", ItaniumMangle.cpp:1774). The field shape is
TartanLlama/expected's namespace-scope `unexpected(E) -> unexpected<E>`: any
reflection consumer walking the tl namespace ICE'd at codegen.
Fix, local to mangleReflection (mangleUnqualifiedName's unreachable is a
sound invariant for normal symbol mangling and stays): encode "dg" + the
DEDUCED template's name + the same '$'-bracketed ODR-hash discriminator
already used for overloaded function templates. Every guide for one template
shares a single DeclarationName -- and Sema's IMPLICIT guides
(per-constructor + copy) enumerate alongside explicit ones, with the
per-constructor guide structurally identical to a same-signature explicit
guide -- so the hash also folds in isImplicit() and the deduction-candidate
kind. Deterministic and cross-TU-stable, preserving legitimate linkonce_odr
merging; distinct from a reflection of the deduced class template itself.
Test: deduction-guide-reflection-mangling.pass.cpp -- two explicit + two
implicit guides lifted via define_static_array, pinned as &probe<m> NTTPs,
asserted pairwise distinct at runtime (a fold is invisible to static_assert).
Stacks on the same-named-function-template NTTP discriminator change (it
extends the same mangleReflection hash block).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ons of a specialization in NTTP mangling The NTTP discriminator for overloaded function-template reflections hashes the template parameter list plus the declaration pattern via ODRHash::AddFunctionDecl -- but AddFunctionDecl silently NO-OPS for any declaration in "specialization context" (its decl-context walk returns on finding a ClassTemplateSpecializationDecl). A member template of an instantiated class template -- the common members_of shape -- therefore contributed only its template HEAD to the hash, and same-named siblings with IDENTICAL heads still mangled identically: tl::expected<T,E>'s four value() member templates (const&/&/const&&/&&, one shared `template <class U = T, enable_if_t<!is_void<U>::value>* = nullptr>` head) got one mangled name, CodeGen silently folded the linkonce_odr dispatch specializations, and value() never bound in a reflection-driven binding generator -- caught by a differential test suite, not a diagnostic. The original change's own field shape escaped only because absl's operator[] siblings differ in their heads. Fix: additionally hash what AddFunctionDecl skips there -- the pattern's function type via ODRHash::AddQualType (return type, parameter types, cv-quals; the ODR type hash handles the dependent pattern types a structural MANGLING cannot, the original constraint) plus the ref-qualifier, which even VisitFunctionProtoType omits (two siblings can differ in nothing else). Test: fn-template-nttp-mangling-spec-context.pass.cpp -- the four-sibling value() shape and a ref-qualifier-only pair, asserted pairwise distinct at runtime. Stacks on the deduction-guide mangling change (same hash block). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #300.
Problem
The NTTP discriminator for overloaded function-template reflections (#286 / #287) hashes the template parameter list plus the declaration pattern via
ODRHash::AddFunctionDecl(pattern, /*SkipBody=*/true). ButAddFunctionDeclsilently no-ops for any declaration in "specialization context" — its decl-context walk returns on finding aClassTemplateSpecializationDecl. A member template of an instantiated class template — the commonmembers_ofshape — therefore contributed only its template HEAD to the hash, and same-named siblings with identical heads still mangled identically. #287's own field shape (absloperator[]+ its pack twin) escaped only because those heads differ.Field shape:
tl::expected<T,E>'s fourvalue()member templates (const&/&/const&&/&&, one sharedtemplate <class U = T, enable_if_t<!is_void<U>::value>* = nullptr>head) — all four reflections got one mangled name, CodeGen silently folded the linkonce_odr dispatcher specializations of a reflection-driven binding generator, andvalue()never bound. Caught by a differential test suite, not a diagnostic; where the TU collides explicitly the symptom iserror: definition with same mangled name ... as another definition(see #300 for the reproducer).Fix
clang/lib/AST/ItaniumMangle.cpp, same hash block: additionally hash whatAddFunctionDeclskips in specialization context —ODRHash::AddQualType(return type, parameter types, cv-quals). The ODR hash handles the dependent pattern types that a structural mangling of the type cannot (lifetimebound SFINAE,noexcept(...)referencing parameters — the original [clang][reflection] Discriminate same-named function-template reflections in NTTP mangling #287 constraint);ODRHash'sVisitFunctionProtoTypeomits — two siblings can differ in nothing else.Test
libcxx/test/std/experimental/reflection/fn-template-nttp-mangling-spec-context.pass.cpp(new): the four-siblingvalue()shape (identical heads, cv/ref-qualifier + return-type differences) and a ref-qualifier-only pair, each pinned as&probe<m>NTTPs from amembers_ofwalk over the specialization and asserted pairwise distinct at runtime.Validation
837da39eb88c); builds standalone.-cwith the duplicate-mangled-name error (clean at-fsyntax-only; clean with the same signatures at namespace scope — the no-specialization-context control); the new test fails.substitute-nested-dependent.pass.cppand the deduction-guide test still pass on the stack tip.clang/test/Reflection: 16/16 with the fix on this machine — identical to base.value()differential went fromAttributeErrorto passing) is green end-to-end,tl::expected<int,std::string>::value()bound with all four sibling reflections mangled distinctly.🤖 Generated with Claude Code