#include "semantic/resolver.h" #include #include #include "clang/Sema/Template.h" #include "clang/Sema/TemplateDeduction.h" #include "clang/Sema/TreeTransform.h" namespace clice { namespace { template constexpr inline bool dependent_false = false; template void visitTemplateDeclContexts(clang::Decl* decl, const Callback& callback) { while(true) { if(llvm::isa(decl)) { break; } clang::TemplateParameterList* params = nullptr; if(auto TD = decl->getDescribedTemplate()) { params = TD->getTemplateParameters(); } if(auto CTPSD = llvm::dyn_cast(decl)) { params = CTPSD->getTemplateParameters(); } if(auto VTPSD = llvm::dyn_cast(decl)) { params = VTPSD->getTemplateParameters(); } if(params) { callback(decl, params); } decl = llvm::dyn_cast(decl->getDeclContext()); } } /// `Sema::SubstType` will not substitute template arguments in aliased types. /// For example: /// /// ```cpp /// template /// struct A { /// using base = std::vector; /// using type = typename base::reference; /// }; /// ``` /// /// In this case, if you call `SubstType` on `type`, the alias `base` will remain with /// the original type parameter `T`, without substituting it. Therefore, we need to /// manually resolve the alias before calling `SubstType`, which is what `DesugarOnly` /// aims to achieve. class DesugarOnly : public clang::TreeTransform { using Base = clang::TreeTransform; public: DesugarOnly(clang::Sema& sema) : Base(sema), context(sema.getASTContext()) {} // FIXME: desugar more types, e.g `UsingType`. clang::QualType TransformTypedefType(clang::TypeLocBuilder& TLB, clang::TypedefTypeLoc TL) { if(clang::TypedefNameDecl* TND = TL.getTypedefNameDecl()) { auto type = TransformType(TND->getUnderlyingType()); if(auto ET = llvm::dyn_cast(type)) { type = ET->getNamedType(); } TLB.pushTrivial(context, type, {}); return type; } return clang::QualType(); } clang::QualType TransformElaboratedType(clang::TypeLocBuilder& TLB, clang::ElaboratedTypeLoc TL) { clang::QualType type = TransformType(TL.getNamedTypeLoc().getType()); TLB.pushTrivial(context, type, {}); return type; } clang::QualType TransformInjectedClassNameType(clang::TypeLocBuilder& TLB, clang::InjectedClassNameTypeLoc TL) { auto ICT = TL.getTypePtr(); clang::QualType type = TransformType(ICT->getInjectedSpecializationType()); TLB.pushTrivial(context, type, {}); return type; } using Base::TransformTemplateSpecializationType; clang::QualType TransformTemplateSpecializationType(clang::TypeLocBuilder& TLB, clang::TemplateSpecializationTypeLoc TL) { if(TL.getTypePtr()->isTypeAlias()) { clang::QualType type = TransformType(TL.getTypePtr()->desugar()); TLB.pushTrivial(context, type, {}); return type; } return Base::TransformTemplateSpecializationType(TLB, TL); } private: clang::ASTContext& context; }; /// When deduce `TemplateSpecializationType` for partial specialization, `TemplateTypeParmType` /// will be deduced as canonical type. /// /// For example: /// ```cpp /// template /// struct A {}; /// /// template /// struct A {}; /// ``` /// If you use deduce `A` with `A`, you will get `T = type-parameter-0-0` instead of `U`. /// /// For code completion, we don't care about the whether it's a canonical type or not. But /// sometimes, the type may be needed to display to the user, e.g. inlay hints. In this case, /// we need to resugar the type to make it more readable. class ResugarOnly : public clang::TreeTransform { public: ResugarOnly(clang::Sema& sema, clang::Decl* decl) : TreeTransform(sema), context(sema.getASTContext()) { visitTemplateDeclContexts(decl, [&](clang::Decl* decl, clang::TemplateParameterList* params) { lists.push_back(params); }); std::ranges::reverse(lists); } 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(); auto index = TTPT->getIndex(); auto isPack = TTPT->isParameterPack(); auto param = llvm::cast(lists[depth]->getParam(index)); type = context.getTemplateTypeParmType(depth, index, isPack, param); } return TLB.push(type).getType(); } private: clang::ASTContext& context; llvm::SmallVector lists; }; /// A helper class to record the instantiation stack. struct InstantiationStack { using Arguments = llvm::SmallVector; using TemplateArguments = llvm::ArrayRef; llvm::SmallVector> data; void clear() { data.clear(); } bool empty() const { return data.empty(); } auto state() const { return data; } void rewind(auto& point) { data = std::move(point); } void push(clang::Decl* decl, TemplateArguments arguments) { data.emplace_back(decl, arguments); } void pop() { data.pop_back(); } auto& frames() { return data; } }; /// The core class that performs pseudo template instantiation. class PseudoInstantiator : public clang::TreeTransform { public: using Base = clang::TreeTransform; using TemplateArguments = llvm::ArrayRef; using TemplateDeductionInfo = clang::sema::TemplateDeductionInfo; PseudoInstantiator(clang::Sema& sema, llvm::DenseMap& resolved) : Base(sema), sema(sema), context(sema.getASTContext()), resolved(resolved) {} public: /// Check whether the given template arguments match the template parameters and /// complete the default template arguments if necessary. bool checkTemplateArguments(clang::TemplateDecl* TD, TemplateArguments& arguments, llvm::SmallVectorImpl& out) { auto list = TD->getTemplateParameters(); out.reserve(list->size()); for(auto arg: arguments) { out.emplace_back(arg); } if(out.size() != list->size()) { for(auto i = out.size(); i < list->size(); ++i) { auto param = list->getParam(i); auto TTPD = llvm::dyn_cast(param); if(TTPD && TTPD->hasDefaultArgument()) { auto type = TTPD->getDefaultArgument().getArgument().getAsType(); auto state = stack.state(); stack.push(TD, out); auto result = TransformType(instantiate(type)); if(result.isNull()) { stack.rewind(state); return false; } out.emplace_back(result); stack.rewind(state); } } } if(out.size() != list->size()) { return false; } return true; } /// Deduce the template arguments for the given declaration. If deduction succeeds, push the /// declaration and its deduced template arguments to the instantiation stack. template bool deduceTemplateArguments(Decl* decl, TemplateArguments arguments) { clang::TemplateParameterList* list = nullptr; TemplateArguments params = {}; if constexpr(std::is_same_v) { const clang::ClassTemplateDecl* CTD = decl; list = CTD->getTemplateParameters(); params = list->getInjectedTemplateArgs(context); } else if constexpr(std::is_same_v) { const clang::ClassTemplatePartialSpecializationDecl* CTPSD = decl; list = CTPSD->getTemplateParameters(); params = CTPSD->getTemplateArgs().asArray(); } else if constexpr(std::is_same_v) { const clang::TypeAliasTemplateDecl* TATD = decl; list = TATD->getTemplateParameters(); params = 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(); if(!success) { return false; } /// made up class template context. if(stack.empty()) { visitTemplateDeclContexts(llvm::dyn_cast(decl->getDeclContext()), [&](clang::Decl* decl, clang::TemplateParameterList* params) { stack.push(decl, params->getInjectedTemplateArgs(context)); }); std::ranges::reverse(stack.frames()); } llvm::SmallVector output(deduced.begin(), deduced.end()); stack.push(decl, output); return true; } using lookup_result = clang::DeclContext::lookup_result; /// If this class and its base class have members with the same name, `DeclContext::lookup` /// will return multiple declarations in order from the base class to the derived class, so we /// use the last declaration. clang::Decl* preferred(lookup_result members) { clang::Decl* decl = nullptr; std::ranges::for_each(members, [&](auto member) { decl = member; }); return decl; } lookup_result lookup(clang::QualType type, clang::DeclarationName name) { clang::Decl* TD = nullptr; llvm::ArrayRef args; type = TransformType(type); if(type.isNull()) { return lookup_result(); } if(auto TST = type->getAs()) { TD = TST->getTemplateName().getAsTemplateDecl(); args = TST->template_arguments(); } else if(auto DTST = type->getAs()) { auto& template_name = DTST->getDependentTemplateName(); /// FIXME: operators does't have the name. auto name = template_name.getName().getIdentifier(); if(!name) { return {}; } if(auto decl = preferred(lookup(template_name.getQualifier(), name))) { TD = decl; args = DTST->template_arguments(); } } if(!TD) { return lookup_result(); } #ifndef NDEBUG if(TemplateResolver::debug) { llvm::outs() << "--------------------------------------------------------------\n"; llvm::outs() << "lookup: { " << name << " } in { " << type.getAsString() << " }\n"; } #endif if(auto CTD = llvm::dyn_cast(TD)) { return lookup(CTD, name, args); } else if(auto TATD = llvm::dyn_cast(TD)) { if(deduceTemplateArguments(TATD, args)) { return lookup(instantiate(TATD->getTemplatedDecl()->getUnderlyingType()), name); } } return lookup_result(); } /// Look up the name in the given nested name specifier. lookup_result lookup(const clang::NestedNameSpecifier* NNS, clang::DeclarationName name) { if(!NNS) { return lookup_result(); } /// Search the resolved entities first. if(auto iter = resolved.find(NNS); iter != resolved.end()) { return lookup(iter->second, name); } switch(NNS->getKind()) { case clang::NestedNameSpecifier::Identifier: { /// If the prefix is `Identifier`, it must be a dependent name. /// For example: `std::vector::value_type::type` /// ^~~~~~~~~~~~~~~~~~~~~~~~~^ /// identifier /// /// So resolve it recursively. auto type = instantiate(preferred(lookup(NNS->getPrefix(), NNS->getAsIdentifier()))); resolved.try_emplace(NNS, type); return lookup(type, name); } case clang::NestedNameSpecifier::TypeSpec: { /// If the prefix is `TypeSpec`, it must be a type. return lookup(clang::QualType(NNS->getAsType(), 0), name); } case clang::NestedNameSpecifier::Global: case clang::NestedNameSpecifier::Namespace: case clang::NestedNameSpecifier::NamespaceAlias: case clang::NestedNameSpecifier::Super: { llvm::errs() << "Unexpected name specifier\n"; std::abort(); } } return lookup_result(); } /// Look up the name in the bases of the given class. Keep stack unchanged. lookup_result lookupInBases(clang::CXXRecordDecl* CRD, clang::DeclarationName name) { if(!CRD->hasDefinition()) { return lookup_result(); } for(auto base: CRD->bases()) { if(auto type = base.getType(); type->isDependentType()) { auto state = stack.state(); if(auto members = lookup(instantiate(type), name); !members.empty()) { return members; } stack.rewind(state); } } return lookup_result(); } /// Look up the name in the given class template. We first search the name in the /// primary template, if failed, try dependent base classes, if still failed, try /// partial specializations. **Note that this function will be responsible for pushing /// the class template and its template arguments to the instantiation stack**. lookup_result lookup(clang::ClassTemplateDecl* CTD, clang::DeclarationName name, TemplateArguments visibleArguments) { llvm::SmallVector arguments; if(!checkTemplateArguments(CTD, visibleArguments, arguments)) { return lookup_result(); } /// Try to find the name in the partial specializations. llvm::SmallVector partials; CTD->getPartialSpecializations(partials); for(auto partial: partials) { if(deduceTemplateArguments(partial, arguments)) { if(auto members = partial->lookup(name); !members.empty()) { return members; } if(auto members = lookupInBases(partial, name); !members.empty()) { return members; } stack.pop(); } } if(deduceTemplateArguments(CTD, arguments)) { auto CRD = CTD->getTemplatedDecl(); /// First, try to find the name in the primary template. if(auto members = CRD->lookup(name); !members.empty()) { return members; } /// If failed, try to find the name in the dependent base classes. if(auto members = lookupInBases(CRD, name); !members.empty()) { return members; } /// If failed, pop the decl and deduced template arguments. stack.pop(); } /// FIXME: try full specializations?. return lookup_result(); } /// Instantiate the given type and clear the instantiation stack. clang::QualType instantiate(clang::QualType type) { if(!type->isDependentType()) { return type; } auto& contexts = sema.CodeSynthesisContexts; assert(contexts.empty() && "CodeSynthesisContexts should be empty"); assert(!stack.frames().empty() && "Instantiation stack should not be empty"); std::ranges::for_each(stack.frames(), [&](auto& frame) { clang::Sema::CodeSynthesisContext context; context.Entity = frame.first; context.TemplateArgs = frame.second.data(); context.Kind = clang::Sema::CodeSynthesisContext::TemplateInstantiation; contexts.push_back(context); }); clang::MultiLevelTemplateArgumentList list; std::ranges::for_each(stack.frames() | std::views::reverse, [&](auto& frame) { list.addOuterTemplateArguments(frame.first, frame.second, true); }); type = DesugarOnly(sema).TransformType(type); #ifndef NDEBUG if(TemplateResolver::debug) { llvm::SmallString<128> args; for(auto& frame: stack.frames()) { args += "<"; for(auto& arg: frame.second) { switch(arg.getKind()) { case clang::TemplateArgument::Null: case clang::TemplateArgument::Type: { args += arg.getAsType().getAsString(); break; } case clang::TemplateArgument::Declaration: { args += arg.getAsDecl()->getNameAsString(); break; } case clang::TemplateArgument::NullPtr: { args += "nullptr"; break; } case clang::TemplateArgument::Integral: { arg.getAsIntegral().toString(args, 10); break; } case clang::TemplateArgument::StructuralValue: { args += arg.getAsStructuralValue().getAsString( context, arg.getStructuralValueType()); break; } case clang::TemplateArgument::Template: { args += arg.getAsTemplate().getAsTemplateDecl()->getNameAsString(); break; } case clang::TemplateArgument::TemplateExpansion: { args += arg.getAsTemplateOrTemplatePattern() .getAsTemplateDecl() ->getNameAsString(); break; } case clang::TemplateArgument::Expression: { args += arg.getAsExpr()->getStmtClassName(); break; } case clang::TemplateArgument::Pack: { args += "pack"; break; } } args += ", "; } args += ">, "; } std::print("try instantiate [{}] with arguments: {}\n", type.getAsString(), args.str().str()); } #endif auto result = sema.SubstType(type, list, {}, {}); #ifndef NDEBUG if(TemplateResolver::debug) { std::print("instantiate success: {}\n", result.getAsString()); } #endif stack.clear(); contexts.clear(); return result; } clang::QualType instantiate(clang::Decl* decl) { if(!decl) { return clang::QualType(); } if(auto TND = llvm::dyn_cast(decl)) { return instantiate(TND->getUnderlyingType()); } else if(auto RD = llvm::dyn_cast(decl)) { return clang::QualType(RD->getTypeForDecl(), 0); } // FIXME: more possibilities? return clang::QualType(); } /// FIXME: Use a general method to dig holes. 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->getDeclContext()->isStdNamespace()) { return clang::QualType(); } if(TD->getName() == "allocator_traits") { assert(TST->template_arguments().size() == 1 && "Invalid template arguments"); auto Alloc = TST->template_arguments()[0].getAsType(); if(member->getName() == "rebind_alloc") { auto T = arguments[0].getAsType(); /// Alloc::rebind::other auto prefix = clang::NestedNameSpecifier::Create(context, nullptr, Alloc.getTypePtr()); auto rebind = sema.getPreprocessor().getIdentifierInfo("rebind"); auto DTST = context.getDependentTemplateSpecializationType( clang::ElaboratedTypeKeyword::None, clang::DependentTemplateStorage(prefix, rebind, false), arguments); prefix = clang::NestedNameSpecifier::Create(context, prefix, DTST.getTypePtr()); auto other = sema.getPreprocessor().getIdentifierInfo("other"); auto DNT = context.getDependentNameType(clang::ElaboratedTypeKeyword::Typename, prefix, other); auto result = PseudoInstantiator(sema, resolved).TransformType(DNT); if(!result.isNull()) { return result; } /// SomeAllocator -> SomeAllocator if(auto TST = Alloc->getAs()) { llvm::SmallVector replaceArguments = {T}; llvm::SmallVector canonicalArguments; for(auto& arg: replaceArguments) { canonicalArguments.emplace_back(context.getCanonicalTemplateArgument(arg)); } return context.getTemplateSpecializationType(TST->getTemplateName(), replaceArguments, canonicalArguments); } } } return clang::QualType(); } public: using Base::TransformType; clang::QualType TransformType(clang::QualType type) { if(type.isNull()) { return clang::QualType(); } if(!type->isDependentType()) { return type; } return Base::TransformType(DesugarOnly(sema).TransformType(type)); } /// Sometimes the outer argument is just a simple type `T` and actually cannot make /// instantiation continue. In this case, we try to use its default argument to replace it, /// which may make the instantiation continue. /// For example: `template > using type = T::value_type`. clang::QualType TransformTemplateTypeParmType(clang::TypeLocBuilder& TLB, clang::TemplateTypeParmTypeLoc TL, bool = false) { if(clang::TemplateTypeParmDecl* TTPD = TL.getDecl()) { if(TTPD->hasDefaultArgument()) { const clang::TemplateArgument& argument = TTPD->getDefaultArgument().getArgument(); clang::QualType type = TransformType(argument.getAsType()); TLB.pushTrivial(context, type, clang::SourceLocation()); return type; } } TLB.push(TL.getType()); return TL.getType(); } clang::QualType TransformDependentNameType(clang::TypeLocBuilder& TLB, clang::DependentNameTypeLoc TL, bool DeducedTSTContext = false) { auto DNT = TL.getTypePtr(); /// Search the resolved entities first. if(auto iter = resolved.find(DNT); iter != resolved.end()) { TLB.pushTrivial(context, iter->second, {}); return iter->second; } auto NNS = TransformNestedNameSpecifierLoc(TL.getQualifierLoc()).getNestedNameSpecifier(); auto type = TransformType(instantiate(preferred(lookup(NNS, DNT->getIdentifier())))); resolved.try_emplace(DNT, type); TLB.pushTrivial(context, type, {}); return type; } using Base::TransformDependentTemplateSpecializationType; /// For a `DependentTemplateSpecializationType`, the template name can be either an alias /// template or a class template. If it is an alias template, we can simplify it directly /// by transforming the alias template's underlying type. However, if it is a class /// template, we need additional context (e.g., suffix name) to simplify it correctly. In /// this case, we defer further transformation to `TransformDependentNameType`, which can /// handle this scenario. Thus, if the template is not an alias template, we keep it /// unchanged here. clang::QualType TransformDependentTemplateSpecializationType( clang::TypeLocBuilder& TLB, clang::DependentTemplateSpecializationTypeLoc TL) { auto DTST = TL.getTypePtr(); /// Search the resolved entities first. if(auto iter = resolved.find(DTST); iter != resolved.end()) { TLB.pushTrivial(context, iter->second, {}); return iter->second; } auto NNS = TransformNestedNameSpecifierLoc(TL.getQualifierLoc()).getNestedNameSpecifier(); if(!NNS) { return clang::QualType(); } /// FIXME: figure out here. clang::TemplateArgumentListInfo info; using iterator = clang::TemplateArgumentLocContainerIterator< clang::DependentTemplateSpecializationTypeLoc>; TransformTemplateArguments(iterator(TL, 0), iterator(TL, TL.getNumArgs()), info); llvm::SmallVector arguments; for(auto& arg: info.arguments()) { arguments.push_back(arg.getArgument()); } /// FIXME: operator does't have a name. auto name = DTST->getDependentTemplateName().getName().getIdentifier(); if(!name) { return clang::QualType(); } /// Try resolve the hole. if(auto result = hole(NNS, name, arguments); !result.isNull()) { resolved.try_emplace(DTST, result); TLB.pushTrivial(context, result, {}); return result; } /// The `lookup` may change the instantiation stack, save the current state. auto state = stack.state(); if(auto decl = preferred(lookup(NNS, name))) { /// FIXME: Current implementation results in duplicated lookup. /// Cache the result of `lookup` to avoid duplicated lookup. if(auto TATD = llvm::dyn_cast(decl)) { if(deduceTemplateArguments(TATD, DTST->template_arguments())) { clang::QualType type = TransformType(instantiate(TATD->getTemplatedDecl()->getUnderlyingType())); resolved.try_emplace(DTST, type); TLB.pushTrivial(context, type, {}); return type; } } stack.rewind(state); } /// FIXME: figure out here. auto result = context.getDependentTemplateSpecializationType( DTST->getKeyword(), clang::DependentTemplateStorage(NNS, name, false), arguments); return TLB.push(result).getType(); } /// FIXME: handle more cases. 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()); TLB.pushTrivial(context, type, {}); return type; } } return Base::TransformDecltypeType(TLB, TL); } private: clang::Sema& sema; clang::ASTContext& context; InstantiationStack stack; llvm::DenseMap& resolved; }; } // namespace clang::QualType TemplateResolver::resolve(clang::QualType type) { PseudoInstantiator instantiator(sema, resolved); return instantiator.TransformType(type); } clang::QualType TemplateResolver::resugar(clang::QualType type, clang::Decl* decl) { ResugarOnly resugar(sema, decl); return resugar.TransformType(type); } TemplateResolver::lookup_result TemplateResolver::lookup(const clang::NestedNameSpecifier* NNS, clang::DeclarationName name) { PseudoInstantiator instantiator(sema, resolved); return instantiator.lookup(NNS, name); } } // namespace clice