diff --git a/docs/dependent-name.md b/docs/dependent-name.md new file mode 100644 index 00000000..fdbeb10e --- /dev/null +++ b/docs/dependent-name.md @@ -0,0 +1,83 @@ +# Concept + +So what is a [dependent name](https://en.cppreference.com/w/cpp/language/dependent_name)? It is a name that depends on a template parameter. For example, + +```cpp +struct X { + constexpr inline static int value = 0; +}; + +template +auto foo() { + return T::value; +} +``` + +In the above code, `T::value` is a dependent name because it depends on the template parameter `T`. The compiler cannot know what `T::value` is until the template is instantiated. This is the simplest example of a dependent name. In actual code, dependent names can be much more complex, like `std::vector>::value_type::value`. Overall, if the prefix of a nested name contains a template parameter, then the nested name is a dependent name. + +C++ standard will take a dependent name as a expression by default, if you want to indicate it is a type, you need to use `typename` keyword to tell the compiler it is a type. Otherwise, you will get a compile error. For example, + +```cpp +template > +void foo() { + T::value_type x; // error + typename T::value_type x; // okay +} +``` + +Sometimes, the name may be neither a type nor a value, it could be a template. In this case, you can use `template` keyword to tell the compiler it is a template. For example, + +```cpp +struct X { + template + constexpr inline static T value = sizeof(T); +}; + +template +auto foo() { + return T::template value; +} +``` + +Here, `value` in `X` is a template static variable, so you need to use `template` keyword to tell the compiler it is a template. + +# Issues around Dependent Name + +Here are some issues complaining about clangd's poor performance with templates, and most of them are caused by dependent names. So what exactly is the problem with dependent names? + +Consider the following code: + +```cpp +template +auto foo() { + return T::va^lue; +} +``` + +`^` represents the cursor position. If at this point, the user tries to click the "Go to Definition" button, they will get nothing. As we mentioned before, the compiler cannot know what `T::value` is until the template is instantiated. + +The same problem also exists in the code completion. For example, + +```cpp +template +auto foo() { + T::va^ +} +``` + +Similarly, nothing will be shown in the code completion list. The problem is that users don't actually need such a fully generic solution. They may only instantiate templates with a few types, but they have no way to tell the LSP. The new language feature `concept` introduced in C++ can help solve this problem, but it is still limited. + +Okay, okay, you say such a direct dependent name cannot be resolved. What about the following code? Why can I still not get the code completion list? Of course, `vec2[0]` is a vector, right? + +```cpp +template +auto foo() { + std::vector> vec2; + vec2[0].^ +} +``` + +According to the C++ standard, the type of `vec2[0]` is `std::vector>::reference`. Oh, damn, still a dependent name. + +# Heuristic + diff --git a/include/AST/Diagnostic.h b/include/AST/Diagnostic.h index 8122e453..6d41a2b4 100644 --- a/include/AST/Diagnostic.h +++ b/include/AST/Diagnostic.h @@ -9,6 +9,7 @@ #include #include #include +#include "clang/Sema/TemplateDeduction.h" namespace clice { diff --git a/include/AST/ParsedAST.h b/include/AST/ParsedAST.h index 1698150b..40cc89fe 100644 --- a/include/AST/ParsedAST.h +++ b/include/AST/ParsedAST.h @@ -5,6 +5,7 @@ namespace clice { struct ParsedAST { + clang::Sema& sema; clang::ASTContext& context; clang::Preprocessor& preproc; clang::FileManager& fileManager; diff --git a/include/AST/Resolver.h b/include/AST/Resolver.h new file mode 100644 index 00000000..c8461327 --- /dev/null +++ b/include/AST/Resolver.h @@ -0,0 +1,142 @@ +#pragma once + +#include "ParsedAST.h" +#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 example, `std::vector::value_type` can be simplified as `T`. +class DependentNameResolver { +public: + DependentNameResolver(clang::Sema& sema, clang::ASTContext& context) : sema(sema), context(context) {} + + clang::QualType simplify(clang::DependentNameType type); + + clang::QualType simplify(clang::DependentTemplateSpecializationType type); + + clang::ExprResult simplify(clang::DependentScopeDeclRefExpr* expr); + + clang::ExprResult simplify(clang::CXXDependentScopeMemberExpr* expr); + + clang::ExprResult simplify(clang::UnresolvedLookupExpr* expr); + + clang::DeclResult simplify(clang::UnresolvedUsingValueDecl* decl); + + clang::DeclResult simplify(clang::UnresolvedUsingTypenameDecl* decl); + + clang::ExprResult simplify(clang::CXXUnresolvedConstructExpr* expr); + +private: + clang::Type* lookup(const clang::CXXRecordDecl* CRD, const clang::IdentifierInfo* II) { + auto reuslt = CRD->lookup(II); + + // FIXME: currently, we assume there are no member template specialization + // that is the size of the partial specialization is 1, + for(auto member: reuslt) { + if(auto type = simplify(member)) { + return type; + } + } + + for(auto base: CRD->bases()) { + if(auto type = simplify(base.getType())) { + return type; + } + } + + return nullptr; + } + + void simplify(clang::NestedNameSpecifier* NNS, clang::IdentifierInfo* II) { + switch(NNS->getKind()) { + // when NNS is a type, e.g., `std::vector::` or `T::` + case clang::NestedNameSpecifier::TypeSpec: { + auto type = NNS->getAsType(); + + if(auto TST = type->getAs()) { + auto TD = TST->getTemplateName().getAsTemplateDecl(); + clang::QualType type; + if(auto CTD = llvm::dyn_cast(TD)) { + // `std::vector::` + } else if(auto TATD = llvm::dyn_cast(TD)) { + // template + // using Vector = std::vector::value_type; + // `Vector::` + } else if(auto TTPD = llvm::dyn_cast(TD)) { + // template typename List> + // using X = List::value_type; + // `List::` + } + + } else if(auto TTPT = type->getAs()) { + // `T::` + } + } + case clang::NestedNameSpecifier::TypeSpecWithTemplate: { + // TODO: + } + + // when NNS is still a dependent name, e.g., `std::vector::value_type::` + case clang::NestedNameSpecifier::Identifier: { + // TODO: + { + auto prefix = NNS->getPrefix(); + auto name = NNS->getAsIdentifier(); + } + } + } + } + + clang::Type* simplify(clang::QualType); + + clang::Type* simplify(const clang::NamedDecl* ND) {} + + clang::Type* simplify(const clang::TemplateSpecializationType* TST, const clang::IdentifierInfo* II) { + auto TD = TST->getTemplateName().getAsTemplateDecl(); + auto arguments = TST->template_arguments(); + + if(auto CTD = llvm::dyn_cast(TD)) { + // `std::vector::` + + // iterate all partial specializations + llvm::SmallVector partials; + CTD->getPartialSpecializations(partials); + + clang::sema::TemplateDeductionInfo Info(CTD->getLocation()); + for(auto partial: partials) { + if(auto error = sema.DeduceTemplateArguments(partial, arguments, Info); + error == clang::TemplateDeductionResult::Success) { + if(auto result = lookup(partial, II)) { + return result; + } + } + } + + // fallback to main template + if(auto result = lookup(CTD->getTemplatedDecl(), II)) { + return result; + } + + } else if(auto TATD = llvm::dyn_cast(TD)) { + // template + // using Vector = std::vector::value_type; + // `Vector::` + } else if(auto TTPD = llvm::dyn_cast(TD)) { + // template typename List> + // using X = List::value_type; + // `List::` + } + } + +private: + clang::Sema& sema; + clang::ASTContext& context; + std::stack templateStack; + std::stack> argumentsStack; +}; + +} // namespace clice diff --git a/src/AST/ParsedAST.cpp b/src/AST/ParsedAST.cpp index 7948e95c..e1e532e5 100644 --- a/src/AST/ParsedAST.cpp +++ b/src/AST/ParsedAST.cpp @@ -62,6 +62,7 @@ std::unique_ptr ParsedAST::build(llvm::StringRef filename, } auto result = new ParsedAST{ + .sema = instance->getSema(), .context = instance->getASTContext(), .preproc = instance->getPreprocessor(), .fileManager = instance->getFileManager(), diff --git a/src/AST/Resolver.cpp b/src/AST/Resolver.cpp new file mode 100644 index 00000000..d5206571 --- /dev/null +++ b/src/AST/Resolver.cpp @@ -0,0 +1,7 @@ +#include "AST/Resolver.h" + +namespace clice { + + + +} // namespace clice diff --git a/src/main.cpp b/src/main.cpp index 76f67636..eef72139 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,15 +4,118 @@ #include #include "llvm/Support/InitLLVM.h" #include "llvm/Support/TargetSelect.h" +#include "clang/Sema/TemplateDeduction.h" + +// llvm::FoldingSetNodeID id; +// clang::ClassTemplateSpecializationDecl::Profile(id, arguments, context); +// for(auto spec: decl->specializations()) { +// llvm::FoldingSetNodeID id2; +// spec->Profile(id2); +// llvm::outs() << "spec profile: " << (id == id2) << "\n"; +// llvm::outs() << "---------------------------------------------------------------\n"; +// for(std::size_t i = 0; i < arguments.size(); i++) { +// llvm::FoldingSetNodeID id3; +// arguments[i].Profile(id3, context); +// llvm::FoldingSetNodeID id4; +// spec->getTemplateArgs().asArray()[i].Profile(id4, context); +// llvm::outs() << "argument profile: " << (id3 == id4) << "\n"; +// arguments[i].dump(); +// spec->getTemplateArgs().asArray()[i].dump(); +// llvm::outs() << "---------------------------------------------------------------\n"; +// } +// } + +// if(auto specialization = decl->findSpecialization(arguments, pos)) { +// specialization->dump(); +// } + +// llvm::SmallVector partials; +// decl->getPartialSpecializations(partials); +// for(auto partial: partials) { +// if(auto specialization = +// decl->findPartialSpecialization(arguments, partial->getTemplateParameters(), pos)) { +// specialization->dump(); +// } +// } const char* source = R"( #include -int main(){ - int x; - return 0; -} + +template +struct rebind : std::__replace_first_arg<_Tp, _Up> {}; + +template +struct rebind<_Tp, _Up, std::void_t> { + using type = int; +}; + +template +struct test { + using result = typename rebind, T, void>::type; +}; + )"; +using namespace llvm; +using namespace clang; + +class Visitor : public clang::RecursiveASTVisitor { +public: + clang::Sema& sema; + clang::ASTContext& context; + + Visitor(clang::Sema& sema, clang::ASTContext& context) : sema(sema), context(context) {} + + void instantiateTemplate(clang::ClassTemplateDecl* CTD, llvm::ArrayRef TemplateArgs) { + void* InsertPos = nullptr; + + if(auto* Spec = CTD->findSpecialization(TemplateArgs, InsertPos)) { + // return Spec; + } + + ClassTemplatePartialSpecializationDecl* BestPartialSpec = nullptr; + + clang::sema::TemplateDeductionInfo Info(CTD->getLocation()); + + llvm::SmallVector partials; + CTD->getPartialSpecializations(partials); + + for(auto partial: partials) { + auto result = sema.DeduceTemplateArguments(partial, TemplateArgs, Info); + if(result == clang::TemplateDeductionResult::Success) { + llvm::outs() << "success\n"; + partial->dump(); + for(auto& arg: Info.takeSugared()->asArray()) { + llvm::outs() << "---------------------------------------\n"; + arg.dump(); + } + } + } + + // return cast(CTD->getTemplatedDecl()); + } + + bool VisitTemplateSpecializationType(const clang::TemplateSpecializationType* type) { + + if(auto CTD = llvm::dyn_cast(type->getTemplateName().getAsTemplateDecl())) { + if(CTD->getName() == "rebind") { + llvm::SmallVector arguments; + llvm::outs() << "count: " << type->template_arguments().size() << "\n"; + for(auto arg: type->template_arguments()) { + if(arg.getKind() == clang::TemplateArgument::ArgKind::Type) { + arguments.emplace_back(arg.getAsType().getCanonicalType()); + } else { + arguments.emplace_back(arg); + } + } + instantiateTemplate(CTD, arguments); + } + } + + return true; + } +}; + int main(int argc, const char** argv) { // clice::execute_path = argv[0]; auto args = std::vector{ @@ -20,9 +123,11 @@ int main(int argc, const char** argv) { "main.cpp", "-resource-dir", "/home/ykiko/C++/clice2/build/lib/clang/20", - "-Wall", }; auto preamble = clice::Preamble::build("main.cpp", source, args); auto parsedAST = clice::ParsedAST::build("main.cpp", source, args, preamble.get()); - parsedAST->tuDecl->dump(); + Visitor visitor(parsedAST->sema, parsedAST->context); + auto tu = parsedAST->context.getTranslationUnitDecl(); + // tu->dump(); + visitor.TraverseDecl(parsedAST->context.getTranslationUnitDecl()); } diff --git a/tests/test.cpp b/tests/test.cpp new file mode 100644 index 00000000..cf32aa11 --- /dev/null +++ b/tests/test.cpp @@ -0,0 +1,44 @@ +template +using void_t = void; + +template +struct replace_first_arg; + +template