diff --git a/src/compile/compilation.cpp b/src/compile/compilation.cpp index e1e10adce..f78a138a9 100644 --- a/src/compile/compilation.cpp +++ b/src/compile/compilation.cpp @@ -320,8 +320,8 @@ CompilationStatus CompilationUnitRef::Self::run_clang( self.run_tidy(); - if(instance.hasSema()) { - self.resolver.emplace(instance.getSema()); + if(instance.hasASTContext()) { + self.resolver.emplace(instance.getASTContext()); } return CompilationStatus::Completed; diff --git a/src/feature/hover.cpp b/src/feature/hover.cpp index a38e46dd9..08f7708db 100644 --- a/src/feature/hover.cpp +++ b/src/feature/hover.cpp @@ -1140,7 +1140,7 @@ auto decls_at(CompilationUnitRef unit, llvm::ArrayRef touc continue; } - for(auto& occurrence: resolve_occurrences(sem_node, &unit.resolver())) { + for(auto& occurrence: resolve_occurrences(semantics, n, &unit.resolver())) { auto location = occurrence.location; if(location.isMacroID()) { location = unit.spelling_location(location); diff --git a/src/feature/semantic_tokens.cpp b/src/feature/semantic_tokens.cpp index 8f5e848a7..c7f5f78e8 100644 --- a/src/feature/semantic_tokens.cpp +++ b/src/feature/semantic_tokens.cpp @@ -444,7 +444,7 @@ class SemanticTokensCollector { combine(token_semantics[index], candidate); }; - for(const auto& entry: semantics.node_entries()) { + for(auto [entry_index, entry]: llvm::enumerate(semantics.node_entries())) { const SemanticNode& node = entry.node; switch(node.kind()) { case SemanticNode::Kind::MacroDefine: { @@ -486,7 +486,10 @@ class SemanticTokensCollector { } default: { - for(auto& occurrence: resolve_occurrences(node, &unit.resolver())) { + for(auto& occurrence: + resolve_occurrences(semantics, + static_cast(entry_index), + &unit.resolver())) { anchor(occurrence.location, classify_decl(occurrence.decl, occurrence.kind), false); diff --git a/src/index/tu_index.cpp b/src/index/tu_index.cpp index 0acc7b0e5..357be6f5b 100644 --- a/src/index/tu_index.cpp +++ b/src/index/tu_index.cpp @@ -527,7 +527,7 @@ class Projector { continue; } - for(auto& occurrence: resolve_occurrences(node, &unit.resolver())) { + for(auto& occurrence: resolve_occurrences(semantics, i, &unit.resolver())) { add_occurrence(occurrence.decl, occurrence.kind, occurrence.location); /// Every occurrence is mirrored as a self-relation, so diff --git a/src/semantic/resolver.cpp b/src/semantic/resolver.cpp index 53f61da0a..8f2e4ee95 100644 --- a/src/semantic/resolver.cpp +++ b/src/semantic/resolver.cpp @@ -1,31 +1,35 @@ #include "semantic/resolver.h" -#include #include +#include "semantic/unifier.h" #include "support/logging.h" -#include "clang/Sema/Template.h" -#include "clang/Sema/TemplateDeduction.h" -#include "clang/Sema/TreeTransform.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/RecursiveASTVisitor.h" /// Template Resolver — pseudo-instantiation of dependent C++ types. /// /// Architecture: -/// PseudoInstantiator (TreeTransform) — heuristic lookup in primary templates/partial specs -/// ├─ TransformDependentNameType — lookup member in template, substitute, recurse -/// ├─ TransformDependentTemplateSPTType — resolve DTST via hole()/lookup, CTD→TST -/// ├─ TransformTemplateTypeParmType — substitute from stack (+ default arg fallback) -/// ├─ TransformTypedefType — delegate to SubstituteOnly (no lookup) -/// └─ TransformType — depth guard + null safety +/// PseudoInstantiator — heuristic lookup in primary templates/partial specs, +/// driven by a hand-written QualType → QualType rewriter with two policies: +/// ├─ Policy::Substitute — expand typedefs/aliases and substitute template +/// │ parameters from the stack; dependent names pass +/// │ through untouched (no lookup) +/// └─ Policy::Resolve — Substitute plus heuristic resolution of +/// DependentNameType/DependentTemplateSpecializationType +/// via member lookup and argument deduction /// -/// SubstituteOnly (TreeTransform) — Phase 2: typedef expansion + param substitution only -/// Does NOT override TransformDependentNameType → no heuristic lookup → breaks cycles. +/// Key invariant: typedef/alias expansion always runs under Policy::Substitute, +/// so it can never re-enter heuristic lookup. Violating this causes +/// typedef ↔ lookup infinite cycles. /// -/// Key invariant: Phase 2 (SubstituteOnly) must NEVER trigger Phase 1 (heuristic lookup). -/// Violating this causes typedef ↔ lookup infinite cycles. -/// -/// See docs: temp/template-resolver-analysis.md, temp/resolver-vector-pipeline.md +/// Everything is pure AST computation (TypeUnifier + ASTContext node +/// construction); Sema and TreeTransform are deliberately not used, so +/// resolution cannot emit diagnostics or mutate the unit's semantic state. namespace clice { @@ -68,77 +72,27 @@ void visit_template_decl_contexts(clang::Decl* decl, const Callback& callback) { } } -/// Resugar canonical TemplateTypeParmType with original parameter declarations. -/// TreeTransform's TransformType(QualType) materializes a trivial -/// TypeSourceInfo at getBaseLocation(). That location must be valid: -/// keyword-carrying dependent types (e.g. `typename T::type`) otherwise -/// produce a TypeLoc whose keyword is set but whose KeywordLoc is invalid, -/// tripping Sema::CheckTypenameType's assertion (and reading garbage -/// location data in release builds). -inline clang::SourceLocation transform_base_location(clang::Sema& sema) { - auto& SM = sema.getSourceManager(); - return SM.getLocForStartOfFile(SM.getMainFileID()); -} - -class ResugarOnly : public clang::TreeTransform { -public: - ResugarOnly(clang::Sema& sema, clang::Decl* decl) : - TreeTransform(sema), context(sema.getASTContext()), - base_location(transform_base_location(sema)) { - visit_template_decl_contexts(decl, - [&](clang::Decl* decl, clang::TemplateParameterList* params) { - lists.push_back(params); - }); - std::ranges::reverse(lists); - } - - /// TreeTransform's setBase is a no-op; the CRTP contract is to override - /// getBaseLocation. See transform_base_location for why it must be valid. - clang::SourceLocation getBaseLocation() { - return base_location; - } - - clang::QualType TransformTemplateTypeParmType(clang::TypeLocBuilder& TLB, - clang::TemplateTypeParmTypeLoc TL, - bool = false) { - clang::QualType type = TL.getType(); - auto TTPT = TL.getTypePtr(); - if(!TTPT->getDecl()) { - auto depth = TTPT->getDepth(); - if(depth >= lists.size()) { - auto NewTL = TLB.push(type); - NewTL.setNameLoc(getBaseLocation()); - return NewTL.getType(); - } - auto index = TTPT->getIndex(); - auto isPack = TTPT->isParameterPack(); - auto param = llvm::cast(lists[depth]->getParam(index)); - type = context.getTemplateTypeParmType(depth, index, isPack, param); - } - auto NewTL = TLB.push(type); - NewTL.setNameLoc(getBaseLocation()); - return NewTL.getType(); - } - -private: - clang::ASTContext& context; - clang::SourceLocation base_location; - llvm::SmallVector lists; -}; - /// A helper class to record the instantiation stack. struct InstantiationStack { using Arguments = llvm::SmallVector; using TemplateArguments = llvm::ArrayRef; - llvm::SmallVector> data; + struct Frame { + clang::Decl* decl; + clang::TemplateParameterList* params; + Arguments arguments; + }; + + llvm::SmallVector data; bool empty() const { return data.empty(); } - void push(clang::Decl* decl, TemplateArguments arguments) { - data.emplace_back(decl, arguments); + void push(clang::Decl* decl, + clang::TemplateParameterList* params, + TemplateArguments arguments) { + data.emplace_back(decl, params, Arguments(arguments.begin(), arguments.end())); } void pop() { @@ -149,229 +103,190 @@ struct InstantiationStack { return data; } - /// Look up a template type parameter in the stack by matching its depth against - /// each frame's template parameter list depth. Searches from innermost (top) to - /// outermost (bottom). Returns nullptr if no matching frame or index out of range. + /// Look up a template parameter's binding, innermost frame first. /// - /// IMPORTANT: depth alone identifies the template "level", not the specific template. - /// Different templates at the same depth (e.g. vector and test both at depth 0) will - /// match the FIRST frame found. Callers must ensure the stack only contains relevant - /// frames when calling this. - const clang::TemplateArgument* find_argument(const clang::TemplateTypeParmType* T) const { - auto depth = T->getDepth(); - auto index = T->getIndex(); - for(auto it = data.rbegin(); it != data.rend(); ++it) { - clang::TemplateParameterList* params = nullptr; - if(auto* CTD = llvm::dyn_cast(it->first)) { - params = CTD->getTemplateParameters(); - } else if(auto* CTPSD = llvm::dyn_cast( - it->first)) { - params = CTPSD->getTemplateParameters(); - } else if(auto* TATD = llvm::dyn_cast(it->first)) { - params = TATD->getTemplateParameters(); - } else if(auto* FTD = llvm::dyn_cast(it->first)) { - params = FTD->getTemplateParameters(); - } - if(params && params->getDepth() == depth) { - if(index < it->second.size()) { - return &it->second[index]; - } - return nullptr; - } - } - return nullptr; - } -}; - -/// Helper to extract underlying type from a Decl. -static clang::QualType get_decl_type(clang::Decl* decl) { - if(!decl) - return clang::QualType(); - if(auto* TND = llvm::dyn_cast(decl)) - return TND->getUnderlyingType(); - if(auto* RD = llvm::dyn_cast(decl)) - return clang::QualType(RD->getTypeForDecl(), 0); - return clang::QualType(); -} - -/// Phase 2 substitution transform. Expands typedefs and substitutes template parameters -/// from the InstantiationStack, but does NOT override TransformDependentNameType. -/// -/// This is critical: the base class TransformDependentNameType just substitutes params -/// in the qualifier and rebuilds the DependentNameType — it does NOT do our heuristic -/// lookup. This breaks the typedef ↔ lookup cycle that would occur if typedef expansion -/// triggered PseudoInstantiator's TransformDependentNameType. -/// -/// Handles: TypedefType, ElaboratedType, InjectedClassNameType, alias TST, TTPT. -/// Does NOT handle: multi-element pack expansion, NTTP, template template params. -class SubstituteOnly : public clang::TreeTransform { - using Base = clang::TreeTransform; - -public: - SubstituteOnly(clang::Sema& sema, InstantiationStack& stack) : - Base(sema), context(sema.getASTContext()), stack(stack), - base_location(transform_base_location(sema)) {} - - clang::SourceLocation getBaseLocation() { - return base_location; - } - - using Base::TransformType; - - clang::QualType TransformType(clang::QualType type) { - if(type.isNull() || !type->isDependentType()) { - return type; - } - if(depth > 16) { - return type; - } - ++depth; - auto result = Base::TransformType(type); - --depth; - return result.isNull() ? type : result; - } - - /// Desugar dependent typedefs to expose template parameters for substitution. - clang::QualType TransformTypedefType(clang::TypeLocBuilder& TLB, clang::TypedefTypeLoc TL) { - if(auto* TND = TL.getTypedefNameDecl()) { - auto underlying = TND->getUnderlyingType(); - if(underlying->isDependentType()) { - auto type = TransformType(underlying); - if(!type.isNull()) { - if(auto ET = llvm::dyn_cast(type)) { - type = ET->getNamedType(); + /// When the parameter's declaration is known, only the frame whose + /// parameter list actually contains that declaration matches — unrelated + /// templates that merely share the same depth (e.g. `test` and + /// `__alloc_traits`, both at depth 0) never capture each other's + /// parameters. If no frame owns the declaration, the parameter is left + /// unsubstituted rather than guessed. + /// + /// Canonical parameters carry no declaration; those fall back to matching + /// the frame's parameter list depth. + /// Stable handle to a binding: (frame index, argument index). Pointers + /// into the frame vector go stale whenever a nested rewrite pushes a + /// frame and the vector reallocates; indices survive because pushes only + /// append and the walk never outlives the frames below it. + using Slot = std::pair; + + std::optional find_slot(const clang::NamedDecl* decl, + unsigned depth, + unsigned index) const { + if(decl) { + for(unsigned f = data.size(); f > 0; f -= 1) { + const auto& frame = data[f - 1]; + if(frame.params && index < frame.params->size() && + frame.params->getParam(index) == decl) { + if(index < frame.arguments.size()) { + return Slot{f - 1, index}; } - TLB.pushTrivial(context, type, getBaseLocation()); - return type; + return std::nullopt; } } + return std::nullopt; } - return Base::TransformTypedefType(TLB, TL); - } - clang::QualType TransformElaboratedType(clang::TypeLocBuilder& TLB, - clang::ElaboratedTypeLoc TL) { - clang::QualType type = TransformType(TL.getNamedTypeLoc().getType()); - if(type.isNull()) { - return Base::TransformElaboratedType(TLB, TL); + for(unsigned f = data.size(); f > 0; f -= 1) { + const auto& frame = data[f - 1]; + if(frame.params && frame.params->getDepth() == depth) { + if(index < frame.arguments.size()) { + return Slot{f - 1, index}; + } + return std::nullopt; + } } - TLB.pushTrivial(context, type, getBaseLocation()); - return type; + return std::nullopt; } - clang::QualType TransformInjectedClassNameType(clang::TypeLocBuilder& TLB, - clang::InjectedClassNameTypeLoc TL) { - auto ICT = TL.getTypePtr(); - clang::QualType type = TransformType(ICT->getInjectedSpecializationType()); - if(type.isNull()) { - return Base::TransformInjectedClassNameType(TLB, TL); - } - TLB.pushTrivial(context, type, getBaseLocation()); - return type; + clang::TemplateArgument& slot(Slot handle) { + return data[handle.first].arguments[handle.second]; } - using Base::TransformTemplateSpecializationType; - - clang::QualType TransformTemplateSpecializationType(clang::TypeLocBuilder& TLB, - clang::TemplateSpecializationTypeLoc TL) { - if(TL.getTypePtr()->isTypeAlias()) { - clang::QualType type = TransformType(TL.getTypePtr()->desugar()); - if(!type.isNull()) { - TLB.pushTrivial(context, type, getBaseLocation()); - return type; - } - } - return Base::TransformTemplateSpecializationType(TLB, TL); + const clang::TemplateArgument* find_argument(const clang::NamedDecl* decl, + unsigned depth, + unsigned index) const { + auto handle = find_slot(decl, depth, index); + return handle ? &data[handle->first].arguments[handle->second] : nullptr; } - /// Substitute template parameters from the stack. - clang::QualType TransformTemplateTypeParmType(clang::TypeLocBuilder& TLB, - clang::TemplateTypeParmTypeLoc TL, - bool = false) { - auto* T = TL.getTypePtr(); - - if(auto* arg = stack.find_argument(T)) { - clang::QualType type; + const clang::TemplateArgument* find_argument(const clang::TemplateTypeParmType* T) const { + return find_argument(T->getDecl(), T->getDepth(), T->getIndex()); + } +}; - if(arg->getKind() == clang::TemplateArgument::Type) { - type = arg->getAsType(); - } else if(arg->getKind() == clang::TemplateArgument::Pack) { - auto pack = arg->getPackAsArray(); - if(pack.size() == 1 && pack[0].getKind() == clang::TemplateArgument::Type) { - type = pack[0].getAsType(); - } - } +/// Collect every template type parameter pack referenced anywhere inside a +/// type, for element-wise expansion of structured pack patterns. +struct PackParmCollector : clang::RecursiveASTVisitor { + llvm::SmallVector packs; + llvm::SmallVector template_packs; + llvm::SmallVector value_packs; - // TODO(pack): Only handles single-element packs (common pack forwarding case). - // Multi-element packs (e.g. Us... = {int, float}) are not expanded here and - // will fall through to return the original type. - if(!type.isNull()) { - TLB.pushTrivial(context, type, TL.getNameLoc()); - return type; - } + bool VisitTemplateTypeParmType(clang::TemplateTypeParmType* T) { + if(T->isParameterPack() && !llvm::is_contained(packs, T)) { + packs.push_back(T); } - - // No substitution: return original type unchanged. - TLB.push(TL.getType()).setNameLoc(TL.getNameLoc()); - return TL.getType(); + return true; } - // TransformDependentNameType is NOT overridden. - // Base class behavior: transforms the qualifier (substitutes params there), - // then rebuilds the DependentNameType. No lookup. + bool VisitDeclRefExpr(clang::DeclRefExpr* expr) { + if(auto NTTP = llvm::dyn_cast(expr->getDecl()); + NTTP && NTTP->isParameterPack() && !llvm::is_contained(value_packs, NTTP)) { + value_packs.push_back(NTTP); + } + return true; + } -private: - clang::ASTContext& context; - clang::SourceLocation base_location; - InstantiationStack& stack; - unsigned depth = 0; + bool TraverseTemplateName(clang::TemplateName name) { + if(auto TTP = + llvm::dyn_cast_or_null(name.getAsTemplateDecl()); + TTP && TTP->isParameterPack() && !llvm::is_contained(template_packs, TTP)) { + template_packs.push_back(TTP); + } + return RecursiveASTVisitor::TraverseTemplateName(name); + } }; -/// The core pseudo-instantiation engine. Extends TreeTransform to resolve dependent -/// names by looking up members in primary templates and partial specializations — -/// a capability clang's own TemplateInstantiator does not have. +/// Helper to extract underlying type from a Decl. +clang::QualType get_decl_type(clang::Decl* decl) { + if(!decl) + return clang::QualType(); + if(auto* TND = llvm::dyn_cast(decl)) + return TND->getUnderlyingType(); + if(auto* RD = llvm::dyn_cast(decl)) + return clang::QualType(RD->getTypeForDecl(), 0); + return clang::QualType(); +} + +/// The core pseudo-instantiation engine. Resolves dependent names by looking up +/// members in primary templates and partial specializations — a capability +/// clang's own instantiation machinery does not have (it only ever sees +/// concrete arguments, so e.g. alias templates never appear on its paths). /// /// Resolution flow for `typename A::type`: -/// 1. TransformDependentNameType intercepts the DependentNameType +/// 1. rewrite() dispatches the DependentNameType to resolve_dependent_name /// 2. lookup(A, "type") → deduce_template_arguments → find member decl -/// 3. substitute(underlying_type) → SubstituteOnly expands typedefs + substitutes params -/// 4. Pop lookup frames, then TransformType on result for further resolution +/// 3. substitute(underlying_type) expands typedefs + substitutes params +/// 4. Pop lookup frames, then rewrite the result for further resolution /// -/// Uses SubstituteOnly for Phase 2 to avoid typedef ↔ lookup cycles. /// Uses active_resolutions / active_ctd_lookups for cycle detection. -class PseudoInstantiator : public clang::TreeTransform { +class PseudoInstantiator { public: - using Base = clang::TreeTransform; - using TemplateArguments = llvm::ArrayRef; - using TemplateDeductionInfo = clang::sema::TemplateDeductionInfo; - - PseudoInstantiator(clang::Sema& sema, + PseudoInstantiator(clang::ASTContext& context, llvm::DenseMap& resolved, unsigned parent_indent = 0) : - Base(sema), sema(sema), context(sema.getASTContext()), resolved(resolved), - indent(parent_indent), base_location(transform_base_location(sema)) {} - - clang::SourceLocation getBaseLocation() { - return base_location; + context(context), resolved(resolved), indent(parent_indent) {} + + /// Rewrite policy. The two-phase split is the resolver's core invariant: + /// typedef/alias expansion must never re-enter heuristic lookup, or + /// mutually recursive typedefs cycle forever. + enum class Policy { + /// Expand sugar and substitute stack parameters only. + Substitute, + /// Substitute plus dependent name resolution through lookup. + Resolve, + }; + + clang::QualType resolve(clang::QualType type) { + return rewrite(type, Policy::Resolve); } -public: - /// Use SubstituteOnly to expand typedefs and substitute parameters without doing lookup. clang::QualType substitute(clang::QualType type) { + return rewrite(type, Policy::Substitute); + } + + /// Entry point for all type rewriting. Guards against: + /// - Null types (return as-is) + /// - Non-dependent types (no transformation needed) + /// - Excessive recursion depth (bail out to prevent runaway recursion) + /// - Exhausted step budget (bounds the total work of one query, including + /// pseudo-SFINAE probes that explore rejected branches) + /// - Null results (return original type instead) + clang::QualType rewrite(clang::QualType type, Policy policy) { if(type.isNull() || !type->isDependentType()) { return type; } - SubstituteOnly subst(sema, stack); - auto result = subst.TransformType(type); + steps += 1; + if(depth > 16 || steps > step_budget) { + truncated = true; + return type; + } + depth += 1; + auto result = rewrite_type(type, policy); + depth -= 1; return result.isNull() ? type : result; } + using lookup_result = clang::DeclContext::lookup_result; + + /// When DeclContext::lookup returns multiple declarations (e.g. a member in + /// both a base class and derived class), take the last one. This heuristic + /// favors the most-derived declaration, though the ordering depends on clang's + /// internal DeclContext storage. + clang::Decl* preferred(lookup_result members) { + clang::Decl* decl = nullptr; + std::ranges::for_each(members, [&](auto member) { decl = member; }); + return decl; + } + /// Verify that `arguments` match `TD`'s parameter list, filling in default - /// template arguments where needed. Default args are substituted using the - /// current stack (via SubstituteOnly), so parameters already provided can - /// appear in default expressions (e.g. `allocator<_Tp>` for vector's `_Alloc`). + /// template arguments where needed. Type defaults are substituted using the + /// current stack, so parameters already provided can appear in default + /// expressions (e.g. `allocator<_Tp>` for vector's `_Alloc`). Non-type and + /// template template defaults are filled when representable without + /// building expressions. bool check_template_arguments(clang::TemplateDecl* TD, TemplateArguments& arguments, llvm::SmallVectorImpl& out) { @@ -381,73 +296,152 @@ class PseudoInstantiator : public clang::TreeTransform { out.emplace_back(arg); } - if(out.size() != list->size()) { - for(auto i = out.size(); i < list->size(); ++i) { - auto param = list->getParam(i); - // TODO(nttp): Only TemplateTypeParmDecl default arguments are handled. - // NonTypeTemplateParmDecl and TemplateTemplateParmDecl defaults are skipped, - // causing check_template_arguments to return false for templates like: - // template struct S; - auto TTPD = llvm::dyn_cast(param); - if(TTPD && TTPD->hasDefaultArgument()) { - auto type = TTPD->getDefaultArgument().getArgument().getAsType(); + for(auto i = out.size(); i < list->size(); i += 1) { + auto param = list->getParam(i); + + if(auto TTPD = llvm::dyn_cast(param); + TTPD && TTPD->hasDefaultArgument()) { + auto type = TTPD->getDefaultArgument().getArgument().getAsType(); + + /// A default inherited from an earlier redeclaration still + /// references that declaration's parameter decls; push every + /// redecl's list (sharing the same arguments) so decl-pointer + /// matching finds them wherever the default was written. + auto frames = stack.data.size(); + if(auto RTD = llvm::dyn_cast(TD)) { + for(auto redecl: RTD->redecls()) { + auto params = + llvm::cast(redecl)->getTemplateParameters(); + if(params != list) { + stack.push(TD, params, out); + } + } + } + stack.push(TD, list, out); + + auto result = substitute(type); - stack.push(TD, out); - auto result = substitute(type); + while(stack.data.size() > frames) { stack.pop(); + } + + if(result.isNull()) { + return false; + } + + LOG_DEBUG( + "{}" "default arg: '{}' = '{}'", + pad(), + TTPD->getNameAsString(), + result.getAsString()); + out.emplace_back(result); + continue; + } - if(result.isNull()) { - return false; + if(auto NTTPD = llvm::dyn_cast(param); + NTTPD && NTTPD->hasDefaultArgument()) { + auto& argument = NTTPD->getDefaultArgument().getArgument(); + if(argument.getKind() != clang::TemplateArgument::Expression) { + out.emplace_back(argument); + continue; + } + auto expr = argument.getAsExpr(); + if(!expr->isValueDependent()) { + if(auto value = expr->getIntegerConstantExpr(context)) { + out.emplace_back( + clang::TemplateArgument(context, *value, NTTPD->getType())); + continue; } + } + /// A default naming an earlier parameter (`M = N`) takes that + /// parameter's already-supplied argument, which may itself + /// still be dependent. Compound expressions stay unfilled. + if(auto NTTP = referenced_nttp(expr); + NTTP && NTTP->getIndex() < out.size() && NTTP->getDepth() == list->getDepth()) { + out.emplace_back(out[NTTP->getIndex()]); + continue; + } + break; + } - LOG_DEBUG( - "{}" "default arg: '{}' = '{}'", - pad(), - TTPD->getNameAsString(), - result.getAsString()); - out.emplace_back(result); + if(auto TTPD = llvm::dyn_cast(param); + TTPD && TTPD->hasDefaultArgument()) { + auto& fallback = TTPD->getDefaultArgument().getArgument(); + + /// A default naming an earlier parameter (`U = TT`) takes + /// that parameter's already-supplied argument. + if(fallback.getKind() == clang::TemplateArgument::Template) { + if(auto prev = llvm::dyn_cast_or_null( + fallback.getAsTemplate().getAsTemplateDecl()); + prev && prev->getDepth() == list->getDepth() && + prev->getIndex() < out.size()) { + out.emplace_back(out[prev->getIndex()]); + continue; + } } + + out.emplace_back(fallback); + continue; } + + break; } - if(out.size() != list->size()) { - return false; + if(out.size() == list->size()) { + return true; } - return true; + /// A parameter pack absorbs any number of surplus arguments (and may + /// stay empty), so exact arity only applies to pack-free lists. + return list->hasParameterPack() && out.size() + 1 >= list->size(); } template bool deduce_template_arguments(Decl* decl, TemplateArguments arguments) { clang::TemplateParameterList* list = nullptr; - TemplateArguments params = {}; + TemplateArguments patterns = {}; if constexpr(std::is_same_v) { const clang::ClassTemplateDecl* CTD = decl; list = CTD->getTemplateParameters(); - params = list->getInjectedTemplateArgs(context); + patterns = list->getInjectedTemplateArgs(context); } else if constexpr(std::is_same_v) { const clang::ClassTemplatePartialSpecializationDecl* CTPSD = decl; list = CTPSD->getTemplateParameters(); - params = CTPSD->getTemplateArgs().asArray(); + patterns = CTPSD->getTemplateArgs().asArray(); } else if constexpr(std::is_same_v) { const clang::TypeAliasTemplateDecl* TATD = decl; list = TATD->getTemplateParameters(); - params = list->getInjectedTemplateArgs(context); + patterns = list->getInjectedTemplateArgs(context); } else { static_assert(dependent_false, "Unknown declaration type"); } assert(list && "No template parameters found"); - TemplateDeductionInfo info = {clang::SourceLocation(), list->getDepth()}; - llvm::SmallVector deduced(list->size()); - - auto result = sema.DeduceTemplateArguments(list, params, arguments, info, deduced, true); - bool success = - result == clang::TemplateDeductionResult::Success && !info.hasSFINAEDiagnostic(); + /// Patterns may reference parameters of enclosing templates (a member + /// partial specialization pinning an outer parameter, e.g. + /// `Inner>`). Those are concrete from this deduction's + /// point of view: substitute the current bindings in first so the + /// unifier compares them structurally instead of guessing. + /// + /// The parameters being deduced must stay free, yet an enclosing + /// in-flight deduction of this same template may have them bound on + /// the stack (recursive chains like allocator_traits' rebind). An + /// empty-argument frame masks them: decl matching stops at it and + /// reports "unbound", while enclosing templates' frames still apply. + llvm::SmallVector substituted; + if(!stack.empty()) { + stack.push(decl, list, {}); + bool changed = rewrite_arguments(patterns, substituted, Policy::Substitute); + stack.pop(); + if(changed) { + patterns = substituted; + } + } - if(!success) { + llvm::SmallVector deduced; + if(!deduce_arguments(context, list, patterns, arguments, deduced)) { return false; } @@ -460,13 +454,12 @@ class PseudoInstantiator : public clang::TreeTransform { visit_template_decl_contexts( llvm::dyn_cast(decl->getDeclContext()), [&](clang::Decl* decl, clang::TemplateParameterList* params) { - stack.push(decl, params->getInjectedTemplateArgs(context)); + stack.push(decl, params, params->getInjectedTemplateArgs(context)); }); std::ranges::reverse(stack.frames()); } - llvm::SmallVector output(deduced.begin(), deduced.end()); - stack.push(decl, output); + stack.push(decl, list, deduced); LOG_DEBUG( "{}deduce {}: {{{}}}", @@ -481,18 +474,18 @@ class PseudoInstantiator : public clang::TreeTransform { }(), [&] { std::string mapping; - for(unsigned j = 0; j < output.size(); ++j) { + for(unsigned j = 0; j < deduced.size(); j += 1) { if(j > 0) mapping += ", "; if(j < list->size()) { mapping += list->getParam(j)->getNameAsString(); mapping += "="; } - if(output[j].getKind() == clang::TemplateArgument::Type) { + if(deduced[j].getKind() == clang::TemplateArgument::Type) { mapping += "'"; - mapping += output[j].getAsType().getAsString(); + mapping += deduced[j].getAsType().getAsString(); mapping += "'"; - } else if(output[j].getKind() == clang::TemplateArgument::Pack) + } else if(deduced[j].getKind() == clang::TemplateArgument::Pack) mapping += ""; else mapping += ""; @@ -503,26 +496,14 @@ class PseudoInstantiator : public clang::TreeTransform { return true; } - using lookup_result = clang::DeclContext::lookup_result; - - /// When DeclContext::lookup returns multiple declarations (e.g. a member in - /// both a base class and derived class), take the last one. This heuristic - /// favors the most-derived declaration, though the ordering depends on clang's - /// internal DeclContext storage. - clang::Decl* preferred(lookup_result members) { - clang::Decl* decl = nullptr; - std::ranges::for_each(members, [&](auto member) { decl = member; }); - return decl; - } - - /// Look up `name` in the given type. First transforms the type (to substitute + /// Look up `name` in the given type. First rewrites the type (to substitute /// any template parameters in it), then extracts the ClassTemplateDecl or /// TypeAliasTemplateDecl from the resulting TST/DTST and dispatches to the /// appropriate lookup overload. lookup_result lookup(clang::QualType type, clang::DeclarationName name) { clang::Decl* TD = nullptr; llvm::ArrayRef args; - type = TransformType(type); + type = resolve(type); if(type.isNull()) { return lookup_result(); @@ -534,7 +515,7 @@ class PseudoInstantiator : public clang::TreeTransform { } else if(auto DTST = type->getAs()) { // If this DTST was already resolved (possibly to itself when unresolvable), // skip the redundant lookup. - if(resolved.count(DTST)) { + if(pack_narrowing == 0 && resolved.count(DTST)) { return lookup_result(); } @@ -574,8 +555,10 @@ class PseudoInstantiator : public clang::TreeTransform { return lookup_result(); } - if(auto iter = resolved.find(NNS); iter != resolved.end()) { - return lookup(iter->second, name); + if(pack_narrowing == 0) { + if(auto iter = resolved.find(NNS); iter != resolved.end()) { + return lookup(iter->second, name); + } } // Handle each NestedNameSpecifier kind: @@ -594,7 +577,9 @@ class PseudoInstantiator : public clang::TreeTransform { stack.pop(); } if(!type.isNull()) { - resolved.try_emplace(NNS, type); + if(pack_narrowing == 0 && !truncated && !ctd_guard_tripped) { + resolved.try_emplace(NNS, type); + } return lookup(type, name); } return {}; @@ -604,9 +589,20 @@ class PseudoInstantiator : public clang::TreeTransform { return lookup(clang::QualType(NNS->getAsType(), 0), name); } - case clang::NestedNameSpecifier::Global: - case clang::NestedNameSpecifier::Namespace: - case clang::NestedNameSpecifier::NamespaceAlias: + /// Namespaces and the global scope are ordinary declaration + /// contexts; a plain lookup returns the full overload set. + case clang::NestedNameSpecifier::Namespace: { + return NNS->getAsNamespace()->lookup(name); + } + + case clang::NestedNameSpecifier::NamespaceAlias: { + return NNS->getAsNamespaceAlias()->getNamespace()->lookup(name); + } + + case clang::NestedNameSpecifier::Global: { + return context.getTranslationUnitDecl()->lookup(name); + } + case clang::NestedNameSpecifier::Super: { return {}; } @@ -619,7 +615,7 @@ class PseudoInstantiator : public clang::TreeTransform { /// is substituted (to resolve template params in it) then looked up. /// /// IMPORTANT: when a member is found, stack frames pushed during the lookup - /// are intentionally left intact. The caller (TransformDependentNameType) + /// are intentionally left intact. The caller (resolve_dependent_name) /// needs them to substitute the found decl's underlying type. The caller /// is responsible for popping frames after substitution. lookup_result lookup_in_bases(clang::CXXRecordDecl* CRD, clang::DeclarationName name) { @@ -668,6 +664,9 @@ class PseudoInstantiator : public clang::TreeTransform { // would infinitely recurse through lookup_in_bases. auto ctd_key = std::make_pair(static_cast(CTD), name.getAsOpaquePtr()); if(!active_ctd_lookups.insert(ctd_key).second) { + /// The empty result here means "already in flight", not "absent"; + /// the pseudo-SFINAE probe must not read it as a proof of absence. + ctd_guard_tripped = true; return lookup_result(); } @@ -695,34 +694,64 @@ class PseudoInstantiator : public clang::TreeTransform { name.getAsString(), CTD->getNameAsString(), partials.size()); - ++indent; + indent += 1; /// Deduction alone may match several overlapping partials; pick the - /// most specialized one, as real instantiation would. + /// most specialized one, as real instantiation would — but only among + /// partials whose dependent pattern constraints survive the + /// pseudo-SFINAE probe (see member_absent). clang::ClassTemplatePartialSpecializationDecl* best = nullptr; + llvm::SmallVector matched; for(auto partial: partials) { if(deduce_template_arguments(partial, arguments)) { + bool viable = satisfies_pattern(partial); stack.pop(); - if(!best) { + if(!viable) { + LOG_DEBUG( + "{}" "pruned partial '{}' (member absent)", + pad(), + partial->getNameAsString()); + continue; + } + matched.push_back(partial); + if(!best || more_specialized(context, partial, best)) { best = partial; - } else if(auto* winner = sema.getMoreSpecializedPartialSpecialization( - partial, - best, - clang::SourceLocation())) { - best = winner; } } } + + /// Real instantiation diagnoses ambiguity: if the winner does not + /// dominate every other match, neither a partial nor the primary may + /// be chosen — degrade to unresolved rather than picking arbitrarily. + if(best && matched.size() > 1) { + for(auto partial: matched) { + if(partial != best && !more_specialized(context, best, partial)) { + LOG_DEBUG("{}" "ambiguous partials; degrading", pad()); + indent -= 1; + return lookup_result(); + } + } + } + + /// Constraint satisfaction is not evaluated (`requires false` would + /// need subsumption machinery); a structurally matching constrained + /// partial is therefore unverifiable — degrade rather than trust it. + if(best && best->getTemplateParameters()->hasAssociatedConstraints()) { + LOG_DEBUG("{}" "constrained partial; degrading", pad()); + indent -= 1; + return lookup_result(); + } + if(best && deduce_template_arguments(best, arguments)) { LOG_DEBUG("{}" "matched partial '{}'", pad(), best->getNameAsString()); if(auto members = best->lookup(name); !members.empty()) { LOG_DEBUG("{}" "found in 'partial'", pad()); - --indent; + indent -= 1; return members; } if(auto members = lookup_in_bases(best, name); !members.empty()) { LOG_DEBUG("{}" "found in 'base'", pad()); - --indent; + indent -= 1; return members; } @@ -734,268 +763,1275 @@ class PseudoInstantiator : public clang::TreeTransform { auto CRD = CTD->getTemplatedDecl(); if(auto members = CRD->lookup(name); !members.empty()) { LOG_DEBUG("{}" "found in 'primary'", pad()); - --indent; + indent -= 1; return members; } if(auto members = lookup_in_bases(CRD, name); !members.empty()) { LOG_DEBUG("{}" "found in 'base'", pad()); - --indent; + indent -= 1; return members; } stack.pop(); } - --indent; + indent -= 1; return lookup_result(); } - /// Short-circuit resolution for `std::allocator_traits::rebind_alloc`. - /// - /// libstdc++'s allocator rebind chain (vector → __alloc_traits → allocator_traits → - /// allocator::rebind) creates deeply nested dependent types that are hard to resolve - /// generically. This function intercepts `allocator_traits::rebind_alloc` - /// and attempts direct resolution. - /// - /// Strategy: - /// 1. Try Alloc::rebind::other (the standard allocator rebind protocol) - /// 2. If that fails (e.g. C++20 removed allocator::rebind), fall back to - /// replacing the first template argument: allocator → allocator - /// - /// TODO: Replace with a general mechanism for resolving well-known standard - /// library patterns, or improve the resolver to handle these chains naturally. - clang::QualType hole(clang::NestedNameSpecifier* NNS, - const clang::IdentifierInfo* member, - TemplateArguments arguments) { - if(NNS->getKind() != clang::NestedNameSpecifier::TypeSpec) { - return clang::QualType(); - } - - auto TST = NNS->getAsType()->getAs(); - if(!TST) { - return clang::QualType(); - } - - auto TD = TST->getTemplateName().getAsTemplateDecl(); - if(!TD) - return clang::QualType(); - if(!TD->getDeclContext()->isStdNamespace()) { - return clang::QualType(); - } +private: + /// Per-kind dispatch. Whitelist of type classes the resolver understands; + /// anything else passes through unchanged, which downstream treats as + /// unresolved. Local qualifiers are stripped here and reapplied on the + /// rewritten result. + clang::QualType rewrite_type(clang::QualType type, Policy policy) { + auto quals = type.getLocalQualifiers(); + const clang::Type* T = type.getLocalUnqualifiedType().getTypePtr(); - if(TD->getName() == "allocator_traits") { - if(TST->template_arguments().size() != 1) { - return clang::QualType(); + clang::QualType result; + switch(T->getTypeClass()) { + case clang::Type::TemplateTypeParm: { + result = rewrite_parameter(llvm::cast(T), policy); + break; } - auto Alloc = TST->template_arguments()[0].getAsType(); - if(member->getName() == "rebind_alloc") { - if(arguments.empty()) - return clang::QualType(); - auto T = arguments[0].getAsType(); - - auto prefix = - clang::NestedNameSpecifier::Create(context, nullptr, Alloc.getTypePtr()); - - auto rebind = sema.getPreprocessor().getIdentifierInfo("rebind"); + /// Sugar nodes: rewrite what they point at; the wrapper is dropped, + /// which is fine because consumers compare canonically or look + /// through sugar. + case clang::Type::Elaborated: { + result = rewrite(llvm::cast(T)->getNamedType(), policy); + break; + } + case clang::Type::Paren: { + result = rewrite(llvm::cast(T)->getInnerType(), policy); + break; + } + case clang::Type::Using: { + result = rewrite(llvm::cast(T)->getUnderlyingType(), policy); + break; + } + case clang::Type::MacroQualified: { + result = + rewrite(llvm::cast(T)->getUnderlyingType(), policy); + break; + } + case clang::Type::SubstTemplateTypeParm: { + result = + rewrite(llvm::cast(T)->getReplacementType(), + policy); + break; + } - auto DTST = context.getDependentTemplateSpecializationType( - clang::ElaboratedTypeKeyword::None, - clang::DependentTemplateStorage(prefix, rebind, false), - arguments); + /// Dependent typedefs expand under Policy::Substitute regardless of + /// the current policy — the invariant that breaks typedef ↔ lookup + /// cycles lives on this single line. + case clang::Type::Typedef: { + auto TND = llvm::cast(T)->getDecl(); + auto underlying = TND->getUnderlyingType(); + if(underlying->isDependentType()) { + result = substitute(underlying); + } + break; + } - prefix = clang::NestedNameSpecifier::Create(context, prefix, DTST.getTypePtr()); + case clang::Type::InjectedClassName: { + auto ICT = llvm::cast(T); + result = rewrite(ICT->getInjectedSpecializationType(), policy); + break; + } - auto other = sema.getPreprocessor().getIdentifierInfo("other"); - /// Keyword must stay None: the synthesized type is transformed - /// through a trivial TypeSourceInfo whose KeywordLoc is invalid, - /// and Sema::CheckTypenameType asserts (Keyword != None) == - /// KeywordLoc.isValid(). - auto DNT = - context.getDependentNameType(clang::ElaboratedTypeKeyword::None, prefix, other); + case clang::Type::TemplateSpecialization: { + result = rewrite_template(llvm::cast(T), policy); + break; + } - auto result = PseudoInstantiator(sema, resolved, indent).TransformType(DNT); - if(!result.isNull() && !result->isDependentType()) { - LOG_DEBUG( - "{}" "hole: 'allocator_traits::rebind_alloc' → '{}'", - pad(), - result.getAsString()); - return result; + case clang::Type::DependentName: { + auto DNT = llvm::cast(T); + if(policy == Policy::Resolve) { + result = resolve_dependent_name(DNT); + } else { + auto NNS = rewrite_specifier(DNT->getQualifier(), policy); + if(NNS != DNT->getQualifier()) { + result = context.getDependentNameType( + DNT->getKeyword(), + const_cast(NNS), + DNT->getIdentifier()); + } } + break; + } - if(auto TST = Alloc->getAs()) { - llvm::SmallVector replaceArguments = {T}; - llvm::SmallVector canonicalArguments; - for(auto& arg: replaceArguments) { - canonicalArguments.emplace_back(context.getCanonicalTemplateArgument(arg)); + case clang::Type::DependentTemplateSpecialization: { + auto DTST = llvm::cast(T); + if(policy == Policy::Resolve) { + result = resolve_dependent_template(DTST); + } else { + auto& template_name = DTST->getDependentTemplateName(); + auto NNS = rewrite_specifier(template_name.getQualifier(), policy); + llvm::SmallVector arguments; + bool changed = rewrite_arguments(DTST->template_arguments(), arguments, policy); + if(NNS != template_name.getQualifier() || changed) { + result = context.getDependentTemplateSpecializationType( + DTST->getKeyword(), + clang::DependentTemplateStorage( + const_cast(NNS), + template_name.getName(), + template_name.hasTemplateKeyword()), + arguments); } - auto result = context.getTemplateSpecializationType(TST->getTemplateName(), - replaceArguments, - canonicalArguments); - LOG_DEBUG( - "{}" "hole: 'allocator_traits::rebind_alloc' → '{}'", - pad(), - result.getAsString()); - return result; } + break; } - } - - return clang::QualType(); - } -public: - using Base::TransformType; - - /// Entry point for all type transformations. Guards against: - /// - Null types (return as-is) - /// - Non-dependent types (no transformation needed) - /// - Excessive recursion depth (bail out to prevent stack overflow) - /// - Null results from base transform (return original type instead) - clang::QualType TransformType(clang::QualType type) { - if(type.isNull() || !type->isDependentType()) { - return type; - } - if(depth > 16) { - return type; - } - ++depth; - auto result = Base::TransformType(type); - --depth; - if(result.isNull()) { - return type; - } - return result; - } + case clang::Type::Pointer: { + auto pointee = rewrite(llvm::cast(T)->getPointeeType(), policy); + /// A substituted reference pointee makes the pointer + /// ill-formed (`P::type` with `type = T*`); degrade + /// rather than fabricate a malformed node. + if(pointee->isReferenceType()) { + break; + } + result = context.getPointerType(pointee); + break; + } - clang::QualType TransformTemplateTypeParmType(clang::TypeLocBuilder& TLB, - clang::TemplateTypeParmTypeLoc TL, - bool = false) { - auto* T = TL.getTypePtr(); + /// Substituted reference pointees collapse per the language + /// rules: `&` wins over anything, `&& &&` stays `&&`. Pointees a + /// reference cannot bind to (`void`, qualified function types) + /// degrade instead of fabricating an invalid node. + case clang::Type::LValueReference: { + auto pointee = + rewrite(llvm::cast(T)->getPointeeType(), policy); + if(!pointee.getNonReferenceType().isReferenceable()) { + break; + } + result = context.getLValueReferenceType(pointee.getNonReferenceType()); + break; + } - // First, try to find a substitution in the instantiation stack. - if(auto* arg = stack.find_argument(T)) { - clang::QualType type; + case clang::Type::RValueReference: { + auto pointee = + rewrite(llvm::cast(T)->getPointeeType(), policy); + if(!pointee.getNonReferenceType().isReferenceable()) { + break; + } + if(pointee->isLValueReferenceType()) { + result = context.getLValueReferenceType(pointee.getNonReferenceType()); + } else { + result = context.getRValueReferenceType(pointee.getNonReferenceType()); + } + break; + } - if(arg->getKind() == clang::TemplateArgument::Type) { - type = arg->getAsType(); - } else if(arg->getKind() == clang::TemplateArgument::Pack) { - auto pack = arg->getPackAsArray(); - if(pack.size() == 1 && pack[0].getKind() == clang::TemplateArgument::Type) { - type = pack[0].getAsType(); + case clang::Type::Attributed: { + auto AT = llvm::cast(T); + auto modified = rewrite(AT->getModifiedType(), policy); + auto equivalent = rewrite(AT->getEquivalentType(), policy); + if(modified == AT->getModifiedType() && equivalent == AT->getEquivalentType()) { + break; } + result = context.getAttributedType(AT->getAttrKind(), modified, equivalent); + break; } - if(!type.isNull()) { - TLB.pushTrivial(context, type, TL.getNameLoc()); - return type; + case clang::Type::Atomic: { + auto AT = llvm::cast(T); + auto value = rewrite(AT->getValueType(), policy); + if(value == AT->getValueType()) { + break; + } + /// Atomic operands must be unqualified, non-reference object + /// types; anything else degrades. + if(value->isReferenceType() || value->isArrayType() || value->isFunctionType() || + value->isAtomicType() || value.hasQualifiers()) { + break; + } + result = context.getAtomicType(value); + break; } - TLB.push(TL.getType()).setNameLoc(TL.getNameLoc()); - return TL.getType(); - } + case clang::Type::Decayed: { + /// A parameter written as a dependent array or function type + /// stores its adjusted form in a DecayedType; rebuild from + /// the substituted original. + auto DT = llvm::cast(T); + auto original = rewrite(DT->getOriginalType(), policy); + if(original == DT->getOriginalType()) { + break; + } + result = context.getAdjustedParameterType(original); + break; + } + + case clang::Type::UnaryTransform: { + auto UTT = llvm::cast(T); + auto base = rewrite(UTT->getBaseType(), policy); + if(base == UTT->getBaseType()) { + break; + } + /// A resolved enum operand computes the transform directly. + if(UTT->getUTTKind() == clang::UnaryTransformType::EnumUnderlyingType) { + if(auto ET = base->getAs()) { + if(auto definition = ET->getDecl()->getDefinition()) { + result = definition->getIntegerType(); + break; + } + } + } + /// Rebuilding with a non-dependent operand would fabricate a + /// node whose canonical type is the operand itself — a wrong + /// answer for every transform (Sema computes the result type + /// and we do not, except for the enum case above). Degrade to + /// the unrewritten node instead. + if(!base->isDependentType()) { + break; + } + result = context.getUnaryTransformType(base, base, UTT->getUTTKind()); + break; + } + + case clang::Type::MemberPointer: { + auto MPT = llvm::cast(T); + auto cls = MPT->getQualifier() ? MPT->getQualifier()->getAsType() : nullptr; + if(!cls) { + break; + } + auto pointee = rewrite(MPT->getPointeeType(), policy); + auto rewritten_cls = rewrite(clang::QualType(cls, 0), policy); + if(pointee == MPT->getPointeeType() && rewritten_cls.getTypePtr() == cls) { + break; + } + /// Pointers to reference or void members do not exist, and + /// the owner must stay a class type (or still-dependent); + /// degrade. + if(pointee->isReferenceType() || pointee->isVoidType()) { + break; + } + if(!rewritten_cls->isDependentType() && !rewritten_cls->isRecordType()) { + break; + } + auto qualifier = clang::NestedNameSpecifier::Create(context, + nullptr, + rewritten_cls.getTypePtr()); + result = context.getMemberPointerType(pointee, + qualifier, + rewritten_cls->getAsCXXRecordDecl()); + break; + } + + case clang::Type::PackExpansion: { + auto PET = llvm::cast(T); + auto pattern = rewrite(PET->getPattern(), policy); + if(pattern == PET->getPattern()) { + break; + } + if(pattern->containsUnexpandedParameterPack()) { + result = context.getPackExpansionType(pattern, PET->getNumExpansions()); + } else { + /// The pack was substituted with a concrete (single) + /// argument; the expansion collapses to it. + result = pattern; + } + break; + } + + /// Attempt to resolve decltype expressions that reference variables. + /// Only handles the simple case of `decltype(var)` where `var` is a VarDecl. + /// TODO: Handle more complex decltype expressions (member access, function calls). + case clang::Type::Decltype: { + auto expr = llvm::cast(T)->getUnderlyingExpr(); + if(auto DRE = llvm::dyn_cast(expr)) { + if(auto decl = DRE->getDecl(); llvm::isa(decl)) { + result = rewrite(decl->getType(), policy); + } + } + break; + } + + case clang::Type::DependentSizedArray: { + auto DSAT = llvm::cast(T); + auto element = rewrite(DSAT->getElementType(), policy); + /// Array elements must be object types (no references, + /// void, or function types); degrade otherwise. + if(element->isReferenceType() || element->isVoidType() || + element->isFunctionType()) { + break; + } + + /// `T[N]` with a known N collapses to a constant array; a + /// bound that is itself a dependent expression (`N = M` from + /// deduction) is threaded through instead of left stale. + if(auto NTTP = referenced_nttp(DSAT->getSizeExpr())) { + auto* argument = stack.find_argument(NTTP, NTTP->getDepth(), NTTP->getIndex()); + if(argument && argument->getKind() == clang::TemplateArgument::Integral) { + /// A negative bound is ill-formed; fabricating an + /// array from its two's-complement bits would be a + /// wrong answer. Degrade. + if(argument->getAsIntegral().isNegative()) { + break; + } + result = context.getConstantArrayType(element, + argument->getAsIntegral(), + nullptr, + DSAT->getSizeModifier(), + DSAT->getIndexTypeCVRQualifiers()); + break; + } + if(argument && argument->getKind() == clang::TemplateArgument::Expression) { + result = + context.getDependentSizedArrayType(element, + argument->getAsExpr(), + DSAT->getSizeModifier(), + DSAT->getIndexTypeCVRQualifiers()); + break; + } + } + + if(element != DSAT->getElementType()) { + result = context.getDependentSizedArrayType(element, + DSAT->getSizeExpr(), + DSAT->getSizeModifier(), + DSAT->getIndexTypeCVRQualifiers()); + } + break; + } + + case clang::Type::ConstantArray: { + auto CAT = llvm::cast(T); + auto element = rewrite(CAT->getElementType(), policy); + if(element->isReferenceType() || element->isVoidType() || + element->isFunctionType()) { + break; + } + if(element != CAT->getElementType()) { + result = context.getConstantArrayType(element, + CAT->getSize(), + CAT->getSizeExpr(), + CAT->getSizeModifier(), + CAT->getIndexTypeCVRQualifiers()); + } + break; + } + + case clang::Type::IncompleteArray: { + auto IAT = llvm::cast(T); + auto element = rewrite(IAT->getElementType(), policy); + if(element->isReferenceType() || element->isVoidType() || + element->isFunctionType()) { + break; + } + if(element != IAT->getElementType()) { + result = context.getIncompleteArrayType(element, + IAT->getSizeModifier(), + IAT->getIndexTypeCVRQualifiers()); + } + break; + } + + case clang::Type::FunctionProto: { + auto FPT = llvm::cast(T); + auto ret = rewrite(FPT->getReturnType(), policy); + llvm::SmallVector params; + bool changed = ret != FPT->getReturnType(); + bool representable = true; + for(auto param: FPT->getParamTypes()) { + /// A parameter pack (`void(Ts...)`) splices its bound + /// elements into the parameter list. + if(auto PET = param->getAs()) { + auto splice = [&](llvm::ArrayRef elements) { + for(auto& element: elements) { + if(element.getKind() != clang::TemplateArgument::Type || + element.getAsType()->isVoidType()) { + representable = false; + return; + } + params.push_back( + context.getAdjustedParameterType(element.getAsType())); + } + changed = true; + }; + + auto pattern = PET->getPattern(); + const clang::TemplateArgument* bound = nullptr; + if(auto TTPT = pattern->getAs()) { + bound = stack.find_argument(TTPT); + } + if(bound && bound->getKind() == clang::TemplateArgument::Pack) { + splice(bound->getPackAsArray()); + continue; + } + if(auto expanded = expand_pack_pattern(pattern, policy)) { + splice(*expanded); + continue; + } + } + + auto rewritten = rewrite(param, policy); + changed |= rewritten != param; + /// Parameters decay (`void(T)` with `T = int[2]` is + /// `void(int*)`), so a substituted type must be adjusted + /// before it enters the prototype; a substituted `void` + /// parameter cannot exist and degrades. + if(rewritten->isVoidType()) { + representable = false; + break; + } + params.push_back(context.getAdjustedParameterType(rewritten)); + } + if(!representable) { + break; + } + /// Functions cannot return arrays or functions; degrade. + if(ret->isArrayType() || ret->isFunctionType()) { + break; + } + + auto EPI = FPT->getExtProtoInfo(); + + /// `noexcept(B)` with a bound bare parameter substitutes at + /// the specification level: an expression binding replaces + /// the operand whole, a value binding selects the concrete + /// specification — no expression is ever rebuilt. + if(FPT->getExceptionSpecType() == clang::EST_DependentNoexcept) { + if(auto NTTP = referenced_nttp(FPT->getNoexceptExpr())) { + auto* bound = stack.find_argument(NTTP, NTTP->getDepth(), NTTP->getIndex()); + if(bound && bound->getKind() == clang::TemplateArgument::Integral) { + EPI.ExceptionSpec = {}; + EPI.ExceptionSpec.Type = bound->getAsIntegral().getBoolValue() + ? clang::EST_BasicNoexcept + : clang::EST_None; + changed = true; + } else if(bound && + bound->getKind() == clang::TemplateArgument::Expression) { + EPI.ExceptionSpec.NoexceptExpr = bound->getAsExpr(); + changed = true; + } + } + } + + if(changed) { + /// Splicing changes the arity; per-parameter ABI info + /// cannot be carried over. + if(params.size() != FPT->getNumParams()) { + EPI.ExtParameterInfos = nullptr; + } + result = context.getFunctionType(ret, params, EPI); + } + break; + } + + default: { + break; + } + } + + if(result.isNull()) { + return clang::QualType(); + } + /// cv-qualifiers applied through substitution are ignored when the + /// substituted type is a reference or function type (`const T` with + /// `T = X&` is just `X&`). + if(quals.hasQualifiers() && !result->isReferenceType() && !result->isFunctionType()) { + result = context.getQualifiedType(result, quals); + } + return result; + } + + clang::QualType rewrite_parameter(const clang::TemplateTypeParmType* TTPT, Policy policy) { + // First, try to find a substitution in the instantiation stack. + if(auto* argument = stack.find_argument(TTPT)) { + clang::QualType type; + + if(argument->getKind() == clang::TemplateArgument::Type) { + type = argument->getAsType(); + } else if(argument->getKind() == clang::TemplateArgument::Pack) { + auto pack = argument->getPackAsArray(); + if(pack.size() == 1 && pack[0].getKind() == clang::TemplateArgument::Type) { + type = pack[0].getAsType(); + } + /// Multi-element packs are spliced at the template argument + /// list level (rewrite_arguments); a bare parameter cannot + /// stand for several types at once. + } + + return type; + } // No stack substitution available. Fall back to using the parameter's // default argument if one exists. This enables resolution chains like: // template> struct vector; // where Alloc's default depends on T. - if(clang::TemplateTypeParmDecl* TTPD = TL.getDecl()) { - if(TTPD->hasDefaultArgument()) { - const clang::TemplateArgument& argument = TTPD->getDefaultArgument().getArgument(); + if(policy == Policy::Resolve) { + if(clang::TemplateTypeParmDecl* TTPD = TTPT->getDecl(); + TTPD && TTPD->hasDefaultArgument()) { + const auto& argument = TTPD->getDefaultArgument().getArgument(); if(argument.getKind() == clang::TemplateArgument::Type) { - clang::QualType type = TransformType(argument.getAsType()); - if(!type.isNull()) { - TLB.pushTrivial(context, type, getBaseLocation()); - return type; + return rewrite(argument.getAsType(), policy); + } + } + } + + return clang::QualType(); + } + + /// Build a template specialization type from as-written (flat) arguments. + /// + /// The canonical argument list must mirror Sema's argument conversion or + /// the produced type would never compare equal to a parsed `X<...>`: the + /// trailing arguments of a parameter pack are grouped into a single Pack + /// argument, while the specified list stays flat as written. + clang::QualType make_specialization(clang::TemplateName name, TemplateArguments arguments) { + /// A template template parameter can be bound to a dependent template + /// name (libc++ binds `_Alloc::template rebind` this way). A + /// TemplateSpecializationType cannot carry those (clang asserts); + /// rebuild the dependent form with the substituted arguments instead. + if(auto dependent = name.getAsDependentTemplateName()) { + return context.getDependentTemplateSpecializationType( + clang::ElaboratedTypeKeyword::None, + *dependent, + arguments); + } + + llvm::SmallVector canonical; + + /// A head bound through a template template parameter may carry an + /// argument list the parameters cannot accept — too few (defaulted + /// trailing parameters must be filled or the canonical list never + /// compares equal to a parsed `target`) or too many (rebuilding + /// would fabricate an invalid specialization; degrade instead). + llvm::SmallVector full; + clang::TemplateParameterList* params = nullptr; + if(auto TD = name.getAsTemplateDecl()) { + params = TD->getTemplateParameters(); + if(!check_template_arguments(TD, arguments, full)) { + return clang::QualType(); + } + arguments = full; + } + + unsigned i = 0; + if(params) { + for(auto param: *params) { + if(param->isTemplateParameterPack()) { + if(arguments.size() - i == 1 && + arguments[i].getKind() == clang::TemplateArgument::Pack) { + /// Already grouped by deduction. + canonical.emplace_back(context.getCanonicalTemplateArgument(arguments[i])); + i += 1; + } else { + llvm::SmallVector pack; + for(; i < arguments.size(); i += 1) { + pack.emplace_back(context.getCanonicalTemplateArgument(arguments[i])); + } + canonical.emplace_back( + clang::TemplateArgument::CreatePackCopy(context, pack)); + } + break; + } + if(i >= arguments.size()) { + break; + } + canonical.emplace_back(context.getCanonicalTemplateArgument(arguments[i])); + i += 1; + } + } + for(; i < arguments.size(); i += 1) { + canonical.emplace_back(context.getCanonicalTemplateArgument(arguments[i])); + } + + /// Fully concrete results should compare equal to the same type + /// written in source, whose canonical form is the specialization + /// decl's record type. findSpecialization is a read-only registry + /// query — if the TU never named this specialization, we keep the + /// bare canonical TST rather than fabricating a declaration. + clang::QualType underlying; + bool concrete = std::ranges::none_of(canonical, [](const clang::TemplateArgument& arg) { + return arg.isDependent(); + }); + if(concrete) { + if(auto CTD = + llvm::dyn_cast_or_null(name.getAsTemplateDecl())) { + void* pos = nullptr; + if(auto CTSD = CTD->findSpecialization(canonical, pos)) { + underlying = context.getTypeDeclType(CTSD); + } + } + } + + /// An alias specialization must carry its aliased type unless the + /// arguments still hold unexpanded packs (clang asserts on this). + /// This arises when a template template parameter got substituted + /// with an alias template: the head is ours, so the aliasing is ours + /// to compute. Failure degrades to an unrewritten (null) result. + if(auto TATD = + llvm::dyn_cast_or_null(name.getAsTemplateDecl())) { + bool expansions = + std::ranges::any_of(canonical, [](const clang::TemplateArgument& arg) { + return arg.isPackExpansion(); + }); + if(!expansions) { + clang::QualType aliased; + if(deduce_template_arguments(TATD, arguments)) { + aliased = substitute(TATD->getTemplatedDecl()->getUnderlyingType()); + stack.pop(); + } + if(aliased.isNull()) { + return clang::QualType(); + } + return context.getTemplateSpecializationType(name, arguments, canonical, aliased); + } + } + + return context.getTemplateSpecializationType(name, arguments, canonical, underlying); + } + + clang::QualType rewrite_template(const clang::TemplateSpecializationType* TST, Policy policy) { + /// Alias specializations carry the substituted underlying type as + /// sugar; expanding it is substitution, not lookup, so it is safe + /// under both policies. + if(TST->isTypeAlias()) { + return rewrite(TST->desugar(), policy); + } + + /// A bound template template parameter in the head is substituted + /// with its deduced template, e.g. `TT` after matching + /// `replace_first, U>` against `box`. + auto name = TST->getTemplateName(); + bool head_changed = false; + if(auto TTP = + llvm::dyn_cast_or_null(name.getAsTemplateDecl())) { + if(auto* bound = stack.find_argument(TTP, TTP->getDepth(), TTP->getIndex()); + bound && bound->getKind() == clang::TemplateArgument::Template) { + name = bound->getAsTemplate(); + head_changed = true; + } + } + + llvm::SmallVector arguments; + bool args_changed = rewrite_arguments(TST->template_arguments(), arguments, policy); + if(!head_changed && !args_changed) { + return clang::QualType(); + } + + return make_specialization(name, arguments); + } + + /// Rewrite a template argument list. Returns true if anything changed. + /// Pack expansions whose pattern is a bound pack parameter are spliced + /// inline, so `type_list` with `Us = {int, float}` becomes + /// `type_list`. + bool rewrite_arguments(TemplateArguments arguments, + llvm::SmallVectorImpl& out, + Policy policy) { + bool changed = false; + + for(auto& argument: arguments) { + switch(argument.getKind()) { + case clang::TemplateArgument::Type: { + auto type = argument.getAsType(); + + if(auto PET = type->getAs()) { + auto pattern = PET->getPattern(); + if(auto TTPT = pattern->getAs()) { + auto* bound = stack.find_argument(TTPT); + if(bound && bound->getKind() == clang::TemplateArgument::Pack) { + out.append(bound->pack_begin(), bound->pack_end()); + changed = true; + continue; + } + } + + /// A pattern that merely contains bound packs + /// (`box...`) expands element-wise: rewrite it + /// once per element with every referenced pack + /// temporarily narrowed to its k-th element. Packs + /// expand in lockstep, so their lengths must agree. + if(auto expanded = expand_pack_pattern(pattern, policy)) { + out.append(expanded->begin(), expanded->end()); + changed = true; + continue; + } + + auto rewritten = rewrite(pattern, policy); + if(rewritten != pattern) { + changed = true; + if(rewritten->containsUnexpandedParameterPack()) { + rewritten = context.getPackExpansionType(rewritten, + PET->getNumExpansions()); + } + out.emplace_back(rewritten); + } else { + out.push_back(argument); + } + continue; + } + + auto rewritten = rewrite(type, policy); + changed |= rewritten != type; + out.emplace_back(rewritten); + break; + } + + case clang::TemplateArgument::Expression: { + /// An expression pack expansion (`Ns...`) splices its + /// bound pack, mirroring the type-pack path above. + if(auto PEE = llvm::dyn_cast(argument.getAsExpr())) { + if(auto NTTP = referenced_nttp(PEE->getPattern())) { + auto* bound = + stack.find_argument(NTTP, NTTP->getDepth(), NTTP->getIndex()); + if(bound && bound->getKind() == clang::TemplateArgument::Pack) { + out.append(bound->pack_begin(), bound->pack_end()); + changed = true; + continue; + } + } + } + + /// Substitute a bound non-type parameter at the argument + /// level; expressions themselves are never rebuilt. + /// TODO(nttp-expr): compound expressions (`value`) + /// therefore keep referencing the original parameter and + /// degrade to unresolved downstream — never a wrong + /// answer, never a crash. Rebuilding them needs an + /// expression substitution engine; deliberately deferred. + /// A Pack binding is only usable inside a narrowed element + /// rewrite (where the slot holds one element) — splicing + /// it here as a single argument would be malformed. + if(auto NTTP = referenced_nttp(argument.getAsExpr())) { + auto* bound = stack.find_argument(NTTP, NTTP->getDepth(), NTTP->getIndex()); + if(bound && !bound->isNull() && + bound->getKind() != clang::TemplateArgument::Pack) { + out.push_back(*bound); + changed = true; + continue; + } + } + out.push_back(argument); + break; + } + + case clang::TemplateArgument::Template: { + /// A template template parameter forwarded as an argument + /// (`apply`) is substituted with its binding. + if(auto TTP = llvm::dyn_cast_or_null( + argument.getAsTemplate().getAsTemplateDecl())) { + auto* bound = stack.find_argument(TTP, TTP->getDepth(), TTP->getIndex()); + if(bound && bound->getKind() == clang::TemplateArgument::Template) { + out.push_back(*bound); + changed = true; + continue; + } + } + + /// A dependent name (`apply`) carries + /// its qualifier inside the TemplateName; rewrite it so + /// the frame's bindings do not go stale. + if(auto dependent = argument.getAsTemplate().getAsDependentTemplateName()) { + auto qualifier = rewrite_specifier(dependent->getQualifier(), policy); + if(qualifier != dependent->getQualifier()) { + auto name = + context.getDependentTemplateName(clang::DependentTemplateStorage( + const_cast(qualifier), + dependent->getName(), + dependent->hasTemplateKeyword())); + out.emplace_back(name); + changed = true; + continue; + } + } + out.push_back(argument); + break; + } + + case clang::TemplateArgument::TemplateExpansion: { + /// `Fs...` splices its bound template pack, symmetric + /// with the type and expression pack paths. + if(auto TTP = llvm::dyn_cast_or_null( + argument.getAsTemplateOrTemplatePattern().getAsTemplateDecl())) { + auto* bound = stack.find_argument(TTP, TTP->getDepth(), TTP->getIndex()); + if(bound && bound->getKind() == clang::TemplateArgument::Pack) { + out.append(bound->pack_begin(), bound->pack_end()); + changed = true; + continue; + } + } + out.push_back(argument); + break; + } + + default: { + out.push_back(argument); + break; + } + } + } + + return changed; + } + + /// Element-wise expansion of a structured pack pattern: if every pack + /// parameter referenced in `pattern` is bound to a Pack of one common + /// length, rewrite the pattern once per element and return the list. + /// Returns nullopt when the pattern has no bound packs, lengths disagree, + /// or an element fails to rewrite cleanly — callers then fall back to + /// rewriting the pattern as a whole. + std::optional> + expand_pack_pattern(clang::QualType pattern, Policy policy) { + PackParmCollector collector; + collector.TraverseType(pattern); + if(collector.packs.empty() && collector.template_packs.empty() && + collector.value_packs.empty()) { + return std::nullopt; + } + + llvm::SmallVector slots; + unsigned length = 0; + auto admit = [&](std::optional handle) { + if(!handle) { + return false; + } + auto& bound = stack.slot(*handle); + if(bound.getKind() != clang::TemplateArgument::Pack) { + return false; + } + if(!slots.empty() && bound.pack_size() != length) { + return false; + } + length = bound.pack_size(); + if(!llvm::is_contained(slots, *handle)) { + slots.push_back(*handle); + } + return true; + }; + + for(auto TTPT: collector.packs) { + if(!admit(stack.find_slot(TTPT->getDecl(), TTPT->getDepth(), TTPT->getIndex()))) { + return std::nullopt; + } + } + for(auto TTP: collector.template_packs) { + if(!admit(stack.find_slot(TTP, TTP->getDepth(), TTP->getIndex()))) { + return std::nullopt; + } + } + for(auto NTTP: collector.value_packs) { + if(!admit(stack.find_slot(NTTP, NTTP->getDepth(), NTTP->getIndex()))) { + return std::nullopt; + } + } + + llvm::SmallVector saved( + llvm::map_range(slots, [&](auto handle) { return stack.slot(handle); })); + + /// While narrowed, node-pointer-keyed caches are bypassed: the same + /// pattern node resolves differently per element, and a cached first + /// element would silently repeat for every later one. + pack_narrowing += 1; + bool ok = true; + llvm::SmallVector expanded; + for(unsigned k = 0; k < length && ok; k += 1) { + for(auto [handle, pack]: llvm::zip(slots, saved)) { + stack.slot(handle) = pack.pack_begin()[k]; + } + auto element = rewrite(pattern, policy); + ok = element != pattern && !element->containsUnexpandedParameterPack(); + if(ok) { + expanded.emplace_back(element); + } + } + pack_narrowing -= 1; + + for(auto [handle, pack]: llvm::zip(slots, saved)) { + stack.slot(handle) = pack; + } + + if(!ok) { + return std::nullopt; + } + return expanded; + } + + const clang::NestedNameSpecifier* rewrite_specifier(const clang::NestedNameSpecifier* NNS, + Policy policy) { + if(!NNS) { + return nullptr; + } + + switch(NNS->getKind()) { + case clang::NestedNameSpecifier::TypeSpec: { + auto prefix = rewrite_specifier(NNS->getPrefix(), policy); + + /// A dependent component written as `prefix::template B` keeps + /// its qualifier in the specifier chain, not in the type node + /// itself; resolve it in the scope of the rewritten prefix. + clang::QualType type; + auto component = clang::QualType(NNS->getAsType(), 0); + auto DTST = + llvm::dyn_cast(NNS->getAsType()); + if(DTST && !DTST->getDependentTemplateName().getQualifier() && + policy == Policy::Resolve) { + type = resolve_dependent_template(DTST, prefix); + } else { + type = rewrite(component, policy); + } + + if(prefix == NNS->getPrefix() && type.getTypePtr() == NNS->getAsType()) { + return NNS; + } + return clang::NestedNameSpecifier::Create( + context, + const_cast(prefix), + type.getTypePtr()); + } + + /// Identifier components are resolved by lookup itself; the prefix + /// may still contain substitutable types. + case clang::NestedNameSpecifier::Identifier: { + auto prefix = rewrite_specifier(NNS->getPrefix(), policy); + if(prefix == NNS->getPrefix()) { + return NNS; + } + return clang::NestedNameSpecifier::Create( + context, + const_cast(prefix), + NNS->getAsIdentifier()); + } + + default: { + return NNS; + } + } + } + + /// Pseudo-SFINAE: decide whether a partial specialization's dependent + /// pattern constraints (e.g. the `void_t::other>` + /// idiom) are satisfiable under the current bindings. + /// + /// Real SFINAE substitutes and rejects on ill-formedness. We approximate + /// with a three-way probe on each dependent member access in the pattern: + /// - member resolves → constraint holds, keep the partial + /// - qualifier resolves to a known template/record but the member does + /// not exist there → constraint provably fails, prune the partial + /// - qualifier unknown (bare parameter etc.) → benefit of the doubt, + /// keep the partial; never guess a concrete answer from uncertainty + bool satisfies_pattern(clang::ClassTemplatePartialSpecializationDecl* partial) { + if(probing > 4) { + return true; + } + + /// The probe expression only survives in the as-written arguments: + /// the converted list has already desugared `void_t<...>` to `void`. + auto written = partial->getTemplateArgsAsWritten(); + if(!written) { + return true; + } + + probing += 1; + bool viable = true; + for(const clang::TemplateArgumentLoc& loc: written->arguments()) { + auto& argument = loc.getArgument(); + if(argument.getKind() == clang::TemplateArgument::Type && + member_absent(argument.getAsType(), 0)) { + viable = false; + break; + } + } + probing -= 1; + return viable; + } + + /// Walk the written form of `type` (through alias sugar arguments, which + /// is where `void_t` hides its probe expression) and report whether any + /// dependent member access provably names a non-existent member. + bool member_absent(clang::QualType type, unsigned guard) { + /// Note: `void_t` canonically IS `void`, so this must test + /// instantiation dependence, not type dependence. + if(type.isNull() || guard > 16 || !type->isInstantiationDependentType()) { + return false; + } + + const clang::Type* T = type.getLocalUnqualifiedType().getTypePtr(); + switch(T->getTypeClass()) { + case clang::Type::DependentName: { + auto DNT = llvm::cast(T); + return specifier_absent(DNT->getQualifier(), guard) || + scope_lacks(DNT->getQualifier(), + DNT->getIdentifier(), + /*wants_template=*/false); + } + + case clang::Type::DependentTemplateSpecialization: { + auto DTST = llvm::cast(T); + auto& template_name = DTST->getDependentTemplateName(); + auto identifier = template_name.getName().getIdentifier(); + auto qualifier = template_name.getQualifier(); + if(specifier_absent(qualifier, guard)) { + return true; + } + return identifier && scope_lacks(qualifier, identifier, /*wants_template=*/true); + } + + case clang::Type::TemplateSpecialization: { + auto TST = llvm::cast(T); + /// Check the arguments as written: alias sugar (`void_t<...>`) + /// desugars to a type that no longer contains the probe. + for(auto& argument: TST->template_arguments()) { + if(argument.getKind() == clang::TemplateArgument::Type && + member_absent(argument.getAsType(), guard + 1)) { + return true; + } + } + return false; + } + + case clang::Type::Elaborated: { + return member_absent(llvm::cast(T)->getNamedType(), + guard + 1); + } + case clang::Type::Paren: { + return member_absent(llvm::cast(T)->getInnerType(), guard + 1); + } + case clang::Type::Pointer: { + return member_absent(llvm::cast(T)->getPointeeType(), + guard + 1); + } + case clang::Type::LValueReference: + case clang::Type::RValueReference: { + return member_absent(llvm::cast(T)->getPointeeType(), + guard + 1); + } + case clang::Type::PackExpansion: { + return member_absent(llvm::cast(T)->getPattern(), + guard + 1); + } + + default: { + return false; + } + } + } + + /// Does any link of the specifier chain provably name a missing member? + bool specifier_absent(const clang::NestedNameSpecifier* NNS, unsigned guard) { + if(!NNS || guard > 16) { + return false; + } + if(specifier_absent(NNS->getPrefix(), guard + 1)) { + return true; + } + + switch(NNS->getKind()) { + case clang::NestedNameSpecifier::Identifier: { + return scope_lacks(NNS->getPrefix(), NNS->getAsIdentifier()); + } + case clang::NestedNameSpecifier::TypeSpec: { + const clang::Type* T = NNS->getAsType(); + if(auto DTST = llvm::dyn_cast(T)) { + auto& template_name = DTST->getDependentTemplateName(); + auto scope = template_name.getQualifier() ? template_name.getQualifier() + : NNS->getPrefix(); + auto identifier = template_name.getName().getIdentifier(); + return identifier && scope_lacks(scope, identifier, /*wants_template=*/true); + } + if(auto DNT = llvm::dyn_cast(T)) { + return scope_lacks(DNT->getQualifier(), DNT->getIdentifier()); + } + return false; + } + default: { + return false; + } + } + } + + /// Resolve `scope` and ask whether it is a known template or record that + /// definitely has no member called `name`. Unknown scopes return false. + /// + /// "Definitely" requires a clean verdict: an empty lookup caused by the + /// CTD recursion guard or by step-budget exhaustion proves nothing, and + /// treating it as absence would prune a partial that real SFINAE keeps — + /// a wrong answer, not a degradation. Those cases stay Unknown. + /// Does `record`'s definition inherit from a still-dependent base? Such + /// a base may contribute the probed member only after instantiation, so + /// absence is never conclusive through it. An undefined record proves + /// nothing either. + static bool has_dependent_base(const clang::CXXRecordDecl* record) { + auto definition = record->getDefinition(); + if(!definition) { + return true; + } + return std::ranges::any_of(definition->bases(), [](const clang::CXXBaseSpecifier& base) { + return base.getType()->isDependentType(); + }); + } + + /// Does any specialization of `CTD` — partial or explicit — declare + /// `name`? Used to gate the Absent verdict: if some specialization has + /// the member, an empty lookup on dependent arguments proves nothing. + bool any_specialization_declares(clang::ClassTemplateDecl* CTD, clang::DeclarationName name) { + auto declares = [&](clang::CXXRecordDecl* record) { + auto* definition = record->getDefinition(); + return definition && (!definition->lookup(name).empty() || + !lookup_in_bases(definition, name).empty()); + }; + + llvm::SmallVector partials; + CTD->getPartialSpecializations(partials); + return std::ranges::any_of(partials, declares) || + std::ranges::any_of(CTD->specializations(), declares); + } + + /// Is `decl` provably unusable for the probe's usage form? `typename + /// X::m` cannot name a value; `X::template m<...>` cannot name a value + /// or a plain type. Unknown declaration kinds stay presumed viable. + static bool unusable_member(clang::Decl* decl, bool wants_template) { + if(auto shadow = llvm::dyn_cast(decl)) { + decl = shadow->getTargetDecl(); + } + if(llvm::isa(decl)) { + return true; + } + /// Access participates in SFINAE: the probe models a use from + /// outside the class, where a non-public member never resolves. + /// Friendship of the probing specialization is not modeled — an + /// accepted approximation; detectors befriended by their subjects + /// degrade to the primary. + if(decl->getAccess() == clang::AS_private || decl->getAccess() == clang::AS_protected) { + return true; + } + /// `typename X::m` cannot name an unspecialized template either. + if(!wants_template && llvm::isa(decl)) { + return true; + } + return wants_template && llvm::isa(decl); + } + + bool scope_lacks(const clang::NestedNameSpecifier* scope, + clang::DeclarationName name, + bool wants_template = false) { + if(!scope) { + return false; + } + + /// Empty lookups and lookups whose every candidate is provably + /// unusable both fail the probe conclusively. + auto conclusive = [&](lookup_result members) { + return std::ranges::all_of(members, [&](clang::Decl* decl) { + return unusable_member(decl, wants_template); + }); + }; + + auto stack_size = stack.data.size(); + + /// The flag is save/reset/restored rather than just read: `scope_lacks` + /// reenters itself through lookup's partial probing, and a nested clean + /// probe must not wash out a trip observed by an in-flight outer one. + /// Restoring with `saved || tripped` propagates any trip outwards, so + /// every enclosing probe also stays Unknown. The reset sits before + /// `rewrite_specifier` because resolving the scope prefix can trip the + /// guard too. + bool saved = ctd_guard_tripped; + ctd_guard_tripped = false; + + auto resolved_scope = rewrite_specifier(scope, Policy::Resolve); + + bool lacks = false; + if(resolved_scope && resolved_scope->getKind() == clang::NestedNameSpecifier::TypeSpec) { + auto type = resolve(clang::QualType(resolved_scope->getAsType(), 0)); + if(!type.isNull()) { + if(auto TST = type->getAs()) { + if(auto CTD = llvm::dyn_cast_or_null( + TST->getTemplateName().getAsTemplateDecl())) { + lacks = conclusive(lookup(type, name)); + + /// With dependent arguments the selection above is + /// provisional — a specialization rejected against a + /// bare parameter may apply once it instantiates. An + /// empty result is then conclusive only if no + /// specialization of the template declares the member + /// at all. + if(lacks && TST->isDependentType() && + any_specialization_declares(CTD, name)) { + lacks = false; + } + + /// A dependent base (`struct D : T`) may contribute + /// the member after instantiation. + if(lacks && has_dependent_base(CTD->getTemplatedDecl())) { + lacks = false; + } } + } else if(auto RD = type->getAsCXXRecordDecl()) { + lacks = conclusive(RD->lookup(name)) && conclusive(lookup_in_bases(RD, name)) && + !has_dependent_base(RD); } } } - TLB.push(TL.getType()).setNameLoc(TL.getNameLoc()); - return TL.getType(); + if(ctd_guard_tripped || truncated) { + lacks = false; + } + ctd_guard_tripped = saved || ctd_guard_tripped; + + while(stack.data.size() > stack_size) { + stack.pop(); + } + return lacks; } - clang::QualType TransformDependentNameType(clang::TypeLocBuilder& TLB, - clang::DependentNameTypeLoc TL, - bool DeducedTSTContext = false) { - auto* DNT = TL.getTypePtr(); + clang::QualType resolve_dependent_name(const clang::DependentNameType* DNT) { LOG_DEBUG("{}" "resolve '{}'", pad(), clang::QualType(DNT, 0).getAsString()); - ++indent; + indent += 1; // Check cache. - if(auto iter = resolved.find(DNT); iter != resolved.end()) { - LOG_DEBUG("{}" "→ '{}' (cached)", pad(), iter->second.getAsString()); - --indent; - TLB.pushTrivial(context, iter->second, getBaseLocation()); - return iter->second; + if(pack_narrowing == 0) { + if(auto iter = resolved.find(DNT); iter != resolved.end()) { + LOG_DEBUG("{}" "→ '{}' (cached)", pad(), iter->second.getAsString()); + indent -= 1; + return iter->second; + } } // Cycle detection: if we're already resolving this DNT, bail out. if(!active_resolutions.insert(DNT).second) { LOG_DEBUG("{}→ ", pad()); - --indent; - auto original = clang::QualType(DNT, 0); - auto NewTL = TLB.push(original); - NewTL.setElaboratedKeywordLoc(TL.getElaboratedKeywordLoc()); - NewTL.setQualifierLoc(TL.getQualifierLoc()); - NewTL.setNameLoc(TL.getNameLoc()); - return original; - } - - auto NNSLoc = TransformNestedNameSpecifierLoc(TL.getQualifierLoc()); - if(!NNSLoc) { - active_resolutions.erase(DNT); - LOG_DEBUG("{}→ ", pad()); - --indent; - auto original = clang::QualType(DNT, 0); - auto NewTL = TLB.push(original); - NewTL.setElaboratedKeywordLoc(TL.getElaboratedKeywordLoc()); - NewTL.setQualifierLoc(TL.getQualifierLoc()); - NewTL.setNameLoc(TL.getNameLoc()); - return original; - } - - auto* NNS = NNSLoc.getNestedNameSpecifier(); + indent -= 1; + return clang::QualType(DNT, 0); + } + + auto* NNS = rewrite_specifier(DNT->getQualifier(), Policy::Resolve); auto stack_size = stack.data.size(); auto* decl = preferred(lookup(NNS, DNT->getIdentifier())); auto type = get_decl_type(decl); clang::QualType result; if(!type.isNull()) { - if(decl) { - const char* decl_kind = "decl"; - if(llvm::isa(decl)) - decl_kind = "typedef"; - else if(llvm::isa(decl)) - decl_kind = "record"; - auto decl_name = llvm::dyn_cast(decl) - ? llvm::dyn_cast(decl)->getNameAsString() - : "?"; - LOG_DEBUG( - "{}" "found {} '{}' = '{}'", - pad(), - decl_kind, - decl_name, - type.getAsString()); - } + const char* decl_kind = "decl"; + if(llvm::isa(decl)) + decl_kind = "typedef"; + else if(llvm::isa(decl)) + decl_kind = "record"; + auto decl_name = llvm::dyn_cast(decl) + ? llvm::dyn_cast(decl)->getNameAsString() + : "?"; + LOG_DEBUG("{}" "found {} '{}' = '{}'", pad(), decl_kind, decl_name, type.getAsString()); // Step 1: substitute params (expand typedefs, no lookup). result = substitute(type); LOG_DEBUG("{}" "substitute → '{}'", pad(), result.getAsString()); // Pop lookup frames BEFORE further resolution. The substitute step already - // used the full stack for parameter substitution. TransformType should only + // used the full stack for parameter substitution. Resolution should only // see the outer context to avoid polluting free variables (e.g. T) with // mappings from intermediate lookup frames. while(stack.data.size() > stack_size) { stack.pop(); } - // Step 2: if still dependent, do full transform (may trigger more lookups). + // Step 2: if still dependent, do full resolution (may trigger more lookups). if(!result.isNull() && result->isDependentType()) { - result = TransformType(result); + result = rewrite(result, Policy::Resolve); } } else { while(stack.data.size() > stack_size) { @@ -1007,84 +2043,52 @@ class PseudoInstantiator : public clang::TreeTransform { if(!result.isNull()) { LOG_DEBUG("{}" "→ '{}'", pad(), result.getAsString()); - --indent; - resolved.try_emplace(DNT, result); - TLB.pushTrivial(context, result, getBaseLocation()); + indent -= 1; + if(pack_narrowing == 0 && !truncated && !ctd_guard_tripped) { + resolved.try_emplace(DNT, result); + } return result; } LOG_DEBUG("{}→ ", pad()); - --indent; - auto original = clang::QualType(DNT, 0); - auto NewTL = TLB.push(original); - NewTL.setElaboratedKeywordLoc(TL.getElaboratedKeywordLoc()); - NewTL.setQualifierLoc(TL.getQualifierLoc()); - NewTL.setNameLoc(TL.getNameLoc()); - return original; + indent -= 1; + return clang::QualType(DNT, 0); } - using Base::TransformDependentTemplateSpecializationType; - - clang::QualType rebuild_dtst(clang::TypeLocBuilder& TLB, - clang::DependentTemplateSpecializationTypeLoc TL) { - auto* DTST = TL.getTypePtr(); - /// push() returns an uninitialized record; the qualifier slot is a - /// pointer, so leaving it garbage crashes any later getSourceRange - /// (e.g. Sema::CheckTemplateArgument on a transformed argument). - auto NewTL = - TLB.push(clang::QualType(DTST, 0)); - NewTL.initializeLocal(context, getBaseLocation()); - return NewTL.getType(); - } - - clang::QualType TransformDependentTemplateSpecializationType( - clang::TypeLocBuilder& TLB, - clang::DependentTemplateSpecializationTypeLoc TL) { - auto* DTST = TL.getTypePtr(); + /// `scope` carries the enclosing specifier prefix for components whose own + /// qualifier is null (see rewrite_specifier). Such resolutions are not + /// cached: the node's identity does not include the scope it was found in. + clang::QualType + resolve_dependent_template(const clang::DependentTemplateSpecializationType* DTST, + const clang::NestedNameSpecifier* scope = nullptr) { LOG_DEBUG("{}" "resolve DTST '{}'", pad(), clang::QualType(DTST, 0).getAsString()); - ++indent; + indent += 1; - if(auto iter = resolved.find(DTST); iter != resolved.end()) { - --indent; - TLB.pushTrivial(context, iter->second, getBaseLocation()); - return iter->second; - } + auto& template_name = DTST->getDependentTemplateName(); + /// Scope-threaded and pack-narrowed resolutions both depend on + /// context the node pointer does not capture; neither may be cached. + bool cacheable = (template_name.getQualifier() != nullptr || !scope) && pack_narrowing == 0; - auto NNSLoc = TransformNestedNameSpecifierLoc(TL.getQualifierLoc()); - if(!NNSLoc) { - LOG_DEBUG("{}→ ", pad()); - --indent; - return rebuild_dtst(TLB, TL); + if(cacheable) { + if(auto iter = resolved.find(DTST); iter != resolved.end()) { + indent -= 1; + return iter->second; + } } - auto* NNS = NNSLoc.getNestedNameSpecifier(); - clang::TemplateArgumentListInfo info; - using iterator = clang::TemplateArgumentLocContainerIterator< - clang::DependentTemplateSpecializationTypeLoc>; - if(TransformTemplateArguments(iterator(TL, 0), iterator(TL, TL.getNumArgs()), info)) { - LOG_DEBUG("{}→ ", pad()); - --indent; - return rebuild_dtst(TLB, TL); - } + const clang::NestedNameSpecifier* NNS = + template_name.getQualifier() + ? rewrite_specifier(template_name.getQualifier(), Policy::Resolve) + : scope; llvm::SmallVector arguments; - for(auto& arg: info.arguments()) { - arguments.push_back(arg.getArgument()); - } + rewrite_arguments(DTST->template_arguments(), arguments, Policy::Resolve); - auto* name = DTST->getDependentTemplateName().getName().getIdentifier(); + auto* name = template_name.getName().getIdentifier(); if(!name) { LOG_DEBUG("{}→ ", pad()); - --indent; - return rebuild_dtst(TLB, TL); - } - - if(auto result = hole(NNS, name, arguments); !result.isNull()) { - LOG_DEBUG("{}" "hole: '{}' → '{}'", pad(), name->getName().str(), result.getAsString()); - --indent; - resolved.try_emplace(DTST, result); - TLB.pushTrivial(context, result, getBaseLocation()); - return result; + indent -= 1; + return clang::QualType(DTST, 0); } auto stack_size = stack.data.size(); @@ -1097,13 +2101,14 @@ class PseudoInstantiator : public clang::TreeTransform { stack.pop(); } if(!type.isNull() && type->isDependentType()) { - type = TransformType(type); + type = rewrite(type, Policy::Resolve); } if(!type.isNull()) { LOG_DEBUG("{}" "→ '{}' (alias)", pad(), type.getAsString()); - --indent; - resolved.try_emplace(DTST, type); - TLB.pushTrivial(context, type, getBaseLocation()); + indent -= 1; + if(cacheable && !truncated && !ctd_guard_tripped) { + resolved.try_emplace(DTST, type); + } return type; } } @@ -1111,18 +2116,14 @@ class PseudoInstantiator : public clang::TreeTransform { // Resolve DTST to a concrete TemplateSpecializationType. // e.g. __alloc_traits>::rebind → rebind (a TST) // This allows subsequent lookup of members (like "other") to work. - // Keep lookup frames on stack — the caller (e.g. TransformNestedNameSpecifierLoc + // Keep lookup frames on stack — the caller (e.g. rewrite_specifier // processing A::B::C) needs them for parameter substitution. - clang::TemplateName TN(CTD); - llvm::SmallVector canonArgs; - for(auto& arg: arguments) { - canonArgs.push_back(context.getCanonicalTemplateArgument(arg)); - } - auto result = context.getTemplateSpecializationType(TN, arguments, canonArgs); + auto result = make_specialization(clang::TemplateName(CTD), arguments); LOG_DEBUG("{}" "→ TST '{}' (class)", pad(), result.getAsString()); - --indent; - resolved.try_emplace(DTST, result); - TLB.pushTrivial(context, result, getBaseLocation()); + indent -= 1; + if(cacheable && !truncated && !ctd_guard_tripped) { + resolved.try_emplace(DTST, result); + } return result; } } @@ -1131,142 +2132,86 @@ class PseudoInstantiator : public clang::TreeTransform { } LOG_DEBUG("{}→ ", pad()); - --indent; - auto fallback = rebuild_dtst(TLB, TL); - resolved.try_emplace(DTST, fallback); - return fallback; - } - - /// Desugar dependent typedefs by delegating to SubstituteOnly. - /// This is called by PseudoInstantiator (not by SubstituteOnly itself, which has - /// its own TransformTypedefType). Using substitute() here ensures that typedef - /// expansion does NOT trigger heuristic lookup, preventing the typedef ↔ lookup cycle. - clang::QualType TransformTypedefType(clang::TypeLocBuilder& TLB, clang::TypedefTypeLoc TL) { - if(auto* TND = TL.getTypedefNameDecl()) { - auto underlying = TND->getUnderlyingType(); - if(underlying->isDependentType()) { - auto type = substitute(underlying); - if(!type.isNull()) { - if(auto ET = llvm::dyn_cast(type)) { - type = ET->getNamedType(); - } - TLB.pushTrivial(context, type, getBaseLocation()); - return type; - } - } - } - return Base::TransformTypedefType(TLB, TL); - } - - /// Attempt to resolve decltype expressions that reference variables. - /// Only handles the simple case of `decltype(var)` where `var` is a VarDecl. - /// TODO: Handle more complex decltype expressions (member access, function calls, etc.) - clang::QualType TransformDecltypeType(clang::TypeLocBuilder& TLB, clang::DecltypeTypeLoc TL) { - auto expr = TL.getTypePtr()->getUnderlyingExpr(); - if(auto DRE = llvm::dyn_cast(expr)) { - if(auto decl = DRE->getDecl(); llvm::isa(decl)) { - auto type = TransformType(decl->getType()); - if(!type.isNull()) { - TLB.pushTrivial(context, type, getBaseLocation()); - return type; - } - } + indent -= 1; + auto fallback = clang::QualType(DTST, 0); + /// Only a conclusive failure may be cached: an exhausted step budget + /// or a tripped recursion guard proves nothing, and the cache is + /// TU-wide — a truncated query must not poison this node for later + /// queries that could still resolve it. + if(cacheable && !truncated && !ctd_guard_tripped) { + resolved.try_emplace(DTST, fallback); } - - return Base::TransformDecltypeType(TLB, TL); + return fallback; } private: - clang::Sema& sema; clang::ASTContext& context; InstantiationStack stack; llvm::DenseMap& resolved; llvm::SmallPtrSet active_resolutions; llvm::DenseSet> active_ctd_lookups; unsigned depth = 0; + unsigned steps = 0; + unsigned probing = 0; + /// Non-zero while a pack element rewrite has a Pack binding narrowed to + /// one element; node-pointer-keyed caches are bypassed for the duration. + unsigned pack_narrowing = 0; + bool ctd_guard_tripped = false; + /// Sticky: some rewrite in this query hit the depth or step limit. A + /// truncated result is not conclusive and must never enter the TU-wide + /// cache — a later query with a fresh budget could still resolve it. + bool truncated = false; + + /// Hard ceiling on rewrite steps per query; bounds the total work + /// including pseudo-SFINAE probes over rejected branches. + constexpr static unsigned step_budget = 4096; unsigned indent = 0; - clang::SourceLocation base_location; std::string pad() const { return std::string(indent * 2, ' '); } }; -/// Pseudo-instantiation drives Sema on speculative inputs, so its failures -/// are expected and must not leak error-level diagnostics into the unit. -/// Only the consumer is swapped out: error counting stays untouched because -/// Sema's error limit is what stops runaway instantiations. -class DiagnosticSilencer { -public: - explicit DiagnosticSilencer(clang::Sema& sema) : engine(sema.getDiagnostics()) { - client = engine.getClient(); - owned = engine.takeClient(); - engine.setClient(&ignoring, false); - } - - ~DiagnosticSilencer() { - /// Deliberate trade-off: error counting is left to accumulate across - /// lookups. Once the engine's error limit trips, later speculative - /// lookups on this unit degrade (Sema bails out of instantiations) — - /// but resetting the counters per lookup hands every pathological - /// instantiation chain a fresh budget, which measures as minutes of - /// resolver time on STL-heavy TUs. Until the resolver carries its - /// own work budget, the shared limit is both the brake and the cap. - if(owned) { - engine.setClient(owned.release(), true); - } else { - engine.setClient(client, false); - } - } - -private: - clang::DiagnosticsEngine& engine; - clang::DiagnosticConsumer* client; - std::unique_ptr owned; - clang::IgnoringDiagConsumer ignoring; -}; - } // namespace clang::QualType TemplateResolver::resolve(clang::QualType type) { - DiagnosticSilencer silencer(sema); - PseudoInstantiator instantiator(sema, resolved); - return instantiator.TransformType(type); -} - -clang::QualType TemplateResolver::resugar(clang::QualType type, clang::Decl* decl) { - DiagnosticSilencer silencer(sema); - ResugarOnly resugar(sema, decl); - return resugar.TransformType(type); + PseudoInstantiator instantiator(context, resolved); + return instantiator.resolve(type); } TemplateResolver::lookup_result TemplateResolver::lookup(const clang::NestedNameSpecifier* NNS, clang::DeclarationName name) { - DiagnosticSilencer silencer(sema); - PseudoInstantiator instantiator(sema, resolved); + PseudoInstantiator instantiator(context, resolved); return instantiator.lookup(NNS, name); } -TemplateResolver::lookup_result - TemplateResolver::lookup(const clang::CXXDependentScopeMemberExpr* expr) { - auto type = expr->getBaseType(); +/// Shared base-type member resolution for dependent member expressions. +static TemplateResolver::lookup_result + lookup_member(clang::ASTContext& context, + llvm::DenseMap& resolved, + clang::QualType type, + bool arrow, + clang::DeclarationName name) { if(type.isNull()) { return {}; } - DiagnosticSilencer silencer(sema); - if(expr->isArrow()) { + if(arrow) { /// Follow overloaded operator-> chains (smart pointers) until a raw - /// pointer appears; bounded, cycles just stop resolving. - auto arrow = sema.getASTContext().DeclarationNames.getCXXOperatorName(clang::OO_Arrow); - for(unsigned hop = 0; hop < 8; hop++) { + /// pointer appears; bounded. A chain that never dereferences to a + /// pointer (no operator->, or a cycle) makes the arrow ill-formed — + /// treating it like a dot access would fabricate candidates. + auto arrow_name = context.DeclarationNames.getCXXOperatorName(clang::OO_Arrow); + bool dereferenced = false; + for(unsigned hop = 0; hop < 8; hop += 1) { if(auto* PT = type->getAs()) { type = PT->getPointeeType(); + dereferenced = true; break; } - PseudoInstantiator instantiator(sema, resolved); + PseudoInstantiator instantiator(context, resolved); const clang::CXXMethodDecl* method = nullptr; - for(auto* candidate: instantiator.lookup(type, arrow)) { + for(auto* candidate: instantiator.lookup(type, arrow_name)) { if((method = llvm::dyn_cast(candidate))) { break; } @@ -1279,6 +2224,9 @@ TemplateResolver::lookup_result return {}; } } + if(!dereferenced) { + return {}; + } } /// Inside the class's own definition `this` is the injected class name; @@ -1288,8 +2236,126 @@ TemplateResolver::lookup_result type = ICNT->getInjectedSpecializationType(); } - PseudoInstantiator instantiator(sema, resolved); - return instantiator.lookup(type, expr->getMemberNameInfo().getName()); + PseudoInstantiator instantiator(context, resolved); + return instantiator.lookup(type, name); +} + +TemplateResolver::lookup_result + TemplateResolver::lookup(const clang::CXXDependentScopeMemberExpr* expr) { + return lookup_member(context, + resolved, + expr->getBaseType(), + expr->isArrow(), + expr->getMemberNameInfo().getName()); +} + +TemplateResolver::lookup_result TemplateResolver::lookup(const clang::UnresolvedMemberExpr* expr) { + return lookup_member(context, + resolved, + expr->getBaseType(), + expr->isArrow(), + expr->getMemberName()); +} + +TemplateResolver::lookup_result + TemplateResolver::lookup(const clang::DependentTemplateSpecializationType* type) { + auto& template_name = type->getDependentTemplateName(); + auto name = template_name.getName(); + if(auto identifier = name.getIdentifier()) { + return lookup(template_name.getQualifier(), identifier); + } + return lookup(template_name.getQualifier(), + context.DeclarationNames.getCXXOperatorName(name.getOperator())); +} + +TemplateResolver::lookup_result TemplateResolver::lookup(const clang::UnresolvedLookupExpr* expr) { + /// A qualified name resolves through its scope, which yields the full + /// overload set from the named context. + if(auto NNS = expr->getQualifier()) { + if(auto members = lookup(NNS, expr->getName()); !members.empty()) { + return members; + } + } + + /// TODO: Unqualified overload sets cannot be returned as a lookup_result + /// (the candidates have no contiguous storage); fall back to the first + /// template declaration. + for(auto decl: expr->decls()) { + if(auto TD = llvm::dyn_cast(decl)) { + return lookup_result(TD); + } + } + + return {}; +} + +/// Can `FD` accept a call with `count` arguments? Default arguments lower the +/// minimum; C-style variadics and parameter packs lift the maximum. +static bool arity_viable(const clang::FunctionDecl* FD, unsigned count, bool member_call) { + /// A member-syntax call does not spell the explicit object argument + /// (`s.foo(1)` with `foo(this S&, int)`), but the declaration counts it. + if(member_call) { + if(auto method = llvm::dyn_cast(FD); + method && method->isExplicitObjectMemberFunction()) { + count += 1; + } + } + if(count < FD->getMinRequiredArguments()) { + return false; + } + if(count <= FD->getNumParams() || FD->isVariadic()) { + return true; + } + return std::ranges::any_of(FD->parameters(), [](const clang::ParmVarDecl* param) { + return param->isParameterPack(); + }); +} + +llvm::SmallVector TemplateResolver::lookup(const clang::CallExpr* expr) { + llvm::SmallVector candidates; + + auto callee = expr->getCallee()->IgnoreParenImpCasts(); + bool member_call = + llvm::isa(callee); + if(auto OE = llvm::dyn_cast(callee)) { + for(auto decl: OE->decls()) { + candidates.push_back(decl); + } + } else if(auto DSME = llvm::dyn_cast(callee)) { + for(auto decl: lookup(DSME)) { + candidates.push_back(decl); + } + } else if(auto DSDRE = llvm::dyn_cast(callee)) { + for(auto decl: lookup(DSDRE)) { + candidates.push_back(decl); + } + } + + /// An argument pack (`f(xs...)`) may instantiate to any number of + /// arguments; fixed-arity filtering would remove viable overloads. + bool has_pack_argument = + std::ranges::any_of(expr->arguments(), [](const clang::Expr* argument) { + return llvm::isa(argument); + }); + if(has_pack_argument) { + return candidates; + } + + auto removed = std::ranges::remove_if(candidates, [&](clang::NamedDecl* decl) { + auto target = decl; + if(auto shadow = llvm::dyn_cast(target)) { + target = shadow->getTargetDecl(); + } + if(auto FTD = llvm::dyn_cast(target)) { + target = FTD->getTemplatedDecl(); + } + /// Non-function candidates (e.g. a callable object's variable) stay: + /// arity says nothing about them. + auto FD = llvm::dyn_cast(target); + return FD && !arity_viable(FD, expr->getNumArgs(), member_call); + }); + candidates.erase(removed.begin(), removed.end()); + return candidates; } } // namespace clice diff --git a/src/semantic/resolver.h b/src/semantic/resolver.h index 429f900ac..8391b471a 100644 --- a/src/semantic/resolver.h +++ b/src/semantic/resolver.h @@ -3,12 +3,6 @@ #include "clang/AST/ExprCXX.h" #include "clang/AST/Type.h" -namespace clang { - -class Sema; - -} - namespace clice { /// This class is used to resolve dependent names in the unit. @@ -18,26 +12,18 @@ namespace clice { /// some heuristics to simplify the dependent names as normal type/expression. /// For example, `std::vector::value_type` can be simplified as `T`. /// +/// Resolution is pure AST computation: it never enters Sema, so speculative +/// lookups cannot emit diagnostics, register specializations, or otherwise +/// mutate the unit's semantic state. +/// /// Thread safety: NOT thread-safe. Each compilation unit should have its own resolver. /// The `resolved` cache persists across multiple resolve() calls on the same unit. class TemplateResolver { public: - explicit TemplateResolver(clang::Sema& sema) : sema(sema) {} + explicit TemplateResolver(clang::ASTContext& context) : context(context) {} clang::QualType resolve(clang::QualType type); - void resolve(clang::CXXUnresolvedConstructExpr* expr); - - void resolve(clang::UnresolvedLookupExpr* expr); - - // TODO: Use a clearer approach for resolving UnresolvedLookupExpr. - - void resolve(clang::UnresolvedUsingType* type); - - /// Resugar the canonical `TemplateTypeParmType` with given template context. - /// `decl` should be the declaration that the type is in. - clang::QualType resugar(clang::QualType type, clang::Decl* decl); - using lookup_result = clang::DeclContext::lookup_result; /// Look up the name in the given nested name specifier. @@ -47,35 +33,25 @@ class TemplateResolver { return lookup(type->getQualifier(), type->getIdentifier()); } - lookup_result lookup(const clang::DependentTemplateSpecializationType* type) { - auto& template_name = type->getDependentTemplateName(); - auto identifier = template_name.getName().getIdentifier(); - if(identifier) { - return lookup(template_name.getQualifier(), identifier); - } else { - /// TODO: Operators don't have an IdentifierInfo; need DeclarationName-based lookup. - return {}; - } - } + lookup_result lookup(const clang::DependentTemplateSpecializationType* type); lookup_result lookup(const clang::DependentScopeDeclRefExpr* expr) { return lookup(expr->getQualifier(), expr->getNameInfo().getName()); } - lookup_result lookup(const clang::UnresolvedLookupExpr* expr) { - /// TODO: Only returns the first TemplateDecl; should handle overloaded lookups. - for(auto decl: expr->decls()) { - if(auto TD = llvm::dyn_cast(decl)) { - return lookup_result(TD); - } - } + lookup_result lookup(const clang::UnresolvedLookupExpr* expr); - return {}; - } + /// Resolve the base type through pseudo-instantiation, then look the + /// member up in the resolved record. Complements the candidates clang + /// already stores on the expression: pseudo-instantiation can reach + /// members of matching partial specializations. + lookup_result lookup(const clang::UnresolvedMemberExpr* expr); - lookup_result lookup(const clang::UnresolvedMemberExpr* expr) { - return {}; - } + /// Resolve a dependent call's candidate set, filtered down to overloads + /// whose parameter list can accept the call's argument count. Full + /// overload resolution needs conversion rules (Sema territory); arity is + /// the safe, conversion-free subset of it. + llvm::SmallVector lookup(const clang::CallExpr* expr); /// Resolve the base type through pseudo-instantiation, then look the /// member up in the resolved record (e.g. `this->foo()` inherited from @@ -91,7 +67,7 @@ class TemplateResolver { } private: - clang::Sema& sema; + clang::ASTContext& context; /// Cache of resolved dependent types, keyed by AST node pointer. /// Shared across resolve() calls within the same TU for performance. diff --git a/src/semantic/semantics.cpp b/src/semantic/semantics.cpp index 41b846552..27e4463c4 100644 --- a/src/semantic/semantics.cpp +++ b/src/semantic/semantics.cpp @@ -1334,7 +1334,10 @@ clang::SourceLocation keyword_after_scope(const clang::Decl* decl, return token ? token->getLocation() : begin; } -void stmt_occurrences(const clang::Stmt* S, Occurrences& out, TemplateResolver* resolver) { +void stmt_occurrences(const clang::Stmt* S, + Occurrences& out, + TemplateResolver* resolver, + const clang::CallExpr* call = nullptr) { /// foo = 1 /// ^~~~ reference if(auto* DRE = llvm::dyn_cast(S)) { @@ -1444,8 +1447,14 @@ void stmt_occurrences(const clang::Stmt* S, Occurrences& out, TemplateResolver* /// ^~~~ weak reference if(auto* DSDRE = llvm::dyn_cast(S)) { if(resolver) { - for(auto* target: resolver->lookup(DSDRE)) { - occur(out, target, RelationKind::WeakReference, DSDRE->getNameInfo().getLoc()); + if(call) { + for(auto* target: resolver->lookup(call)) { + occur(out, target, RelationKind::WeakReference, DSDRE->getNameInfo().getLoc()); + } + } else { + for(auto* target: resolver->lookup(DSDRE)) { + occur(out, target, RelationKind::WeakReference, DSDRE->getNameInfo().getLoc()); + } } } return; @@ -1454,11 +1463,20 @@ void stmt_occurrences(const clang::Stmt* S, Occurrences& out, TemplateResolver* /// foo(...) / obj.foo(...) where foo names an overload set — clang /// already stores the candidates. if(auto* OE = llvm::dyn_cast(S)) { + /// As a callee, the candidate set shrinks to the overloads that can + /// accept the call's argument count. + llvm::SmallVector candidates; + if(call && resolver) { + candidates = resolver->lookup(call); + } else { + candidates.append(OE->decls_begin(), OE->decls_end()); + } + /// Unwrap using shadows to the underlying functions, then reference /// each introducing using-declaration once: hover picks it when the /// overload set is otherwise ambiguous. llvm::SmallPtrSet introducers; - for(auto* target: OE->decls()) { + for(auto* target: candidates) { if(auto* shadow = llvm::dyn_cast(target)) { if(auto* UD = llvm::dyn_cast(shadow->getIntroducer())) { introducers.insert(UD); @@ -1494,8 +1512,14 @@ void stmt_occurrences(const clang::Stmt* S, Occurrences& out, TemplateResolver* /// ^~~~ weak reference, resolved through the template resolver if(auto* DSME = llvm::dyn_cast(S)) { if(resolver) { - for(auto* target: resolver->lookup(DSME)) { - occur(out, target, RelationKind::WeakReference, DSME->getMemberLoc()); + if(call) { + for(auto* target: resolver->lookup(call)) { + occur(out, target, RelationKind::WeakReference, DSME->getMemberLoc()); + } + } else { + for(auto* target: resolver->lookup(DSME)) { + occur(out, target, RelationKind::WeakReference, DSME->getMemberLoc()); + } } } return; @@ -1504,6 +1528,41 @@ void stmt_occurrences(const clang::Stmt* S, Occurrences& out, TemplateResolver* } // namespace +llvm::SmallVector resolve_occurrences(const Semantics& semantics, + std::uint32_t index, + TemplateResolver* resolver) { + auto& entry = semantics.node(index); + + /// A dependent name used as a callee resolves against the call, so the + /// candidate set can be filtered by arity. Walk up through the wrapper + /// nodes the tree keeps for parent chains. + if(auto* S = entry.node.get(); + S && llvm::isa(S)) { + for(auto parent = entry.parent; parent != Semantics::invalid; + parent = semantics.node(parent).parent) { + auto* PS = semantics.node(parent).node.get(); + if(!PS) { + break; + } + if(auto* CE = llvm::dyn_cast(PS)) { + if(CE->getCallee()->IgnoreParenImpCasts() == S) { + llvm::SmallVector out; + stmt_occurrences(S, out, resolver, CE); + return out; + } + break; + } + if(!llvm::isa(PS)) { + break; + } + } + } + + return resolve_occurrences(entry.node, resolver); +} + llvm::SmallVector resolve_occurrences(const SemanticNode& node, TemplateResolver* resolver) { llvm::SmallVector out; diff --git a/src/semantic/semantics.h b/src/semantic/semantics.h index de5750b12..09d13c881 100644 --- a/src/semantic/semantics.h +++ b/src/semantic/semantics.h @@ -318,4 +318,11 @@ class Semantics { std::vector owner_nodes; }; +/// resolve_occurrences with tree context: a dependent name that is the +/// callee of a call (found through the node's parent chain) has its +/// candidate set filtered by the call's arity. +llvm::SmallVector resolve_occurrences(const Semantics& semantics, + std::uint32_t index, + TemplateResolver* resolver = nullptr); + } // namespace clice diff --git a/src/semantic/unifier.cpp b/src/semantic/unifier.cpp new file mode 100644 index 000000000..07dde95c2 --- /dev/null +++ b/src/semantic/unifier.cpp @@ -0,0 +1,790 @@ +#include "semantic/unifier.h" + +#include "clang/AST/ExprCXX.h" +#include "clang/AST/RecursiveASTVisitor.h" + +namespace clice { + +namespace { + +/// Strip local qualifiers and one-step sugar until a structural node is +/// reached, accumulating qualifiers into `quals`. Child sugar is preserved: +/// only the current level is desugared, so template arguments and pointees +/// keep the form the user wrote. +clang::QualType peel(clang::QualType type, clang::Qualifiers& quals) { + while(true) { + if(type.hasLocalQualifiers()) { + quals.addQualifiers(type.getLocalQualifiers()); + type = type.getLocalUnqualifiedType(); + continue; + } + + const clang::Type* T = type.getTypePtr(); + switch(T->getTypeClass()) { + case clang::Type::Elaborated: { + type = llvm::cast(T)->getNamedType(); + continue; + } + case clang::Type::Paren: { + type = llvm::cast(T)->getInnerType(); + continue; + } + case clang::Type::Using: { + type = llvm::cast(T)->getUnderlyingType(); + continue; + } + case clang::Type::Typedef: { + type = llvm::cast(T)->desugar(); + continue; + } + case clang::Type::SubstTemplateTypeParm: { + type = llvm::cast(T)->getReplacementType(); + continue; + } + case clang::Type::MacroQualified: { + type = llvm::cast(T)->getUnderlyingType(); + continue; + } + case clang::Type::Attributed: { + type = llvm::cast(T)->getEquivalentType(); + continue; + } + case clang::Type::TemplateSpecialization: { + auto TST = llvm::cast(T); + /// Alias specializations are sugar for the substituted + /// underlying type; structural matching sees through them. + if(TST->isTypeAlias()) { + type = TST->desugar(); + continue; + } + return type; + } + default: { + return type; + } + } + } +} + +/// Can `TD` be bound to the template template parameter `TTP`? Approximated +/// by arity (defaults and packs included) plus positional parameter kinds: a +/// unary parameter must not accept a binary template, and a type slot must +/// not accept a non-type one. Nested template-template lists are not +/// compared recursively. +bool template_compatible(const clang::TemplateTemplateParmDecl* TTP, clang::TemplateDecl* TD) { + auto params = TTP->getTemplateParameters(); + auto candidate = TD->getTemplateParameters(); + + auto kinds_match = [&](unsigned count) { + for(unsigned i = 0; i < count; i += 1) { + auto lhs = params->getParam(i); + auto rhs = candidate->getParam(i); + if(lhs->getKind() != rhs->getKind()) { + return false; + } + } + return true; + }; + + if(params->hasParameterPack()) { + /// The pack absorbs every candidate slot past the fixed prefix, so + /// each of those slots must agree with the pack's kind (a type pack + /// cannot cover a non-type slot, defaulted or not) — and the + /// candidate must be able to take the fixed prefix at all. + unsigned fixed = params->size() - 1; + if(candidate->size() < fixed && !candidate->hasParameterPack()) { + return false; + } + if(!kinds_match(std::min(fixed, candidate->size()))) { + return false; + } + auto pack_kind = params->getParam(params->size() - 1)->getKind(); + for(unsigned i = fixed; i < candidate->size(); i += 1) { + if(candidate->getParam(i)->getKind() != pack_kind) { + return false; + } + } + return true; + } + if(params->size() < candidate->getMinRequiredArguments() || + (params->size() > candidate->size() && !candidate->hasParameterPack())) { + return false; + } + return kinds_match(std::min(params->size(), candidate->size())); +} + +/// Indices of the deduction-depth parameter packs referenced inside an +/// expansion pattern; a zero-length expansion must still bind them (to the +/// empty pack) so cardinality conflicts with other occurrences surface. +struct PackIndexCollector : clang::RecursiveASTVisitor { + unsigned depth; + llvm::SmallVector indices; + + explicit PackIndexCollector(unsigned depth) : depth(depth) {} + + bool VisitTemplateTypeParmType(clang::TemplateTypeParmType* T) { + if(T->isParameterPack() && T->getDepth() == depth) { + indices.push_back(T->getIndex()); + } + return true; + } + + bool VisitDeclRefExpr(clang::DeclRefExpr* expr) { + if(auto NTTP = llvm::dyn_cast(expr->getDecl()); + NTTP && NTTP->isParameterPack() && NTTP->getDepth() == depth) { + indices.push_back(NTTP->getIndex()); + } + return true; + } + + bool TraverseTemplateName(clang::TemplateName name) { + if(auto TTP = + llvm::dyn_cast_or_null(name.getAsTemplateDecl()); + TTP && TTP->isParameterPack() && TTP->getDepth() == depth) { + indices.push_back(TTP->getIndex()); + } + return RecursiveASTVisitor::TraverseTemplateName(name); + } + + void traverse(const clang::TemplateArgument& argument) { + switch(argument.getKind()) { + case clang::TemplateArgument::Type: { + TraverseType(argument.getAsType()); + break; + } + case clang::TemplateArgument::Expression: { + TraverseStmt(argument.getAsExpr()); + break; + } + case clang::TemplateArgument::Template: + case clang::TemplateArgument::TemplateExpansion: { + TraverseTemplateName(argument.getAsTemplateOrTemplatePattern()); + break; + } + default: { + break; + } + } + } +}; + +} // namespace + +const clang::NonTypeTemplateParmDecl* referenced_nttp(const clang::Expr* expr) { + if(!expr) { + return nullptr; + } + if(auto DRE = llvm::dyn_cast(expr->IgnoreParenImpCasts())) { + return llvm::dyn_cast(DRE->getDecl()); + } + return nullptr; +} + +bool TypeUnifier::equivalent(const clang::TemplateArgument& lhs, + const clang::TemplateArgument& rhs) const { + if(context.getCanonicalTemplateArgument(lhs).structurallyEquals( + context.getCanonicalTemplateArgument(rhs))) { + return true; + } + + /// Dependent expression arguments keep distinct node identities even + /// when they spell the same thing; two bare references to one + /// parameter (`A`) are the same value. + if(lhs.getKind() == clang::TemplateArgument::Expression && + rhs.getKind() == clang::TemplateArgument::Expression) { + auto* left = referenced_nttp(lhs.getAsExpr()); + return left && left == referenced_nttp(rhs.getAsExpr()); + } + return false; +} + +bool TypeUnifier::bind(unsigned index, const clang::TemplateArgument& argument) { + if(index >= bindings.size()) { + return false; + } + + auto& existing = bindings[index]; + if(existing.isNull()) { + existing = argument; + return true; + } + + if(!equivalent(existing, argument)) { + return false; + } + + /// Same argument bound twice; keep the more sugared spelling. + if(existing.getKind() == clang::TemplateArgument::Type && + argument.getKind() == clang::TemplateArgument::Type) { + auto type = existing.getAsType(); + if(type == type.getCanonicalType() && + argument.getAsType() != argument.getAsType().getCanonicalType()) { + existing = argument; + } + } + return true; +} + +bool TypeUnifier::collect(unsigned index, const clang::TemplateArgument& argument) { + if(index >= bindings.size()) { + return false; + } + if(elements.size() < bindings.size()) { + elements.resize(bindings.size()); + } + + auto& group = elements[index]; + + /// A pack referenced twice in one expansion element (`pair...`) + /// must see the same value at every occurrence. + if(group.size() == element_ordinal + 1) { + return equivalent(group.back(), argument); + } + + /// A pack that skipped an earlier element cannot re-synchronize. + if(group.size() != element_ordinal) { + return false; + } + + group.push_back(argument); + return true; +} + +bool TypeUnifier::template_id(clang::QualType type, + clang::TemplateName& name, + TemplateArguments& arguments) const { + clang::Qualifiers quals; + type = peel(type, quals); + + if(auto ICNT = llvm::dyn_cast(type)) { + type = ICNT->getInjectedSpecializationType(); + } + + if(auto TST = llvm::dyn_cast(type)) { + name = TST->getTemplateName(); + arguments = TST->template_arguments(); + return true; + } + + if(auto RT = llvm::dyn_cast(type)) { + if(auto CTSD = llvm::dyn_cast(RT->getDecl())) { + name = clang::TemplateName(CTSD->getSpecializedTemplate()); + arguments = CTSD->getTemplateArgs().asArray(); + return true; + } + } + + return false; +} + +bool TypeUnifier::unify(clang::QualType pattern, clang::QualType argument) { + if(pattern.isNull() || argument.isNull()) { + return false; + } + + clang::Qualifiers pattern_quals; + clang::Qualifiers argument_quals; + pattern = peel(pattern, pattern_quals); + argument = peel(argument, argument_quals); + + /// `cv-list T`: the parameter absorbs the qualifiers the pattern doesn't + /// mention, so its qualifiers must be a subset of the argument's. + /// + /// Only parameters at the deduction depth bind; a parameter of an + /// enclosing template is a concrete type from this deduction's point of + /// view and falls through to structural comparison below — treating it + /// as a wildcard would let `Inner>` match any first element. + if(auto TTPT = llvm::dyn_cast(pattern); + TTPT && TTPT->getDepth() == depth) { + if(!argument_quals.isStrictSupersetOf(pattern_quals) && argument_quals != pattern_quals) { + return false; + } + auto remaining = argument_quals; + remaining.removeQualifiers(pattern_quals); + auto bound = context.getQualifiedType(argument, remaining); + if(expanding && TTPT->isParameterPack()) { + return collect(TTPT->getIndex(), clang::TemplateArgument(bound)); + } + return bind(TTPT->getIndex(), clang::TemplateArgument(bound)); + } + + /// A pack expansion on the argument side has an unknown element count, + /// but a fixed pattern must still be structurally admissible against one + /// element (`void(int)` can never match `void(Us*...)`). + if(auto APET = llvm::dyn_cast(argument)) { + return unify(pattern, APET->getPattern()); + } + + /// Anything else matches structurally: qualifiers must agree exactly. + if(pattern_quals != argument_quals) { + return false; + } + + switch(pattern->getTypeClass()) { + case clang::Type::Pointer: { + auto AP = llvm::dyn_cast(argument); + return AP && unify(llvm::cast(pattern)->getPointeeType(), + AP->getPointeeType()); + } + + case clang::Type::LValueReference: + case clang::Type::RValueReference: { + if(pattern->getTypeClass() != argument->getTypeClass()) { + return false; + } + return unify(llvm::cast(pattern)->getPointeeType(), + llvm::cast(argument)->getPointeeType()); + } + + case clang::Type::TemplateSpecialization: + case clang::Type::InjectedClassName: + case clang::Type::Record: { + clang::TemplateName pattern_name, argument_name; + TemplateArguments pattern_args, argument_args; + if(!template_id(pattern, pattern_name, pattern_args)) { + /// A plain record with no template head matches only itself. + return context.hasSameUnqualifiedType(pattern, argument); + } + if(!template_id(argument, argument_name, argument_args)) { + return false; + } + + /// A template template parameter in the head deduces the + /// argument's template, e.g. matching `X>`. + if(auto TTP = llvm::dyn_cast_or_null( + pattern_name.getAsTemplateDecl()); + TTP && TTP->getDepth() == depth) { + if(auto TD = argument_name.getAsTemplateDecl(); + TD && !template_compatible(TTP, TD)) { + return false; + } + auto head = clang::TemplateArgument(argument_name); + if(expanding && TTP->isParameterPack() ? !collect(TTP->getIndex(), head) + : !bind(TTP->getIndex(), head)) { + return false; + } + } else if(!context.hasSameTemplateName(pattern_name, argument_name)) { + return false; + } + + return unify(pattern_args, argument_args); + } + + case clang::Type::FunctionProto: { + auto PF = llvm::cast(pattern); + auto AF = llvm::dyn_cast(argument); + if(!AF || PF->isVariadic() != AF->isVariadic()) { + return false; + } + + /// The extended info (calling convention, regparm, ...) is part + /// of the function type: an ABI-specific pattern must not accept + /// the default ABI. + if(PF->getExtInfo() != AF->getExtInfo()) { + return false; + } + + /// noexcept participates in the function type; a mismatch rejects + /// the match unless either side's specification is still value + /// dependent (`noexcept(B)` with unbound B). + if(PF->getExceptionSpecType() != clang::EST_DependentNoexcept && + AF->getExceptionSpecType() != clang::EST_DependentNoexcept && + PF->isNothrow() != AF->isNothrow()) { + return false; + } + + /// `noexcept(B)` with a bare parameter deduces B from the + /// argument's specification, like an array bound. + if(PF->getExceptionSpecType() == clang::EST_DependentNoexcept) { + if(auto NTTP = referenced_nttp(PF->getNoexceptExpr()); + NTTP && NTTP->getDepth() == depth) { + clang::TemplateArgument bound; + if(AF->getExceptionSpecType() == clang::EST_DependentNoexcept && + AF->getNoexceptExpr()) { + bound = clang::TemplateArgument(AF->getNoexceptExpr(), + /*IsCanonical=*/false); + } else if(!NTTP->getType()->isDependentType()) { + bound = clang::TemplateArgument( + context, + llvm::APSInt(llvm::APInt(1, AF->isNothrow() ? 1 : 0), true), + NTTP->getType()); + } + if(!bound.isNull() && !bind(NTTP->getIndex(), bound)) { + return false; + } + } + } + + /// Method cv/ref qualifiers are part of the type as well: + /// `R (C::*)(As...) const` must not accept a non-const one. + if(PF->getMethodQuals() != AF->getMethodQuals() || + PF->getRefQualifier() != AF->getRefQualifier()) { + return false; + } + if(!unify(PF->getReturnType(), AF->getReturnType())) { + return false; + } + + /// Parameter lists reuse the argument-list machinery so a trailing + /// `As...` deduces element-wise like a trailing pack argument. + llvm::SmallVector pattern_params; + for(auto type: PF->getParamTypes()) { + pattern_params.emplace_back(type); + } + llvm::SmallVector argument_params; + for(auto type: AF->getParamTypes()) { + argument_params.emplace_back(type); + } + return unify(pattern_params, argument_params); + } + + case clang::Type::MemberPointer: { + auto PM = llvm::cast(pattern); + auto AM = llvm::dyn_cast(argument); + if(!AM) { + return false; + } + auto pattern_cls = PM->getQualifier() ? PM->getQualifier()->getAsType() : nullptr; + auto argument_cls = AM->getQualifier() ? AM->getQualifier()->getAsType() : nullptr; + if(!pattern_cls || !argument_cls) { + return false; + } + return unify(clang::QualType(pattern_cls, 0), clang::QualType(argument_cls, 0)) && + unify(PM->getPointeeType(), AM->getPointeeType()); + } + + case clang::Type::ConstantArray: { + auto PA = llvm::cast(pattern); + auto AA = llvm::dyn_cast(argument); + return AA && PA->getSize() == AA->getSize() && + unify(PA->getElementType(), AA->getElementType()); + } + + case clang::Type::IncompleteArray: { + auto AA = llvm::dyn_cast(argument); + return AA && unify(llvm::cast(pattern)->getElementType(), + AA->getElementType()); + } + + case clang::Type::DependentSizedArray: { + auto PA = llvm::cast(pattern); + + /// `T[N]`: deduce N from a constant array bound. An integral + /// argument needs a concrete type: `auto` bounds deduce as the + /// array bound type per the language rules. + if(auto NTTP = referenced_nttp(PA->getSizeExpr()); NTTP && NTTP->getDepth() == depth) { + if(auto AA = llvm::dyn_cast(argument)) { + auto type = NTTP->getType(); + if(type->isDependentType()) { + type = context.getSizeType(); + } + llvm::APSInt size(AA->getSize()); + size.setIsUnsigned(type->isUnsignedIntegerType()); + + /// The bound must be representable in the parameter's + /// type: `bool N` never deduces from `U[2]`. + auto converted = size.extOrTrunc(context.getIntWidth(type)); + if(!llvm::APSInt::isSameValue(converted.extOrTrunc(size.getBitWidth()), size)) { + return false; + } + + if(!bind(NTTP->getIndex(), clang::TemplateArgument(context, converted, type))) { + return false; + } + return unify(PA->getElementType(), AA->getElementType()); + } + } + + /// `T[N]` against another dependent-sized array deduces N from + /// the argument's bound expression; the types must agree for the + /// value to survive conversion. + if(auto NTTP = referenced_nttp(PA->getSizeExpr()); NTTP && NTTP->getDepth() == depth) { + if(auto AA = llvm::dyn_cast(argument); + AA && AA->getSizeExpr()) { + if(!NTTP->getType()->isDependentType() && + !AA->getSizeExpr()->getType()->isDependentType() && + !context.hasSameUnqualifiedType(NTTP->getType(), + AA->getSizeExpr()->getType())) { + return false; + } + if(!bind(NTTP->getIndex(), + clang::TemplateArgument(AA->getSizeExpr(), /*IsCanonical=*/false))) { + return false; + } + return unify(PA->getElementType(), AA->getElementType()); + } + } + + /// A bounded pattern (`U[N]`) only matches arrays that have a + /// bound; `X[]` stays with the primary. + if(llvm::isa(argument)) { + return unify(PA->getElementType(), + llvm::cast(argument)->getElementType()); + } + return false; + } + + /// Dependent forms we cannot look into are non-deduced contexts: + /// they constrain nothing. + case clang::Type::DependentName: + case clang::Type::DependentTemplateSpecialization: + case clang::Type::Decltype: + case clang::Type::UnresolvedUsing: + case clang::Type::PackExpansion: { + return true; + } + + default: { + return context.hasSameUnqualifiedType(pattern, argument); + } + } +} + +bool TypeUnifier::unify(const clang::TemplateArgument& pattern, + const clang::TemplateArgument& argument) { + switch(pattern.getKind()) { + case clang::TemplateArgument::Type: { + if(argument.getKind() != clang::TemplateArgument::Type) { + return false; + } + return unify(pattern.getAsType(), argument.getAsType()); + } + + case clang::TemplateArgument::Expression: { + /// A bare reference to an NTTP deduces it; any other expression + /// is a non-deduced context. + /// TODO(nttp-expr): compound expression patterns (`P`) + /// are accepted without post-deduction validation, which can keep + /// a partial real deduction would reject. Validating requires + /// evaluating dependent expressions under bindings; deliberately + /// deferred — the failure mode must stay mis-selection between + /// declared specializations, never a crash. Constant expression arguments are + /// normalized to Integral so downstream substitution (e.g. array + /// bounds) sees a value, not an expression. + if(auto NTTP = referenced_nttp(pattern.getAsExpr()); + NTTP && NTTP->getDepth() == depth) { + auto bound = argument; + if(argument.getKind() == clang::TemplateArgument::Expression) { + auto expr = argument.getAsExpr(); + + /// Value deduction requires the types to agree: a `bool` + /// pattern parameter never matches an `int` argument, no + /// matter the eventual value. + if(!NTTP->getType()->isDependentType() && !expr->getType()->isDependentType() && + !context.hasSameUnqualifiedType(NTTP->getType(), expr->getType())) { + return false; + } + + /// An integral TemplateArgument requires a concrete type; + /// a parameter typed by an earlier parameter (`T N`) stays + /// an expression until that type is substituted. + if(!expr->isValueDependent() && !NTTP->getType()->isDependentType()) { + if(auto value = expr->getIntegerConstantExpr(context)) { + bound = clang::TemplateArgument(context, *value, NTTP->getType()); + } + } + } + if(expanding && NTTP->isParameterPack()) { + return collect(NTTP->getIndex(), bound); + } + return bind(NTTP->getIndex(), bound); + } + return true; + } + + case clang::TemplateArgument::Integral: { + if(argument.getKind() == clang::TemplateArgument::Integral) { + return context.hasSameType(pattern.getIntegralType(), argument.getIntegralType()) && + llvm::APSInt::isSameValue(pattern.getAsIntegral(), argument.getAsIntegral()); + } + /// As-written value arguments (`pick`) arrive as + /// expressions; evaluate constants so value-specialized partials + /// can match. The value's type is part of the identity for `auto` + /// parameters (`trait<1, B>` must not swallow `trait<1L, Y>`). + if(argument.getKind() == clang::TemplateArgument::Expression) { + auto expr = argument.getAsExpr(); + if(!expr->isValueDependent() && + context.hasSameType(pattern.getIntegralType(), expr->getType())) { + if(auto value = expr->getIntegerConstantExpr(context)) { + return llvm::APSInt::isSameValue(pattern.getAsIntegral(), *value); + } + } + } + return false; + } + + case clang::TemplateArgument::Template: { + if(auto TTP = llvm::dyn_cast_or_null( + pattern.getAsTemplate().getAsTemplateDecl()); + TTP && TTP->getDepth() == depth) { + if(argument.getKind() == clang::TemplateArgument::Template) { + if(auto TD = argument.getAsTemplate().getAsTemplateDecl(); + TD && !template_compatible(TTP, TD)) { + return false; + } + } + if(expanding && TTP->isParameterPack()) { + return collect(TTP->getIndex(), argument); + } + return bind(TTP->getIndex(), argument); + } + if(argument.getKind() != clang::TemplateArgument::Template) { + return false; + } + return context.hasSameTemplateName(pattern.getAsTemplate(), argument.getAsTemplate()); + } + + default: { + auto lhs = context.getCanonicalTemplateArgument(pattern); + auto rhs = context.getCanonicalTemplateArgument(argument); + return lhs.structurallyEquals(rhs); + } + } +} + +bool TypeUnifier::unify(TemplateArguments patterns, TemplateArguments arguments) { + /// Flatten Pack entries on both sides so positional matching lines up: + /// converted argument lists (injected arguments, partial specialization + /// patterns) group a pack's arguments as `Pack{...}`, and substitution + /// may produce Pack entries inline. + llvm::SmallVector flat_patterns; + for(auto& pattern: patterns) { + if(pattern.getKind() == clang::TemplateArgument::Pack) { + flat_patterns.append(pattern.pack_begin(), pattern.pack_end()); + } else { + flat_patterns.push_back(pattern); + } + } + + llvm::SmallVector flat; + for(auto& argument: arguments) { + if(argument.getKind() == clang::TemplateArgument::Pack) { + flat.append(argument.pack_begin(), argument.pack_end()); + } else { + flat.push_back(argument); + } + } + + unsigned i = 0; + for(unsigned p = 0; p < flat_patterns.size(); p += 1) { + auto& pattern = flat_patterns[p]; + if(pattern.isPackExpansion()) { + /// Nested expansions stay non-deduced. + if(expanding) { + return true; + } + + /// A pack expansion matches arguments element-wise: pack + /// parameters inside the pattern accumulate one binding per + /// element (`box...` against `box, box` deduces + /// `Us = {int, X}`), while non-pack parameters must deduce + /// consistently across elements. The element count is fixed by + /// the arity — whatever the fixed suffix (`void(Ts..., int)`) + /// does not claim belongs to the expansion. A second expansion + /// has no unique split and rejects. + unsigned suffix = flat_patterns.size() - p - 1; + for(unsigned q = p + 1; q < flat_patterns.size(); q += 1) { + if(flat_patterns[q].isPackExpansion()) { + return false; + } + } + if(flat.size() < i + suffix) { + return false; + } + unsigned length = flat.size() - i - suffix; + + auto inner = pattern.getPackExpansionPattern(); + elements.clear(); + elements.resize(bindings.size()); + expanding = true; + bool matched = true; + for(unsigned j = i; j < i + length; j += 1) { + element_ordinal = j - i; + if(!unify(inner, flat[j])) { + matched = false; + break; + } + } + expanding = false; + if(!matched) { + return false; + } + + /// A zero-length expansion collected nothing, but its packs must + /// still bind to the empty pack: `tuple` against `tuple<>` + /// conflicts with `Ts = {X}` from another occurrence. With a + /// non-zero length, empty groups belong to packs guarded away by + /// nested expansions and stay unbound. + if(length == 0) { + PackIndexCollector collector(depth); + collector.traverse(inner); + for(auto index: collector.indices) { + if(!bind(index, clang::TemplateArgument::CreatePackCopy(context, {}))) { + return false; + } + } + } + for(auto [index, group]: llvm::enumerate(elements)) { + if(!group.empty() && + !bind(index, clang::TemplateArgument::CreatePackCopy(context, group))) { + return false; + } + } + i += length; + continue; + } + + if(i >= flat.size()) { + return false; + } + if(!unify(pattern, flat[i])) { + return false; + } + i += 1; + } + + return i == flat.size(); +} + +bool deduce_arguments(clang::ASTContext& context, + clang::TemplateParameterList* params, + llvm::ArrayRef patterns, + llvm::ArrayRef arguments, + llvm::SmallVectorImpl& deduced) { + TypeUnifier unifier(context, params->getDepth(), params->size()); + if(!unifier.unify(patterns, arguments)) { + return false; + } + + deduced.assign(unifier.results().begin(), unifier.results().end()); + for(auto [i, argument]: llvm::enumerate(deduced)) { + if(!argument.isNull()) { + continue; + } + + /// An unbound pack deduces as empty. + if(params->getParam(i)->isTemplateParameterPack()) { + argument = clang::TemplateArgument::CreatePackCopy(context, {}); + continue; + } + + return false; + } + return true; +} + +bool more_specialized(clang::ASTContext& context, + clang::ClassTemplatePartialSpecializationDecl* left, + clang::ClassTemplatePartialSpecializationDecl* right) { + auto matches = [&](clang::ClassTemplatePartialSpecializationDecl* pattern, + clang::ClassTemplatePartialSpecializationDecl* argument) { + auto params = pattern->getTemplateParameters(); + TypeUnifier unifier(context, params->getDepth(), params->size()); + return unifier.unify(pattern->getTemplateArgs().asArray(), + argument->getTemplateArgs().asArray()); + }; + + return matches(right, left) && !matches(left, right); +} + +} // namespace clice diff --git a/src/semantic/unifier.h b/src/semantic/unifier.h new file mode 100644 index 000000000..3d2b81888 --- /dev/null +++ b/src/semantic/unifier.h @@ -0,0 +1,100 @@ +#pragma once + +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclTemplate.h" + +namespace clice { + +/// Sema-free structural unification of template argument lists, replacing +/// `Sema::DeduceTemplateArguments` and `Sema::getMoreSpecializedPartialSpecialization` +/// for pseudo-instantiation. +/// +/// Differences from clang's deduction, by design: +/// - Works on sugared types: a parameter binds the argument *as written* +/// (e.g. `T = std::string`, not `T = std::basic_string`), so no +/// separate resugar pass is needed downstream. +/// - Binds template parameters to dependent arguments (`T = U`), which real +/// deduction never faces but pseudo-instantiation relies on. +/// - Skips conformance corners irrelevant to lookup (reference collapsing +/// adjustments, array bound promotion, constraint checks). Failure means +/// "this pattern doesn't match", which degrades to an unresolved name. +class TypeUnifier { +public: + using TemplateArguments = llvm::ArrayRef; + + explicit TypeUnifier(clang::ASTContext& context, unsigned depth, unsigned size) : + context(context), depth(depth), bindings(size) {} + + /// Unify `patterns` (a partial specialization's argument pattern or a + /// primary template's injected arguments) against `arguments`. On success, + /// every deduced parameter at `depth` is recorded in `bindings`. + bool unify(TemplateArguments patterns, TemplateArguments arguments); + + /// The deduced arguments, indexed by parameter index. Unbound parameters + /// hold a null TemplateArgument. + TemplateArguments results() const { + return bindings; + } + +private: + bool unify(const clang::TemplateArgument& pattern, const clang::TemplateArgument& argument); + + bool unify(clang::QualType pattern, clang::QualType argument); + + /// Extract a template-id (template + arguments) view of `type`, looking + /// through TST, ClassTemplateSpecializationDecl records and injected class + /// names. Returns false if `type` is not a template-id. + bool template_id(clang::QualType type, + clang::TemplateName& name, + TemplateArguments& arguments) const; + + /// Argument equality for repeated bindings: canonical structural + /// equality, with bare references to the same NTTP compared by decl + /// (canonical dependent expressions keep distinct node identities). + bool equivalent(const clang::TemplateArgument& lhs, const clang::TemplateArgument& rhs) const; + + bool bind(unsigned index, const clang::TemplateArgument& argument); + + /// Record one element of a pack parameter's binding while matching a + /// structured pack expansion pattern (`box...`) element-wise. + bool collect(unsigned index, const clang::TemplateArgument& argument); + + clang::ASTContext& context; + unsigned depth; + llvm::SmallVector bindings; + + /// Element-wise matching state for a structured pack expansion pattern: + /// while `expanding`, pack parameters accumulate one element per matched + /// argument instead of binding directly. `element_ordinal` is the index + /// of the argument currently being matched, so repeated pack references + /// within one element are checked for consistency instead of appended. + bool expanding = false; + unsigned element_ordinal = 0; + llvm::SmallVector, 2> elements; +}; + +/// Deduce the arguments of `params` at its own depth by matching `patterns` +/// against `arguments`. An unbound pack deduces as empty; any other unbound +/// parameter fails the deduction. Default arguments are not consulted here — +/// the caller fills them (with its own instantiation stack) before deducing. +/// +/// `patterns` and `params` come in the same pairings the resolver already +/// uses: injected arguments for primary templates and alias templates, +/// `getTemplateArgs()` for partial specializations. +bool deduce_arguments(clang::ASTContext& context, + clang::TemplateParameterList* params, + llvm::ArrayRef patterns, + llvm::ArrayRef arguments, + llvm::SmallVectorImpl& deduced); + +/// Partial ordering via symmetric deduction: `left` is more specialized than +/// `right` iff right's pattern matches left's and not vice versa. +bool more_specialized(clang::ASTContext& context, + clang::ClassTemplatePartialSpecializationDecl* left, + clang::ClassTemplatePartialSpecializationDecl* right); + +/// If `expr` is a (possibly parenthesized/casted) reference to a non-type +/// template parameter, return its declaration. +const clang::NonTypeTemplateParmDecl* referenced_nttp(const clang::Expr* expr); + +} // namespace clice diff --git a/tests/integration/lifecycle/anomaly.test.ts b/tests/integration/lifecycle/anomaly.test.ts index d73eff344..102d11811 100644 --- a/tests/integration/lifecycle/anomaly.test.ts +++ b/tests/integration/lifecycle/anomaly.test.ts @@ -58,6 +58,25 @@ test.skipIf(process.platform !== "linux")("worker crash reported", async ({ sess const serverPid = client.child.pid!; const workers = childPids(serverPid); expect(workers.length, "server should have spawned worker processes").toBeGreaterThan(0); + + // The crash handler is installed when the worker opens its log file; + // killing a worker before that point produces no backtrace. Wait + // until every spawned worker has a log file — the log names carry + // worker names, not pids, so per-process association is impossible + // and readiness of the whole fleet is the usable condition. + const logsDir = workspace.path(".clice/logs"); + for (let i = 0; i < 50; i++) { + const names = fs.existsSync(logsDir) + ? fs.readdirSync(logsDir, { recursive: true, encoding: "utf8" }) + : []; + const workerLogs = names.filter( + (name) => name.endsWith(".log") && path.basename(name) !== "master.log", + ); + if (workerLogs.length >= workers.length) { + break; + } + await sleep(200); + } // SIGABRT: dies via the same signal path as clice's own Debug traps, // exercises the crash handler, and is not intercepted by ASan // (handle_abort=0), unlike SIGSEGV which ASan turns into its own diff --git a/tests/unit/semantic/template_resolver_tests.cpp b/tests/unit/semantic/template_resolver_tests.cpp index 3ca2a4744..a5595eaf5 100644 --- a/tests/unit/semantic/template_resolver_tests.cpp +++ b/tests/unit/semantic/template_resolver_tests.cpp @@ -1,5 +1,6 @@ #include "test/test.h" #include "test/tester.h" +#include "semantic/semantics.h" #include "clang/AST/RecursiveASTVisitor.h" @@ -417,6 +418,146 @@ TEST_CASE(DefaultArgument) { )code"); } +TEST_CASE(TemplateTemplateReplace) { + run(R"code( + template + struct box {}; + + template + struct replace_first {}; + + template