From 258f8d8f1cb25639a4e9bf4398e75115efc5c13f Mon Sep 17 00:00:00 2001 From: ykiko Date: Tue, 12 Nov 2024 22:12:55 +0800 Subject: [PATCH] Refactor `TemplateResolver`. --- include/Compiler/Compiler.h | 6 + include/Compiler/Resolver.h | 37 +- src/Compiler/Compiler.cpp | 1 + src/Compiler/Resolver.cpp | 480 +++++++++++-------- tests/TemplateResolver/alias-argument.cpp | 19 - tests/TemplateResolver/alias-dependent.cpp | 20 - tests/TemplateResolver/alias-template.cpp | 19 - tests/TemplateResolver/base-dependent.cpp | 16 - tests/TemplateResolver/multi-level.cpp | 23 - tests/TemplateResolver/multi-nested.cpp | 14 - tests/TemplateResolver/nested-template.cpp | 19 - tests/TemplateResolver/partial-dependent.cpp | 19 - tests/TemplateResolver/single-level.cpp | 13 - tests/TemplateResolver/vector.cpp | 7 - unittests/AST/Resolver.cpp | 362 ++++++++++++-- unittests/Index/Tester.h | 4 - unittests/Test.h | 1 + 17 files changed, 637 insertions(+), 423 deletions(-) delete mode 100644 tests/TemplateResolver/alias-argument.cpp delete mode 100644 tests/TemplateResolver/alias-dependent.cpp delete mode 100644 tests/TemplateResolver/alias-template.cpp delete mode 100644 tests/TemplateResolver/base-dependent.cpp delete mode 100644 tests/TemplateResolver/multi-level.cpp delete mode 100644 tests/TemplateResolver/multi-nested.cpp delete mode 100644 tests/TemplateResolver/nested-template.cpp delete mode 100644 tests/TemplateResolver/partial-dependent.cpp delete mode 100644 tests/TemplateResolver/single-level.cpp delete mode 100644 tests/TemplateResolver/vector.cpp diff --git a/include/Compiler/Compiler.h b/include/Compiler/Compiler.h index 7a97028e..04692cb7 100644 --- a/include/Compiler/Compiler.h +++ b/include/Compiler/Compiler.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace clice { @@ -70,6 +71,10 @@ public: return *buffer; } + TemplateResolver& resolver() { + return *m_Resolver; + } + private: void ExecuteAction(); @@ -79,6 +84,7 @@ private: std::unique_ptr action; std::unique_ptr instance; std::unique_ptr buffer; + std::unique_ptr m_Resolver; }; } // namespace clice diff --git a/include/Compiler/Resolver.h b/include/Compiler/Resolver.h index 3d123723..12669d63 100644 --- a/include/Compiler/Resolver.h +++ b/include/Compiler/Resolver.h @@ -14,15 +14,46 @@ class TemplateResolver { public: TemplateResolver(clang::Sema& sema) : sema(sema) {} - clang::QualType resolve(clang::QualType type); - // TODO: // use a relative clear way to resolve `UnresolvedLookupExpr`. - // TODO: desugar UsingType. + + clang::QualType resolve(clang::QualType type); + + clang::QualType resolve(const clang::DependentNameType* type) { + if(auto iter = resolved.find(type); iter != resolved.end()) { + return iter->second; + } + + return resolve(clang::QualType(type, 0)); + } + + clang::QualType resolve(const clang::DependentTemplateSpecializationType* type) { + if(auto iter = resolved.find(type); iter != resolved.end()) { + return iter->second; + } + + return resolve(clang::QualType(type, 0)); + } + + clang::ExprResult resolve(clang::DependentScopeDeclRefExpr* expr); + + clang::ExprResult resolve(clang::CXXDependentScopeMemberExpr* expr); + + clang::ExprResult resolve(clang::CXXUnresolvedConstructExpr* expr); + + clang::ExprResult resolve(clang::UnresolvedLookupExpr* expr); + + void resolve(clang::UnresolvedUsingValueDecl* decl); + + void resolve(clang::UnresolvedUsingTypenameDecl* decl); + + void resolve(clang::UnresolvedUsingType* type); + private: clang::Sema& sema; + llvm::DenseMap resolved; }; } // namespace clice diff --git a/src/Compiler/Compiler.cpp b/src/Compiler/Compiler.cpp index 2803e5cd..d789bc5e 100644 --- a/src/Compiler/Compiler.cpp +++ b/src/Compiler/Compiler.cpp @@ -61,6 +61,7 @@ bool Compiler::applyPCM(llvm::StringRef filepath, llvm::StringRef name) { void Compiler::buildAST() { action = std::make_unique(); ExecuteAction(); + m_Resolver = std::make_unique(instance->getSema()); } void Compiler::generatePCH(llvm::StringRef outpath, std::uint32_t bound, bool endAtStart) { diff --git a/src/Compiler/Resolver.cpp b/src/Compiler/Resolver.cpp index e4bfb666..664281ad 100644 --- a/src/Compiler/Resolver.cpp +++ b/src/Compiler/Resolver.cpp @@ -2,12 +2,15 @@ #include #include #include +#include namespace clice { namespace { -/// `Sema::SubstType` will not substitute in aliased types, e.g. +/// `Sema::SubstType` will not substitute template arguments in aliased types. +/// For example: +/// /// ```cpp /// template /// struct A { @@ -15,8 +18,11 @@ namespace { /// using type = typename base::reference; /// }; /// ``` -/// In this case, if you call `SubstType` on `type`, base will keep the same. -/// So we need to dealias the type first before calling `SubstType`. +/// +/// 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 `DealiasOnly` +/// aims to achieve. class DealiasOnly : public clang::TreeTransform { public: DealiasOnly(clang::Sema& sema) : TreeTransform(sema), context(sema.getASTContext()) {} @@ -34,45 +40,235 @@ public: return clang::QualType(); } -private: - clang::ASTContext& context; -}; - -class TemplateResolverImpl : public clang::TreeTransform { -private: - struct Frame { - clang::NamedDecl* decl; - llvm::ArrayRef arguments; - }; - - using Names = llvm::SmallVector; - using Base = clang::TreeTransform; - -public: - TemplateResolverImpl(clang::Sema& sema) : TreeTransform(sema), sema(sema), context(sema.getASTContext()) {} - - clang::QualType TransformElaboratedType(clang::TypeLocBuilder& TLB, clang::ElaboratedTypeLoc TL) { + clang::QualType TransformElaboratedType(clang::TypeLocBuilder& TLB, + clang::ElaboratedTypeLoc TL) { clang::QualType type = TransformType(TL.getNamedTypeLoc().getType()); TLB.pushTrivial(context, type, {}); return type; } - 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(); +private: + clang::ASTContext& context; +}; + +/// A helper class to record the instantiation stack. +struct InstantiationStack { + using TemplateArguments = llvm::ArrayRef; + + llvm::SmallVector> data; + + void clear() { + data.clear(); + } + + 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); + } + + 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 lookup_result = clang::DeclContext::lookup_result; + + PseudoInstantiator(clang::Sema& sema, llvm::DenseMap& resolved) : + Base(sema), sema(sema), resolved(resolved) {} + +public: + /// 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, const clang::IdentifierInfo* name) { + clang::Decl* TD = nullptr; + llvm::ArrayRef args; + type = TransformType(type); + + if(auto TST = type->getAs()) { + TD = TST->getTemplateName().getAsTemplateDecl(); + args = TST->template_arguments(); + } else if(auto DTST = type->getAs()) { + if(auto decl = preferred(lookup(DTST->getQualifier(), DTST->getIdentifier()))) { + TD = decl; + args = DTST->template_arguments(); } - TLB.pushTrivial(context, type, {}); + } + + if(!TD) { + return lookup_result(); + } + + if(auto CTD = llvm::dyn_cast(TD)) { + return lookup(CTD, name, args); + } else if(auto TATD = llvm::dyn_cast(TD)) { + stack.push(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, const clang::IdentifierInfo* name) { + /// 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: + case clang::NestedNameSpecifier::TypeSpecWithTemplate: { + /// If the prefix is `TypeSpec` or `TypeSpecWithTemplate`, 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::terminate(); + } + } + } + + /// 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, + const clang::IdentifierInfo* name, + TemplateArguments arguments) { + /// First, try to find the name in the primary template. + if(auto members = CTD->getTemplatedDecl()->lookup(name); !members.empty()) { + stack.push(CTD, arguments); + return members; + } + + /// Try to find the member in the base class. + for(auto base: CTD->getTemplatedDecl()->bases()) { + if(auto type = base.getType(); type->isDependentType()) { + /// Because we instantiate the base class, this will clear the instantiation stack. + /// If the lookup fails, we need to rewind the stack to try the next base class. + auto state = stack.state(); + stack.push(CTD, arguments); + + if(auto members = lookup(instantiate(type), name); !members.empty()) { + return members; + } + + stack.rewind(state); + } + } + + /// Try to find the name in the partial specializations. + llvm::SmallVector partials; + CTD->getPartialSpecializations(partials); + + for(auto partial: partials) { + auto info = clang::sema::TemplateDeductionInfo(clang::SourceLocation()); + auto result = sema.DeduceTemplateArguments(partial, arguments, info); + if(result == clang::TemplateDeductionResult::Success) { + if(auto members = partial->lookup(name); !members.empty()) { + auto list = info.takeSugared(); + stack.push(partial, list->asArray()); + // FIXME: should we delete the list? + return members; + } + } + } + + /// 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"); + + 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::ranges::views::reverse, [&](auto& frame) { + list.addOuterTemplateArguments(frame.first, frame.second, true); + }); + + type = DealiasOnly(sema).TransformType(type); + auto result = sema.SubstType(type, list, {}, {}); + + 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(); } +public: /// 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 to replace it, which - /// may make the instantiation continue. + /// 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 SuppressObjCLifetime = false) { @@ -80,7 +276,7 @@ public: if(TTPD->hasDefaultArgument()) { const clang::TemplateArgument& argument = TTPD->getDefaultArgument().getArgument(); clang::QualType type = TransformType(argument.getAsType()); - TLB.pushTrivial(context, type, {}); + TLB.pushTrivial(sema.getASTContext(), type, clang::SourceLocation()); return type; } } @@ -89,205 +285,77 @@ public: return TL.getType(); } - /// `TransformDependentNameType` is the most important function in `TemplateResolver`. - /// Everytime we meet a `DependentNameType`, we try to resolve it to a simpler type. clang::QualType TransformDependentNameType(clang::TypeLocBuilder& TLB, clang::DependentNameTypeLoc TL, bool DeducedTSTContext = false) { - auto NNS = TransformNestedNameSpecifierLoc(TL.getQualifierLoc()).getNestedNameSpecifier(); - auto II = TL.getTypePtr()->getIdentifier(); + auto DNT = TL.getTypePtr(); - Names names; - if(lookup(names, NNS, II) && names.size() == 1) { - clang::QualType type = TransformType(substitute(names.front())); - TLB.pushTrivial(context, type, {}); - return type; + /// Search the resolved entities first. + if(auto iter = resolved.find(DNT); iter != resolved.end()) { + TLB.pushTrivial(sema.getASTContext(), iter->second, {}); + return iter->second; } - return clang::QualType(); + + auto NNS = TransformNestedNameSpecifierLoc(TL.getQualifierLoc()).getNestedNameSpecifier(); + auto type = TransformType(instantiate(preferred(lookup(NNS, DNT->getIdentifier())))); + resolved.try_emplace(DNT, type); + TLB.pushTrivial(sema.getASTContext(), type, {}); + return type; } using Base::TransformDependentTemplateSpecializationType; - clang::QualType TransformDependentTemplateSpecializationType(clang::TypeLocBuilder& TLB, - clang::DependentTemplateSpecializationTypeLoc TL) { - auto NNS = TransformNestedNameSpecifierLoc(TL.getQualifierLoc()).getNestedNameSpecifier(); - auto II = TL.getTypePtr()->getIdentifier(); + /// 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(); - Names names; - auto copy = frames; - if(lookup(names, NNS, II) && names.size() == 1) { - if(auto TATD = llvm::dyn_cast(names.front())) { - // FIXME: Transform template arguments - frames.emplace_back(TATD, TL.getTypePtr()->template_arguments()); - clang::QualType type = TransformType(substitute(TATD->getTemplatedDecl()->getUnderlyingType())); - TLB.pushTrivial(context, type, {}); + /// Search the resolved entities first. + if(auto iter = resolved.find(DTST); iter != resolved.end()) { + TLB.pushTrivial(sema.getASTContext(), iter->second, {}); + return iter->second; + } + + auto NNS = TransformNestedNameSpecifierLoc(TL.getQualifierLoc()).getNestedNameSpecifier(); + // FIXME: Transform template arguments. + + /// The `lookup` may change the instantiation stack, save the current state. + auto state = stack.state(); + if(auto decl = preferred(lookup(NNS, DTST->getIdentifier()))) { + if(auto TATD = llvm::dyn_cast(decl)) { + stack.push(TATD, DTST->template_arguments()); + clang::QualType type = TATD->getTemplatedDecl()->getUnderlyingType(); + type = TransformType(instantiate(type)); + resolved.try_emplace(DTST, type); + TLB.pushTrivial(sema.getASTContext(), type, {}); return type; } + stack.rewind(state); } - frames = std::move(copy); - TLB.push(TL.getType()); - return TL.getType(); - } - - /// look up the name in the given type. - bool lookup(Names& names, clang::QualType type, const clang::IdentifierInfo* II) { - type = TransformType(type); - - llvm::outs() << "lookup { " + II->getName() + " } in { " + type.getAsString() + " }\n"; - - clang::TemplateDecl* TD; - llvm::ArrayRef args; - - if(auto TST = type->getAs()) { - TD = TST->getTemplateName().getAsTemplateDecl(); - args = TST->template_arguments(); - } else if(auto DTST = type->getAs()) { - Names members; - if(lookup(members, DTST->getQualifier(), DTST->getIdentifier()) && members.size() == 1) { - TD = llvm::dyn_cast(members.front()); - args = DTST->template_arguments(); - } else { - return false; - } - } - - if(auto CTD = llvm::dyn_cast(TD)) { - return lookup(names, CTD, II, args); - } else if(auto TATD = llvm::dyn_cast(TD)) { - frames.emplace_back(TATD, args); - return lookup(names, substitute(TATD->getTemplatedDecl()->getUnderlyingType()), II); - } - - return true; - } - - /// look up the name in the given nested name specifier. - bool lookup(Names& names, const clang::NestedNameSpecifier* NNS, const clang::IdentifierInfo* II) { - switch(NNS->getKind()) { - case clang::NestedNameSpecifier::Identifier: { - Names members; - if(lookup(members, NNS->getPrefix(), NNS->getAsIdentifier()) && members.size() == 1) { - return lookup(names, substitute(members.front()), II); - } - } - - case clang::NestedNameSpecifier::TypeSpec: - case clang::NestedNameSpecifier::TypeSpecWithTemplate: { - return lookup(names, clang::QualType(NNS->getAsType(), 0), II); - } - - default: { - break; - } - } - return false; - } - - /// look up the name in the given class template. We first try to find the name in the main - /// template, if failed, we try to find the name in the base classes, if still failed, we try to find - /// the name in the partial specializations. - bool lookup(Names& names, - clang::ClassTemplateDecl* CTD, - const clang::IdentifierInfo* II, - llvm::ArrayRef arguments) { - // main template first - auto decls = CTD->getTemplatedDecl()->lookup(II); - if(!decls.empty()) { - std::size_t count = 0; - for(auto decl: decls) { - names.push_back(decl); - } - - frames.emplace_back(CTD, arguments); - return true; - } - - for(auto base: CTD->getTemplatedDecl()->bases()) { - // store the current state - auto copy = frames; - - // try to find the member in the base class - frames.emplace_back(CTD, arguments); - if(lookup(names, substitute(base.getType()), II)) { - return true; - } - - // if failed, restore the state - frames = std::move(copy); - } - - // if failed, try partial specializations - llvm::SmallVector partials; - CTD->getPartialSpecializations(partials); - - for(auto partial: partials) { - clang::sema::TemplateDeductionInfo info(partial->getLocation()); - if(sema.DeduceTemplateArguments(partial, arguments, info) != clang::TemplateDeductionResult::Success) { - break; - } - - auto decls = partial->lookup(II); - if(decls.empty()) { - break; - } - - for(auto decl: decls) { - names.push_back(decl); - } - - // NOTE: takeSugared will take the ownership of the list - auto list = info.takeSugared(); - frames.emplace_back(partial, list->asArray()); - // FIXME: should we delete the list? - // delete list; - return true; - } - - return false; - } - - clang::QualType substitute(clang::QualType type) { - type = DealiasOnly(sema).TransformType(type); - - clang::MultiLevelTemplateArgumentList list; - for(auto begin = frames.rbegin(), end = frames.rend(); begin != end; ++begin) { - list.addOuterTemplateArguments(begin->decl, begin->arguments, true); - } - - for(auto frame: frames) { - clang::Sema::CodeSynthesisContext context; - context.Entity = frame.decl; - context.TemplateArgs = frame.arguments.data(); - context.Kind = clang::Sema::CodeSynthesisContext::TemplateInstantiation; - sema.pushCodeSynthesisContext(context); - } - - auto result = sema.SubstType(type, list, {}, {}); - llvm::outs() << "substitute { " + type.getAsString() + " } to { " + result.getAsString() + " }\n"; - frames.clear(); - return result; - } - - clang::QualType substitute(clang::NamedDecl* decl) { - if(auto TND = llvm::dyn_cast(decl)) { - return substitute(TND->getUnderlyingType()); - } - - return clang::QualType(); + return Base::TransformDependentTemplateSpecializationType(TLB, TL); } private: clang::Sema& sema; - clang::ASTContext& context; - std::vector frames; + InstantiationStack stack; + llvm::DenseMap& resolved; }; } // namespace clang::QualType TemplateResolver::resolve(clang::QualType type) { - TemplateResolverImpl resolver(sema); - return resolver.TransformType(type); + PseudoInstantiator instantiator(sema, resolved); + return instantiator.TransformType(type); } +clang::QualType TemplateResolver(const clang::DependentTemplateSpecializationType* type) {} + } // namespace clice diff --git a/tests/TemplateResolver/alias-argument.cpp b/tests/TemplateResolver/alias-argument.cpp deleted file mode 100644 index 56a9d6d5..00000000 --- a/tests/TemplateResolver/alias-argument.cpp +++ /dev/null @@ -1,19 +0,0 @@ -template -struct type_list {}; - -template -struct A { - using type = T1; -}; - -template -struct B { - using base = A; - using type = type_list; -}; - -template -struct test { - using result = typename B::type; - using expect = type_list; -}; diff --git a/tests/TemplateResolver/alias-dependent.cpp b/tests/TemplateResolver/alias-dependent.cpp deleted file mode 100644 index d99dccfb..00000000 --- a/tests/TemplateResolver/alias-dependent.cpp +++ /dev/null @@ -1,20 +0,0 @@ -template -struct type_list {}; - -template -struct A { - using type = type_list; -}; - -template -struct B { - using base = A; - using type = typename base::type; -}; - -template -struct test { - using result = typename B::type; - using expect = type_list; -}; - diff --git a/tests/TemplateResolver/alias-template.cpp b/tests/TemplateResolver/alias-template.cpp deleted file mode 100644 index 122f8ca6..00000000 --- a/tests/TemplateResolver/alias-template.cpp +++ /dev/null @@ -1,19 +0,0 @@ -template -struct type_list {}; - -template -struct A { - using type = type_list; -}; - -template -struct B { - template - using type = typename A::type; -}; - -template -struct test { - using result = typename B::template type; - using expect = type_list; -}; diff --git a/tests/TemplateResolver/base-dependent.cpp b/tests/TemplateResolver/base-dependent.cpp deleted file mode 100644 index b10128a6..00000000 --- a/tests/TemplateResolver/base-dependent.cpp +++ /dev/null @@ -1,16 +0,0 @@ -template -struct type_list {}; - -template -struct A { - using type = type_list; -}; - -template -struct B : A {}; - -template -struct test { - using result = typename B::type; - using expect = type_list; -}; diff --git a/tests/TemplateResolver/multi-level.cpp b/tests/TemplateResolver/multi-level.cpp deleted file mode 100644 index d5b31a4c..00000000 --- a/tests/TemplateResolver/multi-level.cpp +++ /dev/null @@ -1,23 +0,0 @@ -template -struct type_list {}; - -template -struct A { - using type = type_list; -}; - -template -struct B { - using type = typename A::type; -}; - -template -struct C { - using type = typename B::type; -}; - -template -struct test { - using result = typename C::type; - using expect = type_list; -}; diff --git a/tests/TemplateResolver/multi-nested.cpp b/tests/TemplateResolver/multi-nested.cpp deleted file mode 100644 index 6c33e6da..00000000 --- a/tests/TemplateResolver/multi-nested.cpp +++ /dev/null @@ -1,14 +0,0 @@ -template -struct type_list {}; - -template -struct A { - using self = A; - using type = type_list; -}; - -template -struct test { - using result = typename A::self::self::self::self::self::type; - using expect = type_list; -}; diff --git a/tests/TemplateResolver/nested-template.cpp b/tests/TemplateResolver/nested-template.cpp deleted file mode 100644 index ac72c563..00000000 --- a/tests/TemplateResolver/nested-template.cpp +++ /dev/null @@ -1,19 +0,0 @@ -template -struct type_list {}; - -template -struct A { - template - struct B { - template - struct C { - using type = type_list; - }; - }; -}; - -template -struct test { - using result = typename A::template B::template C::type; - using expect = type_list; -}; diff --git a/tests/TemplateResolver/partial-dependent.cpp b/tests/TemplateResolver/partial-dependent.cpp deleted file mode 100644 index 08061855..00000000 --- a/tests/TemplateResolver/partial-dependent.cpp +++ /dev/null @@ -1,19 +0,0 @@ -template -struct type_list {}; - -template -struct A {}; - -template -struct B {}; - -template typename HKT> -struct B> { - using type = type_list; -}; - -template -struct test { - using result = typename B>::type; - using expect = type_list; -}; diff --git a/tests/TemplateResolver/single-level.cpp b/tests/TemplateResolver/single-level.cpp deleted file mode 100644 index 7c7ec5ca..00000000 --- a/tests/TemplateResolver/single-level.cpp +++ /dev/null @@ -1,13 +0,0 @@ -template -struct type_list {}; - -template -struct A { - using type = type_list; -}; - -template -struct test { - using result = typename A::type; - using expect = type_list; -}; diff --git a/tests/TemplateResolver/vector.cpp b/tests/TemplateResolver/vector.cpp deleted file mode 100644 index 56adbc6a..00000000 --- a/tests/TemplateResolver/vector.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -template -struct test { - using result = typename std::vector::reference; - using expect = T&; -}; diff --git a/unittests/AST/Resolver.cpp b/unittests/AST/Resolver.cpp index 0f85a833..701456d8 100644 --- a/unittests/AST/Resolver.cpp +++ b/unittests/AST/Resolver.cpp @@ -6,27 +6,23 @@ namespace { using namespace clice; -std::vector compileArgs = { - "clang++", - "-std=c++20", - "main.cpp", - "-resource-dir", - "/home/ykiko/C++/clice2/build/lib/clang/20", -}; - -struct Visitor : public clang::RecursiveASTVisitor { - clang::QualType result; - clang::QualType expect; - clice::Compiler compiler; - - Visitor(llvm::StringRef content) : compiler("main.cpp", content, compileArgs) { - compiler.buildAST(); +struct TemplateResolverTester : public clang::RecursiveASTVisitor { + TemplateResolverTester(llvm::StringRef code) { + compileArgs = { + "clang++", + "-std=c++20", + "main.cpp", + "-resource-dir", + "/home/ykiko/C++/clice2/build/lib/clang/20", + }; + compiler = std::make_unique("main.cpp", code, compileArgs); + compiler->buildAST(); + test(); } bool VisitTypeAliasDecl(clang::TypeAliasDecl* decl) { - if(decl->getName() == "result") { - TemplateResolver resolver(compiler.sema()); - result = resolver.resolve(decl->getUnderlyingType()); + if(decl->getName() == "input") { + input = decl->getUnderlyingType(); } if(decl->getName() == "expect") { @@ -36,36 +32,320 @@ struct Visitor : public clang::RecursiveASTVisitor { return true; } - void test() { - auto decl = compiler.context().getTranslationUnitDecl(); - TraverseDecl(decl); + void test(std::source_location location = std::source_location::current()) { + TraverseDecl(compiler->tu()); + EXPECT_FALSE(input.isNull()); + EXPECT_FALSE(expect.isNull()); + + auto& resolver = compiler->resolver(); + clang::QualType result = resolver.resolve(input); EXPECT_EQ(result.getCanonicalType(), expect.getCanonicalType()); + + /// Test whether cache works. + clang::QualType result2 = resolver.resolve(input); + EXPECT_EQ(result, result2); } + + clang::QualType input; + clang::QualType expect; + std::vector compileArgs; + std::unique_ptr compiler; }; -void match(clang::QualType type, std::string name, std::initializer_list args) { - auto TST = type->getAs(); - ASSERT_TRUE(TST); - ASSERT_EQ(TST->getTemplateName().getAsTemplateDecl()->getName(), name); +TEST(TemplateResolver, TypeParameterType) { + TemplateResolverTester tester(R"cpp( +template +struct A { + using type = T; +}; - auto template_args = TST->template_arguments(); - ASSERT_EQ(template_args.size(), args.size()); - - auto iter = args.begin(); - for(auto arg: template_args) { - auto T = llvm::dyn_cast(arg.getAsType()); - ASSERT_TRUE(T); - ASSERT_EQ(T->getDecl()->getName(), *iter); - ++iter; - } +template +struct test { + using input = typename A::type; + using expect = X; +}; +)cpp"); } -TEST(clice, TemplateResolver) { - foreachFile("TemplateResolver", [&](std::string file, llvm::StringRef content) { - Visitor visitor(content); - visitor.test(); - llvm::outs() << fmt::format(fg(fmt::color::yellow_green), "[TemplateResolver: {}]\n", file); - }); +TEST(TemplateResolver, SingleLevel) { + TemplateResolverTester tester(R"cpp( +template +struct type_list {}; + +template +struct A { + using type = type_list; +}; + +template +struct test { + using input = typename A::type; + using expect = type_list; +}; +)cpp"); +} + +TEST(TemplateResolver, SingleLevelNotDependent) { + TemplateResolverTester tester(R"cpp( +template +struct A { + using type = int; +}; + +template +struct test { + using input = typename A::type; + using expect = int; +}; +)cpp"); +} + +TEST(TemplateResolver, MultiLevel) { + TemplateResolverTester tester(R"cpp( +template +struct type_list {}; + +template +struct A { + using type = type_list; +}; + +template +struct B { + using type = typename A::type; +}; + +template +struct C { + using type = typename B::type; +}; + +template +struct test { + using input = typename C::type; + using expect = type_list; +}; +)cpp"); +} + +TEST(TemplateResolver, MultiLevelNotDependent) { + TemplateResolverTester tester(R"cpp( +template +struct A { + using type = int; +}; + +template +struct B { + using type = typename A::type; +}; + +template +struct C { + using type = typename B::type; +}; + +template +struct test { + using input = typename C::type; + using expect = int; +}; +)cpp"); +} + +TEST(TemplateResolver, ArgumentDependent) { + TemplateResolverTester tester(R"cpp( +template +struct type_list {}; + +template +struct A { + using type = T1; +}; + +template +struct B { + using type = type_list; +}; + +template +struct test { + using input = typename B::type>::type; + using expect = type_list; +}; +)cpp"); +} + +TEST(TemplateResolver, AliasArgument) { + TemplateResolverTester tester(R"cpp( +template +struct type_list {}; + +template +struct A { + using type = T1; +}; + +template +struct B { + using base = A; + using type = type_list; +}; + +template +struct test { + using input = typename B::type; + using expect = type_list; +}; +)cpp"); +} + +TEST(TemplateResolver, AliasDependent) { + TemplateResolverTester tester(R"cpp( +template +struct type_list {}; + +template +struct A { + using type = type_list; +}; + +template +struct B { + using base = A; + using type = typename base::type; +}; + +template +struct test { + using input = typename B::type; + using expect = type_list; +}; +)cpp"); +} + +TEST(TemplateResolver, AliasTemplate) { + TemplateResolverTester tester(R"cpp( +template +struct type_list {}; + +template +struct A { + using type = type_list; +}; + +template +struct B { + template + using type = typename A::type; +}; + +template +struct test { + using input = typename B::template type; + using expect = type_list; +}; +)cpp"); +} + +TEST(TemplateResolver, BaseDependent) { + TemplateResolverTester tester(R"cpp( +template +struct type_list {}; + +template +struct A { + using type = type_list; +}; + +template +struct B : A {}; + +template +struct test { + using input = typename B::type; + using expect = type_list; +}; +)cpp"); +} + +TEST(TemplateResolver, MultiNested) { + TemplateResolverTester tester(R"cpp( +template +struct type_list {}; + +template +struct A { + using self = A; + using type = type_list; +}; + +template +struct test { + using input = typename A::self::self::self::self::self::type; + using expect = type_list; +}; +)cpp"); +} + +TEST(TemplateResolver, DependentMemberClass) { + TemplateResolverTester tester(R"cpp( +template +struct type_list {}; + +template +struct A { + template + struct B { + template + struct C { + using type = type_list; + }; + }; +}; + +template +struct test { + using input = typename A::template B::template C::type; + using expect = type_list; +}; +)cpp"); +} + +TEST(TemplateResolver, PartialSpecialization) { + TemplateResolverTester tester(R"cpp( +template +struct type_list {}; + +template +struct A {}; + +template +struct B {}; + +template typename HKT> +struct B> { + using type = type_list; +}; + +template +struct test { + using input = typename B>::type; + using expect = type_list; +}; +)cpp"); +} + +TEST(TemplateResolver, Standard) { + TemplateResolverTester tester(R"cpp( +#include + +template +struct test { + using input = typename std::vector::reference; + using expect = T&; +}; +)cpp"); } } // namespace diff --git a/unittests/Index/Tester.h b/unittests/Index/Tester.h index 98802468..c26b075e 100644 --- a/unittests/Index/Tester.h +++ b/unittests/Index/Tester.h @@ -8,8 +8,6 @@ namespace clice { -namespace { - using namespace index; /// Recursively test the binary index has the totally same content @@ -96,6 +94,4 @@ struct IndexerTester { } }; -} // namespace - } // namespace clice diff --git a/unittests/Test.h b/unittests/Test.h index 396ca39a..f2601a04 100644 --- a/unittests/Test.h +++ b/unittests/Test.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace clice { std::string test_dir();