diff --git a/include/AST/FilterASTVisitor.h b/include/AST/FilterASTVisitor.h new file mode 100644 index 00000000..c4a225f4 --- /dev/null +++ b/include/AST/FilterASTVisitor.h @@ -0,0 +1,230 @@ +#pragma once + +#include "Basic/SourceCode.h" +#include "Compiler/AST.h" +#include "clang/AST/RecursiveASTVisitor.h" + +namespace clice { + +/// A visitor class that extends clang::RecursiveASTVisitor to traverse +/// AST nodes with an additional filtering mechanism. +template +class FilteredASTVisitor : public clang::RecursiveASTVisitor { +private: + using Base = clang::RecursiveASTVisitor; + + bool filterable(clang::SourceRange range) { + auto [begin, end] = range; + + /// FIXME: Most of implicit decls don't have valid source range. Is it possible + /// that we want to visit them sometimes? + if(begin.isInvalid() || end.isInvalid()) { + return true; + } + + if(begin == end) { + /// We are only interested in expansion location. + auto [fid, offset] = AST.getDecomposedLoc(AST.getExpansionLoc(begin)); + + /// For builtin files, we don't want to visit them. + if(AST.isBuiltinFile(fid)) { + return true; + } + + /// Filter out if the location is not in the interested file. + if(interestedOnly) { + auto interested = AST.getInterestedFile(); + if(fid != interested) { + return true; + } + + if(targetRange && !targetRange->contains(offset)) { + return true; + } + } + } else { + auto [beginFID, beginOffset] = AST.getDecomposedLoc(AST.getExpansionLoc(begin)); + auto [endFID, endOffset] = AST.getDecomposedLoc(AST.getExpansionLoc(end)); + + if(AST.isBuiltinFile(beginFID) || AST.isBuiltinFile(endFID)) { + return true; + } + + if(interestedOnly) { + auto interested = AST.getInterestedFile(); + if(beginFID != interested && endFID != interested) { + return true; + } + + if(targetRange && !targetRange->intersects({beginOffset, endOffset})) { + return true; + } + } + } + + return false; + } + + Derived& getDerived() { + return static_cast(*this); + } + +public: +#define CHECK_DERIVED_IMPL(func) \ + static_assert(std::same_as, \ + "Derived class should not implement this method"); + + bool TraverseDecl(clang::Decl* decl) { + CHECK_DERIVED_IMPL(TraverseDecl); + + if(!decl) { + return true; + } + + if(llvm::isa(decl)) { + return Base::TraverseDecl(decl); + } + + if(filterable(decl->getSourceRange()) || decl->isImplicit()) { + return true; + } + + /// We don't want to visit implicit instantiation. + if(auto SD = llvm::dyn_cast(decl)) { + if(SD->getSpecializationKind() == clang::TSK_ImplicitInstantiation) { + return true; + } + } + + if(auto SD = llvm::dyn_cast(decl)) { + if(SD->getTemplateSpecializationKind() == clang::TSK_ImplicitInstantiation) { + return true; + } + } + + if(auto SD = llvm::dyn_cast(decl)) { + if(SD->getSpecializationKind() == clang::TSK_ImplicitInstantiation) { + return true; + } + } + + return Base::TraverseDecl(decl); + } + + bool TraverseStmt(clang::Stmt* stmt) { + CHECK_DERIVED_IMPL(TraverseStmt); + + if(!stmt || filterable(stmt->getSourceRange())) { + return true; + } + + return Base::TraverseStmt(stmt); + } + + /// FIXME: See https://github.com/llvm/llvm-project/issues/117687. + bool TraverseAttributedStmt(clang::AttributedStmt* stmt) { + CHECK_DERIVED_IMPL(TraverseAttributedStmt); + + if(!stmt || filterable(stmt->getSourceRange())) { + return true; + } + + for(auto attr: stmt->getAttrs()) { + Base::TraverseAttr(const_cast(attr)); + } + + return Base::TraverseAttributedStmt(stmt); + } + + /// We don't want to node withou location information. + constexpr bool TraverseType [[gnu::always_inline]] (clang::QualType) { + CHECK_DERIVED_IMPL(TraverseType); + return true; + } + + bool TraverseTypeLoc(clang::TypeLoc loc) { + CHECK_DERIVED_IMPL(TraverseTypeLoc); + + if(!loc || filterable(loc.getSourceRange())) { + return true; + } + + /// FIXME: Workaround for `QualifiedTypeLoc`. + if(auto QL = loc.getAs()) { + return Base::TraverseTypeLoc(QL.getUnqualifiedLoc()); + } + + return Base::TraverseTypeLoc(loc); + } + + bool TraverseAttr(clang::Attr* attr) { + CHECK_DERIVED_IMPL(TraverseAttr); + + if(!attr || filterable(attr->getRange())) { + return true; + } + + return Base::TraverseAttr(attr); + } + + /// We don't want to node withou location information. + constexpr bool TraverseNestedNameSpecifier + [[gnu::always_inline]] (clang::NestedNameSpecifier*) { + CHECK_DERIVED_IMPL(TraverseNestedNameSpecifier); + return true; + } + + /// Note that `RecursiveASTVisitor` doesn't have `VisitNestedNameSpecifier`, + /// it is our own implementation. + bool VisitNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc loc) { + return true; + } + + bool TraverseNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc loc) { + CHECK_DERIVED_IMPL(TraverseNestedNameSpecifierLoc); + + if(!loc || filterable(loc.getSourceRange())) { + return true; + } + + if(!getDerived().VisitNestedNameSpecifierLoc(loc)) { + return false; + } + + return Base::TraverseNestedNameSpecifierLoc(loc); + } + + /// Note that `RecursiveASTVisitor` doesn't have `VisitNestedNameSpecifier`, + /// it is our own implementation. + bool VisitConceptReference(clang::ConceptReference* reference) { + return true; + } + + bool TraverseConceptReference(clang::ConceptReference* reference) { + CHECK_DERIVED_IMPL(TraverseConceptReference); + + if(!reference || filterable(reference->getSourceRange())) { + return true; + } + + if(!getDerived().VisitConceptReference(reference)) { + return false; + } + + return Base::TraverseConceptReference(reference); + } + +#undef CHECK_DERIVED_IMPL + +protected: + FilteredASTVisitor(ASTInfo& AST, + bool interestedOnly, + std::optional targetRange) : + AST(AST), interestedOnly(interestedOnly), targetRange(targetRange) {} + + ASTInfo& AST; + bool interestedOnly = true; + std::optional targetRange; +}; + +} // namespace clice diff --git a/include/AST/Semantic.h b/include/AST/Semantic.h index 4b7b1dcb..3a83546d 100644 --- a/include/AST/Semantic.h +++ b/include/AST/Semantic.h @@ -4,25 +4,20 @@ #include "Resolver.h" #include "SymbolKind.h" #include "RelationKind.h" -#include "Compiler/Compilation.h" -#include "clang/AST/RecursiveASTVisitor.h" +#include "FilterASTVisitor.h" namespace clice { template -class SemanticVisitor : public clang::RecursiveASTVisitor> { +class SemanticVisitor : public FilteredASTVisitor> { public: - using Base = clang::RecursiveASTVisitor; + using Base = FilteredASTVisitor; SemanticVisitor(ASTInfo& info, bool mainFileOnly = false) : - sema(info.sema()), pp(info.pp()), resolver(info.resolver()), srcMgr(info.srcMgr()), - tokBuf(info.tokBuf()), info(info), mainFileOnly(mainFileOnly) {} + Base(info, mainFileOnly, {}), sema(info.sema()), pp(info.pp()), resolver(info.resolver()), + srcMgr(info.srcMgr()), tokBuf(info.tokBuf()), info(info), mainFileOnly(mainFileOnly) {} public: - consteval bool VisitImplicitInstantiation() { - return true; - } - Derived& getDerived() { return static_cast(*this); } @@ -170,23 +165,6 @@ public: #define VISIT_TYPE(type) bool Visit##type(const clang::type* type) #define VISIT_TYPELOC(type) bool Visit##type(clang::type loc) -#define TRAVERSE_DECL(type) bool Traverse##type(clang::type* decl) - - TRAVERSE_DECL(Decl) { - if(!decl) { - return true; - } - - if(!llvm::isa(decl) && needFilter(decl->getLocation())) { - return true; - } - - decls.push_back(decl); - auto result = Base::TraverseDecl(decl); - decls.pop_back(); - return result; - } - VISIT_DECL(ImportDecl) { auto tokens = tokBuf.expandedTokens(decl->getSourceRange()); @@ -380,6 +358,17 @@ public: return true; } + bool TraverseFunctionDecl(clang::FunctionDecl* decl) { + if(!decl) { + return true; + } + + decls.emplace_back(decl); + auto result = Base::TraverseFunctionDecl(decl); + decls.pop_back(); + return result; + } + /// void foo() { ... } /// ^~~~ declaration/definition VISIT_DECL(FunctionDecl) { @@ -513,36 +502,18 @@ public: /// requires Foo; /// ^~~~ reference - bool TraverseConceptReference(clang::ConceptReference* reference) { + bool VisitConceptReference(clang::ConceptReference* reference) { auto decl = reference->getNamedConcept(); auto location = reference->getConceptNameLoc(); handleDeclOccurrence(decl, RelationKind::Reference, location); handleRelation(decl, RelationKind::Reference, decl, location); - return Base::TraverseConceptReference(reference); + return true; } /// ============================================================================ /// TypeLoc /// ============================================================================ - /// We don't care about type without location information. - constexpr bool TraverseType [[gnu::const]] (clang::QualType) { - return true; - } - - bool TraverseTypeLoc(clang::TypeLoc loc) { - /// FIXME: Workaround for `QualifiedTypeLoc`. - if(auto QL = loc.getAs()) { - return Base::TraverseTypeLoc(QL.getUnqualifiedLoc()); - } - - if(needFilter(loc.getSourceRange().getBegin())) { - return true; - } - - return Base::TraverseTypeLoc(loc); - } - /// unsigned int foo = 2; /// ^~~~~~~~^~~~~~~~ reference VISIT_TYPELOC(BuiltinTypeLoc) { @@ -623,40 +594,7 @@ public: /// Specifier /// ============================================================================ - bool TraverseAttr(clang::Attr* attr) { - if(needFilter(attr->getLocation())) { - return true; - } - - getDerived().handleAttrOccurrence(attr, attr->getLocation()); - - return Base::TraverseAttr(attr); - } - - /// FIXME: clang currently doesn't traverse attributes in `AttrbutedStmt` correctly. - /// See https://github.com/llvm/llvm-project/issues/117687. - bool TraverseAttributedStmt(clang::AttributedStmt* stmt) { - if(needFilter(stmt->getBeginLoc())) { - return true; - } - - for(auto attr: stmt->getAttrs()) { - getDerived().handleAttrOccurrence(attr, attr->getRange()); - } - - return Base::TraverseAttributedStmt(stmt); - } - - /// We don't care about name specifier without location information. - constexpr bool TraverseNestedNameSpecifier [[gnu::const]] (clang::NestedNameSpecifier*) { - return true; - } - - bool TraverseNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc loc) { - if(!loc || needFilter(loc.getLocalBeginLoc())) { - return true; - } - + bool VisitNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc loc) { auto NNS = loc.getNestedNameSpecifier(); switch(NNS->getKind()) { case clang::NestedNameSpecifier::Namespace: { @@ -689,21 +627,18 @@ public: }; } - return Base::TraverseNestedNameSpecifierLoc(loc); + return true; + } + + bool VisitAttr(clang::Attr* attr) { + getDerived().handleAttrOccurrence(attr, attr->getRange()); + return true; } /// ============================================================================ /// Statement /// ============================================================================ - bool TraverseStmt(clang::Stmt* stmt) { - if(stmt && needFilter(stmt->getBeginLoc())) { - return true; - } - - return Base::TraverseStmt(stmt); - } - /// foo = 1 /// ^~~~ reference VISIT_EXPR(DeclRefExpr) { diff --git a/include/Basic/SourceCode.h b/include/Basic/SourceCode.h index de720d77..d24938b6 100644 --- a/include/Basic/SourceCode.h +++ b/include/Basic/SourceCode.h @@ -12,6 +12,14 @@ struct LocalSourceRange { /// The end position offset to the source file. uint32_t end; + + bool contains(uint32_t offset) const { + return offset >= begin && offset <= end; + } + + bool intersects(const LocalSourceRange& other) const { + return begin <= other.end && end >= other.begin; + } }; /// Get the content of the file with the given file ID. diff --git a/include/Compiler/AST.h b/include/Compiler/AST.h index 3474d5c8..228094d0 100644 --- a/include/Compiler/AST.h +++ b/include/Compiler/AST.h @@ -19,7 +19,7 @@ public: llvm::DenseMap directives) : interested(interested), action(std::move(action)), instance(std::move(instance)), m_resolver(std::move(resolver)), buffer(std::move(buffer)), - m_directives(std::move(directives)) {} + m_directives(std::move(directives)), SM(this->instance->getSourceManager()) {} ASTInfo(const ASTInfo&) = delete; @@ -72,11 +72,41 @@ public: return interested; } +public: /// All files involved in building the AST. const llvm::DenseSet& files(); std::vector deps(); + clang::SourceLocation getSpellingLoc(clang::SourceLocation loc) { + return SM.getSpellingLoc(loc); + } + + clang::SourceLocation getExpansionLoc(clang::SourceLocation loc) { + return SM.getExpansionLoc(loc); + } + + auto getDecomposedLoc(clang::SourceLocation loc) { + return SM.getDecomposedLoc(loc); + } + + /// Get the file ID of a source location. The source location should always + /// be a spelling location. + clang::FileID getFileID(clang::SourceLocation spelling) { + assert(spelling.isInvalid() && spelling.isFileID() && "Invalid source location"); + return SM.getFileID(spelling); + } + + /// Get the file path of a file ID. If the file exists the path + /// will be real path, otherwise it will be virtual path. + llvm::StringRef getFilePath(clang::FileID fid); + + /// Check if a file is a builtin file. + bool isBuiltinFile(clang::FileID fid) { + auto path = getFilePath(fid); + return path == "" || path == "" || path == ""; + } + private: /// The interested file ID. clang::FileID interested; @@ -98,6 +128,11 @@ private: llvm::DenseMap m_directives; llvm::DenseSet allFiles; + + clang::SourceManager& SM; + + /// Cache for file path. It is used to avoid multiple file path lookup. + llvm::DenseMap pathCache; }; } // namespace clice diff --git a/src/Compiler/AST.cpp b/src/Compiler/AST.cpp index 806d8403..f7d2b427 100644 --- a/src/Compiler/AST.cpp +++ b/src/Compiler/AST.cpp @@ -45,4 +45,26 @@ std::vector ASTInfo::deps() { return result; } +llvm::StringRef ASTInfo::getFilePath(clang::FileID fid) { + if(auto it = pathCache.find(fid); it != pathCache.end()) { + return it->second; + } + + auto entry = SM.getFileEntryRefForID(fid); + assert(entry && "Invalid file entry"); + + auto name = entry->getName(); + llvm::SmallString<128> path; + + /// Try to get the real path of the file. + if(auto error = llvm::sys::fs::real_path(name, path)) { + /// If failed, use the virtual path. + path = name; + } + + auto [it, inserted] = pathCache.try_emplace(fid, path.data(), path.size()); + assert(inserted && "File path already exists"); + return it->second; +} + } // namespace clice diff --git a/src/Feature/SemanticTokens.cpp b/src/Feature/SemanticTokens.cpp index a952a995..173635f3 100644 --- a/src/Feature/SemanticTokens.cpp +++ b/src/Feature/SemanticTokens.cpp @@ -185,7 +185,7 @@ public: /// FIXME: handle module name. void merge(std::vector& tokens) { - ranges::sort(tokens, refl::less, [](const auto& token) { return token.range; }); + std::ranges::sort(tokens, refl::less, [](const auto& token) { return token.range; }); std::vector merged;