From 6f7f2bb8a08219f51f29626e19eb330867058461 Mon Sep 17 00:00:00 2001 From: ykiko Date: Fri, 6 Sep 2024 18:36:24 +0800 Subject: [PATCH] add test for Resolver. --- .clang-format | 5 +- .gitmodules | 3 + CMakeLists.txt | 18 ++- deps/googletest | 1 + include/AST/ParsedAST.h | 6 +- include/AST/Resolver.h | 252 +++++----------------------------------- src/AST/Resolver.cpp | 119 ++++++++++++++++++- tests/Resolver.cpp | 75 ++++++++++++ tests/test.cpp | 56 --------- 9 files changed, 245 insertions(+), 290 deletions(-) create mode 160000 deps/googletest create mode 100644 tests/Resolver.cpp delete mode 100644 tests/test.cpp diff --git a/.clang-format b/.clang-format index bc8c33a5..072cce5a 100644 --- a/.clang-format +++ b/.clang-format @@ -32,7 +32,8 @@ AlignArrayOfStructures: Left PointerAlignment: Left BreakAfterAttributes: Leave -BreakBeforeBinaryOperators: None +BreakAfterReturnType: Automatic +PenaltyReturnTypeOnItsOwnLine: 100 BreakBeforeConceptDeclarations: Always BreakBeforeInlineASMColon: OnlyMultiline BreakBeforeTernaryOperators: true @@ -61,7 +62,7 @@ AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes RequiresClausePosition: OwnLine BinPackArguments: false -BinPackParameters: false +BinPackParameters: OnePerLine # Space SeparateDefinitionBlocks: Always diff --git a/.gitmodules b/.gitmodules index db271fee..0626f647 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "deps/toml"] path = deps/toml url = https://github.com/marzer/tomlplusplus.git +[submodule "deps/googletest"] + path = deps/googletest + url = https://github.com/google/googletest.git diff --git a/CMakeLists.txt b/CMakeLists.txt index b0a8dcb6..37a08b9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,16 +63,22 @@ file(GLOB_RECURSE SRC_FILES "${CMAKE_SOURCE_DIR}/src/*/*.cpp") target_sources(clice PRIVATE ${SRC_FILES} "${CMAKE_SOURCE_DIR}/src/main.cpp") target_include_directories(clice PRIVATE - ${CMAKE_SOURCE_DIR}/deps/libuv/include - ${CMAKE_SOURCE_DIR}/deps/spdlog/include - ${CMAKE_SOURCE_DIR}/deps/toml/include + "${CMAKE_SOURCE_DIR}/deps/libuv/include" + "${CMAKE_SOURCE_DIR}/deps/spdlog/include" + "${CMAKE_SOURCE_DIR}/deps/toml/include" ) target_link_libraries(clice PRIVATE uv spdlog::spdlog) if(CLICE_ENABLE_TEST) - add_executable(test) - target_clang(test) + add_executable(clice_test) + + # add googletest + add_subdirectory("${CMAKE_SOURCE_DIR}/deps/googletest") + target_include_directories(clice_test PRIVATE "${CMAKE_SOURCE_DIR}/deps/googletest/googletest/include") + target_link_libraries(clice_test PRIVATE gtest_main) + + target_clang(clice_test) file(GLOB_RECURSE AST_SRC_FILES "${CMAKE_SOURCE_DIR}/src/AST/*.cpp") - target_sources(test PRIVATE ${AST_SRC_FILES} "${CMAKE_SOURCE_DIR}/tests/test.cpp") + target_sources(clice_test PRIVATE ${AST_SRC_FILES} "${CMAKE_SOURCE_DIR}/tests/Resolver.cpp") endif() diff --git a/deps/googletest b/deps/googletest new file mode 160000 index 00000000..0953a17a --- /dev/null +++ b/deps/googletest @@ -0,0 +1 @@ +Subproject commit 0953a17a4281fc26831da647ad3fcd5e21e6473b diff --git a/include/AST/ParsedAST.h b/include/AST/ParsedAST.h index 6cc8bb11..b8ad3182 100644 --- a/include/AST/ParsedAST.h +++ b/include/AST/ParsedAST.h @@ -15,8 +15,10 @@ struct ParsedAST { std::unique_ptr action; std::unique_ptr instance; - static std::unique_ptr - build(llvm::StringRef filename, llvm::StringRef content, std::vector& args, Preamble* preamble); + static std::unique_ptr build(llvm::StringRef filename, + llvm::StringRef content, + std::vector& args, + Preamble* preamble = nullptr); }; } // namespace clice diff --git a/include/AST/Resolver.h b/include/AST/Resolver.h index f7d702a6..35db5f7a 100644 --- a/include/AST/Resolver.h +++ b/include/AST/Resolver.h @@ -1,249 +1,55 @@ #pragma once #include "ParsedAST.h" -#include #include #include +#include namespace clice { /// This class is used to resolve dependent names in the AST. -/// For dependent names, we cannot know the any information about the name until the template is instantiated. -/// This can be frustrating, you cannot get completion, you cannot get go-to-definition, etc. -/// To avoid this, we just use some heuristics to simplify the dependent names as normal type/expression. +/// For dependent names, we cannot know the any information about the name until +/// the template is instantiated. This can be frustrating, you cannot get +/// completion, you cannot get go-to-definition, etc. To avoid this, we just use +/// some heuristics to simplify the dependent names as normal type/expression. /// For example, `std::vector::value_type` can be simplified as `T`. class DependentNameResolver { + public: DependentNameResolver(clang::Sema& sema, clang::ASTContext& context) : sema(sema), context(context) {} - std::vector resolve(llvm::ArrayRef arguments) { - std::vector result; - for(auto arg: arguments) { - if(arg.getKind() == clang::TemplateArgument::ArgKind::Type) { - // check whether it is a TemplateTypeParmType. - if(auto type = llvm::dyn_cast(arg.getAsType())) { - const clang::TemplateTypeParmDecl* param = type->getDecl(); - if(param && param->hasDefaultArgument()) { - result.push_back(param->getDefaultArgument().getArgument()); - continue; - } - } - } - result.push_back(arg); - } - return result; - } + clang::QualType resolve(clang::QualType type); - clang::QualType dealias(clang::QualType type) { - if(auto DNT = type->getAs()) { - return clang::QualType(DNT, 0); - } else if(auto DTST = type->getAs()) { - auto NNS = clang::NestedNameSpecifier::Create( - context, - nullptr, - false, - dealias(clang::QualType(DTST->getQualifier()->getAsType(), 0)).getTypePtr()); - return context.getDependentTemplateSpecializationType(DTST->getKeyword(), - NNS, - DTST->getIdentifier(), - resolve(DTST->template_arguments())); - } else { - return type; - } - } + clang::QualType resolve(const clang::DependentNameType* DNT); - clang::QualType resolve(clang::QualType type) { - while(true) { - // llvm::outs() << - // "--------------------------------------------------------------------\n"; - // type.dump(); + /// lookup member in a given nested name specifier + bool lookup(llvm::SmallVector& result, + const clang::NestedNameSpecifier* NNS, + const clang::IdentifierInfo* II); - clang::MultiLevelTemplateArgumentList list; - if(auto DNT = type->getAs()) { - type = resolve(resolve(DNT->getQualifier(), DNT->getIdentifier())); - for(auto begin = arguments.rbegin(), end = arguments.rend(); begin != end; ++begin) { - list.addOuterTemplateArguments((*begin)->first, (*begin)->second, true); - } - type = sema.SubstType(dealias(type), list, {}, {}); - arguments.clear(); + bool lookup(llvm::SmallVector& result, + const clang::TemplateSpecializationType* TST, + const clang::IdentifierInfo* II); - } else if(auto DTST = type->getAs()) { - auto ND = resolve(DTST->getQualifier(), DTST->getIdentifier()); - if(auto TATD = llvm::dyn_cast(ND)) { - auto args = resolve(DTST->template_arguments()); + // lookup member in a given class template + // FIXME: search in base classes + bool lookup(llvm::SmallVector& result, + clang::ClassTemplateDecl* CTD, + const clang::IdentifierInfo* II, + llvm::ArrayRef arguments); - clang::Sema::CodeSynthesisContext context; - context.Entity = TATD; - context.Kind = clang::Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation; - context.TemplateArgs = args.data(); - sema.pushCodeSynthesisContext(context); - - list.addOuterTemplateArguments(TATD, args, true); - for(auto begin = arguments.rbegin(), end = arguments.rend(); begin != end; ++begin) { - list.addOuterTemplateArguments((*begin)->first, (*begin)->second, true); - } - - // llvm::outs() << "before: - // ----------------------------------------------------------------\n"; - // TATD->getTemplatedDecl()->getUnderlyingType().dump(); - type = dealias(TATD->getTemplatedDecl()->getUnderlyingType()); - // llvm::outs() << "arguments: - // -------------------------------------------------------------\n"; - // list.dump(); - type = sema.SubstType(type, list, {}, {}); - // type.dump(); - arguments.clear(); - - } else { - ND->dump(); - std::terminate(); - } - // return resolve(DTST); - } else if(auto LRT = type->getAs()) { - type = context.getLValueReferenceType(resolve(LRT->getPointeeType())); - } else { - return type; - } - } - } - - clang::QualType resolve(clang::NamedDecl* ND) { - if(auto TD = llvm::dyn_cast(ND)) { - return TD->getUnderlyingType(); - } else if(auto TAD = llvm::dyn_cast(ND)) { - return TAD->getUnderlyingType(); - } else { - ND->dump(); - std::terminate(); - } - } - - clang::NamedDecl* resolve(const clang::NestedNameSpecifier* NNS, const clang::IdentifierInfo* II) { - switch(NNS->getKind()) { - // prefix is an identifier, e.g. <...>::name:: - case clang::NestedNameSpecifier::SpecifierKind::Identifier: { - return lookup(resolve(resolve(NNS->getPrefix(), NNS->getAsIdentifier())), II); - } - - // prefix is a type, e.g. <...>::typename name:: - case clang::NestedNameSpecifier::SpecifierKind::TypeSpec: - case clang::NestedNameSpecifier::SpecifierKind::TypeSpecWithTemplate: { - return lookup(clang::QualType(NNS->getAsType(), 0), II); - } - - default: { - NNS->dump(); - std::terminate(); - } - } - } - - clang::NamedDecl* lookup(clang::QualType Type, const clang::IdentifierInfo* Name) { - clang::NamedDecl* TemplateDecl; - clang::ArrayRef arguments; - - // llvm::outs() << "--------------------------------------------------------------------\n"; - - if(auto TTPT = Type->getAs()) { - Type->dump(); - std::terminate(); - } else if(auto TST = Type->getAs()) { - auto TemplateName = TST->getTemplateName(); - TemplateDecl = TemplateName.getAsTemplateDecl(); - arguments = TST->template_arguments(); - } else if(auto DTST = Type->getAs()) { - TemplateDecl = resolve(DTST->getQualifier(), DTST->getIdentifier()); - arguments = DTST->template_arguments(); - } else if(auto RT = Type->getAs()) { - return RT->getDecl()->lookup(Name).front(); - } else { - Type->dump(); - std::terminate(); - } - - clang::NamedDecl* result; - clang::Sema::CodeSynthesisContext context; - - if(auto CTD = llvm::dyn_cast(TemplateDecl)) { - // always we check main template first - result = CTD->getTemplatedDecl()->lookup(Name).front(); - if(result) { - this->arguments.push_back(new std::pair>{ - TemplateDecl, - resolve(arguments), - }); - context.Entity = TemplateDecl; - context.Kind = clang::Sema::CodeSynthesisContext::TemplateInstantiation; - context.TemplateArgs = this->arguments.back()->second.data(); - sema.pushCodeSynthesisContext(context); - } else { - llvm::SmallVector paritals; - CTD->getPartialSpecializations(paritals); - - clang::sema::TemplateDeductionInfo info(CTD->getLocation()); - for(auto partial: paritals) { - auto deduction = sema.DeduceTemplateArguments(partial, arguments, info); - if(deduction == clang::TemplateDeductionResult::Success) { - auto decl = partial->lookup(Name).front(); - if(decl) { - result = decl; - - // TODO: fix resugar - // std::vector arguments; - auto arguments = info.takeSugared(); - for(auto& arg: arguments->asArray()) { - arg.dump(); - // if(arg.getKind() == clang::TemplateArgument::ArgKind::Type) { - // auto type = arg.getAsType(); - // type->dump(); - // if(type.isCanonical()) { - // auto TemplateDecl = - // llvm::dyn_cast(this->arguments.back()->first); - // auto params = TemplateDecl->getTemplateParameters(); - // auto TTPT = type->getAs(); - // unsigned Depth = TTPT->getDepth(); - // unsigned Index = TTPT->getIndex(); - // auto* TTPD = dyn_cast(params->getParam(Index)); - // arguments.push_back( - // clang::TemplateArgument(this->context.getTypeDeclType(TTPD))); - // } else { - // arguments.push_back(arg); - // } - // } else { - // arguments.push_back(arg); - // } - } - - this->arguments.push_back(new std::pair>{ - partial, - arguments->asArray(), - }); - context.Entity = partial; - context.Kind = clang::Sema::CodeSynthesisContext::TemplateInstantiation; - context.TemplateArgs = this->arguments.back()->second.data(); - sema.pushCodeSynthesisContext(context); - break; - } - } - } - } - - } else if(auto TATD = llvm::dyn_cast(TemplateDecl)) { - result = lookup(TATD->getTemplatedDecl()->getUnderlyingType(), Name); - } - - if(result == nullptr) { - Type.dump(); - std::terminate(); - } - - return result; - } + // replace the template arguments in the type, using the arguments in the frame + clang::QualType substitute(clang::QualType type); private: + struct Frame { + clang::NamedDecl* decl; + std::vector arguments; + }; + clang::Sema& sema; clang::ASTContext& context; - std::vector>*> arguments; + std::vector frames; }; } // namespace clice diff --git a/src/AST/Resolver.cpp b/src/AST/Resolver.cpp index d5206571..cfd16836 100644 --- a/src/AST/Resolver.cpp +++ b/src/AST/Resolver.cpp @@ -2,6 +2,123 @@ namespace clice { +clang::QualType DependentNameResolver::resolve(clang::QualType type) { + if(auto DNT = clang::dyn_cast(type)) { + return resolve(DNT); + } else { + std::terminate(); + } +} + +clang::QualType DependentNameResolver::resolve(const clang::DependentNameType* DNT) { + auto prefix = DNT->getQualifier(); + auto II = DNT->getIdentifier(); + llvm::SmallVector result; + lookup(result, prefix, II); + assert(result.size() == 1); + + // TODO: substitute template arguments + if(auto TAD = clang::dyn_cast(result.front())) { + return substitute(TAD->getUnderlyingType()); + } else { + std::terminate(); + } +} + +bool DependentNameResolver::lookup(llvm::SmallVector& result, + clang::ClassTemplateDecl* CTD, + const clang::IdentifierInfo* II, + llvm::ArrayRef arguments) { + + // main template first + auto decls = CTD->getTemplatedDecl()->lookup(II); + if(!decls.empty()) { + for(auto decl: decls) { + result.push_back(decl); + } + + frames.emplace_back(CTD, arguments); + return true; + } + + // 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) { + return false; + } + + auto decls = partial->lookup(II); + if(decls.empty()) { + return false; + } + + for(auto decl: decls) { + result.push_back(decl); + } + + auto list = info.takeSugared(); + frames.emplace_back(partial, list->asArray()); + delete list; + return true; + } +} + +bool DependentNameResolver::lookup(llvm::SmallVector& result, + const clang::NestedNameSpecifier* NNS, + const clang::IdentifierInfo* II) { + switch(NNS->getKind()) { + // prefix is an identifier, e.g. <...>::name:: + case clang::NestedNameSpecifier::SpecifierKind::Identifier: { + // return lookup(resolve(resolve(NNS->getPrefix(), NNS->getAsIdentifier())), + // II); + } + + // prefix is a type, e.g. <...>::typename name:: + case clang::NestedNameSpecifier::SpecifierKind::TypeSpec: + case clang::NestedNameSpecifier::SpecifierKind::TypeSpecWithTemplate: { + auto type = NNS->getAsType(); + if(auto TST = type->getAs()) { + auto TD = TST->getTemplateName().getAsTemplateDecl(); + // FIXME: consider default arguments + auto args = TST->template_arguments(); + if(auto CTD = llvm::dyn_cast(TD)) { + return lookup(result, CTD, II, args); + } + } + + std::terminate(); + } + + default: { + NNS->dump(); + std::terminate(); + } + } +} + +clang::QualType DependentNameResolver::substitute(clang::QualType 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, {}, {}); + + frames.clear(); + + return result; +} - } // namespace clice diff --git a/tests/Resolver.cpp b/tests/Resolver.cpp new file mode 100644 index 00000000..79b508a1 --- /dev/null +++ b/tests/Resolver.cpp @@ -0,0 +1,75 @@ +#include +#include + +using namespace clice; + +namespace { + +struct ASTVisitor : public clang::RecursiveASTVisitor { + clang::Sema& sema; + clang::QualType& result; + clang::ASTContext& context; + + bool VisitTypeAliasDecl(clang::TypeAliasDecl* decl) { + if(decl->getName() == "result") { + auto type = decl->getUnderlyingType(); + { + clang::Sema::CodeSynthesisContext context; + context.Kind = clang::Sema::CodeSynthesisContext::TemplateInstantiation; + context.Entity = decl; + context.TemplateArgs = nullptr; + sema.pushCodeSynthesisContext(context); + } + auto resolver = DependentNameResolver(sema, context); + result = resolver.resolve(type); + } + return true; + } +}; + +TEST(DependentNameResolver, resolve) { + std::vector args{ + "clang++", + "-std=c++20", + "main.cpp", + "-resource-dir=../build/lib/clang/20", + }; + + const char* code = R"( +template +struct type_list {}; + +template +struct A { + using type = type_list; +}; + +template +struct test { + using result = typename A::type; +}; +)"; + + auto parsedAST = ParsedAST::build("main.cpp", code, args); + auto decl = parsedAST->context.getTranslationUnitDecl(); + + clang::QualType result; + + ASTVisitor visitor{{}, parsedAST->sema, result, parsedAST->context}; + visitor.TraverseDecl(decl); + + { + auto TST = result->getAs(); + ASSERT_TRUE(TST); + ASSERT_EQ(TST->getTemplateName().getAsTemplateDecl()->getName(), "type_list"); + + auto args = TST->template_arguments(); + ASSERT_EQ(args.size(), 1); + auto T = llvm::dyn_cast(args[0].getAsType()); + ASSERT_TRUE(T); + ASSERT_EQ(T->getDecl()->getName(), "X"); + } +} + +} // namespace + diff --git a/tests/test.cpp b/tests/test.cpp deleted file mode 100644 index 785ff85a..00000000 --- a/tests/test.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include - -using namespace clice; - -const char* code = R"( -#include - -template -struct X {}; - -template typename List> -struct X> { - using type = std::vector; -}; - -template -struct test { - using result = typename X>::type; -}; -)"; - -struct ASTVisitor : public clang::RecursiveASTVisitor { - ParsedAST& ast; - - bool VisitTypeAliasDecl(clang::TypeAliasDecl* decl) { - if(decl->getName() == "result") { - auto type = decl->getUnderlyingType(); - clang::Sema::CodeSynthesisContext context; - context.Kind = clang::Sema::CodeSynthesisContext::TemplateInstantiation; - context.Entity = decl; - context.TemplateArgs = nullptr; - ast.sema.pushCodeSynthesisContext(context); - auto resolver = DependentNameResolver(ast.sema, ast.context); - auto resolved = resolver.resolve(type); - resolved->dump(); - } - return true; - } -}; - -int main() { - std::vector args{ - "clang++", - "-std=c++20", - "main.cpp", - "-resource-dir=/home/ykiko/C++/clice2/build/lib/clang/20", - }; - auto ast = ParsedAST::build("main.cpp", code, args, nullptr); - auto decl = ast->context.getTranslationUnitDecl(); - - ASTVisitor visitor{.ast = *ast}; - - visitor.TraverseDecl(decl); - - return 0; -}