diff --git a/include/AST/FilterASTVisitor.h b/include/AST/FilterASTVisitor.h index 7a2264aa..6f240852 100644 --- a/include/AST/FilterASTVisitor.h +++ b/include/AST/FilterASTVisitor.h @@ -2,34 +2,19 @@ #include "AST/SourceCode.h" #include "Compiler/CompilationUnit.h" - #include "clang/AST/RecursiveASTVisitor.h" namespace clice { -struct RAVFileter { - - RAVFileter(CompilationUnit& unit, bool interestedOnly, std::optional limit) : - unit(unit), limit(limit), interestedOnly(interestedOnly) {} - - bool filterable(clang::SourceRange range) const; - - CompilationUnit& unit; - std::optional limit; - bool interestedOnly = true; -}; - /// A visitor class that extends clang::RecursiveASTVisitor to traverse /// AST nodes with an additional filtering mechanism. template -class FilteredASTVisitor : public clang::RecursiveASTVisitor, public RAVFileter { +class FilteredASTVisitor : public clang::RecursiveASTVisitor { public: using Base = clang::RecursiveASTVisitor; - FilteredASTVisitor(CompilationUnit& unit, - bool interestedOnly, - std::optional targetRange = std::nullopt) : - RAVFileter(unit, interestedOnly, targetRange) {} + FilteredASTVisitor(CompilationUnit& unit, bool interested_only) : + unit(unit), interested_only(interested_only) {} #define CHECK_DERIVED_IMPL(func) \ static_assert(std::same_as, \ @@ -47,10 +32,19 @@ public: } if(llvm::isa(decl)) { + if(interested_only) { + for(auto top: unit.top_level_decls()) { + if(!TraverseDecl(top)) { + return false; + } + } + return true; + } + return Base::TraverseDecl(decl); } - if(filterable(decl->getSourceRange()) || decl->isImplicit()) { + if(decl->isImplicit()) { return true; } @@ -73,9 +67,8 @@ public: } } - /// if constexpr(requires) - if constexpr(requires { getDerived().hookTraverseDecl(decl, &Base::TraverseDecl); }) { - return getDerived().hookTraverseDecl(decl, &Base::TraverseDecl); + if constexpr(requires { getDerived().on_traverse_decl(decl, &Base::TraverseDecl); }) { + return getDerived().on_traverse_decl(decl, &Base::TraverseDecl); } else { return Base::TraverseDecl(decl); } @@ -84,7 +77,7 @@ public: bool TraverseStmt(clang::Stmt* stmt) { CHECK_DERIVED_IMPL(TraverseStmt); - if(!stmt || filterable(stmt->getSourceRange())) { + if(!stmt) { return true; } @@ -95,7 +88,7 @@ public: bool TraverseAttributedStmt(clang::AttributedStmt* stmt) { CHECK_DERIVED_IMPL(TraverseAttributedStmt); - if(!stmt || filterable(stmt->getSourceRange())) { + if(!stmt) { return true; } @@ -115,7 +108,7 @@ public: bool TraverseTypeLoc(clang::TypeLoc loc) { CHECK_DERIVED_IMPL(TraverseTypeLoc); - if(!loc || filterable(loc.getSourceRange())) { + if(!loc) { return true; } @@ -130,7 +123,7 @@ public: bool TraverseAttr(clang::Attr* attr) { CHECK_DERIVED_IMPL(TraverseAttr); - if(!attr || filterable(attr->getRange())) { + if(!attr) { return true; } @@ -153,7 +146,7 @@ public: bool TraverseNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc loc) { CHECK_DERIVED_IMPL(TraverseNestedNameSpecifierLoc); - if(!loc || filterable(loc.getSourceRange())) { + if(!loc) { return true; } @@ -173,7 +166,7 @@ public: bool TraverseConceptReference(clang::ConceptReference* reference) { CHECK_DERIVED_IMPL(TraverseConceptReference); - if(!reference || filterable(reference->getSourceRange())) { + if(!reference) { return true; } @@ -185,6 +178,10 @@ public: } #undef CHECK_DERIVED_IMPL + +protected: + CompilationUnit& unit; + bool interested_only; }; } // namespace clice diff --git a/include/AST/Semantic.h b/include/AST/Semantic.h index 6bb763a3..a591960e 100644 --- a/include/AST/Semantic.h +++ b/include/AST/Semantic.h @@ -13,8 +13,8 @@ class SemanticVisitor : public FilteredASTVisitor> { public: using Base = FilteredASTVisitor; - SemanticVisitor(CompilationUnit& unit, bool interestedOnly) : - Base(unit, interestedOnly, {}), unit(unit), resolver(unit.resolver()) {} + SemanticVisitor(CompilationUnit& unit, bool interested_only) : + Base(unit, interested_only), unit(unit), resolver(unit.resolver()) {} public: Derived& getDerived() { @@ -102,7 +102,7 @@ public: } void run() { - if(Base::interestedOnly) { + if(Base::interested_only) { for(auto decl: unit.top_level_decls()) { Base::TraverseDecl(decl); } @@ -245,7 +245,7 @@ public: handleDeclOccurrence(decl, RelationKind::Definition, decl->getLocation()); handleRelation(decl, RelationKind::Definition, decl, decl->getLocation()); - if(auto target = declForType(decl->getType())) { + if(auto target = ast::decl_of(decl->getType())) { handleRelation(decl, RelationKind::TypeDefinition, target, decl->getLocation()); } @@ -280,7 +280,7 @@ public: handleDeclOccurrence(decl, RelationKind::Definition, decl->getLocation()); handleRelation(decl, RelationKind::Definition, decl, decl->getLocation()); - if(auto target = declForType(decl->getType())) { + if(auto target = ast::decl_of(decl->getType())) { handleRelation(decl, RelationKind::TypeDefinition, target, decl->getLocation()); } @@ -309,7 +309,7 @@ public: handleDeclOccurrence(decl, RelationKind::Definition, decl->getLocation()); handleRelation(decl, RelationKind::Definition, decl, decl->getLocation()); - if(auto target = declForType(decl->getType())) { + if(auto target = ast::decl_of(decl->getType())) { handleRelation(decl, RelationKind::TypeDefinition, target, decl->getLocation()); } @@ -332,7 +332,7 @@ public: case clang::TSK_ExplicitInstantiationDeclaration: case clang::TSK_ExplicitInstantiationDefinition: { - auto decl = instantiatedFrom(CTSD); + auto decl = ast::instantiated_from(CTSD); handleDeclOccurrence(decl, RelationKind::Reference, CTSD->getLocation()); handleRelation(decl, RelationKind::Reference, decl, CTSD->getLocation()); return true; @@ -349,7 +349,7 @@ public: if(auto def = CRD->getDefinition()) { for(auto base: CRD->bases()) { /// FIXME: Handle dependent base class. - if(auto target = declForType(base.getType())) { + if(auto target = ast::decl_of(base.getType())) { handleRelation(def, RelationKind::Base, target, base.getSourceRange()); handleRelation(target, RelationKind::Derived, def, base.getSourceRange()); } @@ -437,7 +437,7 @@ public: handleDeclOccurrence(decl, RelationKind::Definition, decl->getLocation()); handleRelation(decl, RelationKind::Definition, decl, decl->getLocation()); - if(auto target = declForType(decl->getUnderlyingType())) { + if(auto target = ast::decl_of(decl->getUnderlyingType())) { handleRelation(decl, RelationKind::TypeDefinition, target, decl->getLocation()); } @@ -472,7 +472,7 @@ public: handleDeclOccurrence(decl, kind, decl->getLocation()); handleRelation(decl, kind, decl, decl->getLocation()); - if(auto target = declForType(decl->getType())) { + if(auto target = ast::decl_of(decl->getType())) { handleRelation(decl, RelationKind::TypeDefinition, target, decl->getLocation()); } @@ -563,7 +563,7 @@ public: return true; } - auto decl = declForType(loc.getType()); + auto decl = ast::decl_of(loc.getType()); auto location = loc.getTemplateNameLoc(); handleDeclOccurrence(decl, RelationKind::Reference, location); handleRelation(decl, RelationKind::Reference, decl, location); diff --git a/include/AST/Utility.h b/include/AST/Utility.h index a0496e0b..f9d8da7f 100644 --- a/include/AST/Utility.h +++ b/include/AST/Utility.h @@ -1,32 +1,30 @@ #include "clang/AST/Decl.h" -namespace clice { +namespace clice::ast { /// is this decl a definition? -bool isDefinition(const clang::Decl* decl); +bool is_definition(const clang::Decl* decl); /// Check whether the decl is a template. Note that for partial specializations, /// we consider it as a template while clang does not. -bool isTemplated(const clang::Decl* decl); +bool is_templated(const clang::Decl* decl); /// Return the decl where it is instantiated from. If could be a template decl /// or a member of a class template. If the decl is a full specialization, return /// itself. -const clang::NamedDecl* instantiatedFrom(const clang::NamedDecl* decl); +const clang::NamedDecl* instantiated_from(const clang::NamedDecl* decl); const clang::NamedDecl* normalize(const clang::NamedDecl* decl); /// Get the name of the decl. -std::string getDeclName(const clang::NamedDecl* decl); +std::string name_of(const clang::NamedDecl* decl); /// To response go-to-type-definition request. Some decls actually have a type /// for example the result of `typeof(var)` is the type of `var`. This function /// returns the type for the decl if any. -clang::QualType typeForDecl(const clang::NamedDecl* decl); +clang::QualType type_of(const clang::NamedDecl* decl); /// Get the underlying decl for a type if any. -const clang::NamedDecl* declForType(clang::QualType type); +const clang::NamedDecl* decl_of(clang::QualType type); -void dumpInclude(clang::SourceManager& srcMgr, clang::SourceLocation loc); - -} // namespace clice +} // namespace clice::ast diff --git a/include/Async/Lock.h b/include/Async/Lock.h index 7594536a..67ad9f99 100644 --- a/include/Async/Lock.h +++ b/include/Async/Lock.h @@ -31,9 +31,7 @@ public: class Guard { public: - Guard(Lock* lock) : lock(lock) { - assert(lock->locked && "Guard: should be locked"); - } + Guard(Lock* lock) : lock(lock) {} Guard(Guard&& other) : lock(other.lock) { other.lock = nullptr; @@ -58,12 +56,19 @@ public: /// Try to get the lock. If the lock is locked, the current coroutine will be /// suspended and wait for the lock to be released. Task try_lock() { + /// Note that this task also may be canceled, we make sure + /// even cancel, it can resume one task(through destructor). + Guard guard(this); + if(locked) { co_await awaiter::lock{awaiters}; } locked = true; - co_return Guard{this}; + + /// Use `std::move` to make sure it will not resume + /// the awaiter here. + co_return std::move(guard); } private: diff --git a/include/Async/Network.h b/include/Async/Network.h index 4baeec81..9b083195 100644 --- a/include/Async/Network.h +++ b/include/Async/Network.h @@ -18,7 +18,7 @@ void listen(Callback callback); /// Listen on the given ip and port, callback is called when there is a LSP message available. void listen(const char* ip, unsigned int port, Callback callback); -/// Spawn a new process and listen on its stdin/stdout. +/// FIXME: Spawn a new process and listen on its stdin/stdout. void spawn(llvm::StringRef path, llvm::ArrayRef args, Callback callback); /// Write a JSON value to the client. diff --git a/include/Async/Task.h b/include/Async/Task.h index 9056a703..87d35391 100644 --- a/include/Async/Task.h +++ b/include/Async/Task.h @@ -8,7 +8,6 @@ #include #include "Support/Format.h" -#include "llvm/ADT/PointerIntPair.h" namespace clice::async { @@ -24,10 +23,15 @@ struct promise_base { /// The coroutine handle will be destroyed when the task is done or cancelled. Disposable = 1 << 1, + + /// The coroutine is done or is cancelled and resumed, means it will never + /// scheduled again. + Finished = 1 << 2, }; - /// The address of the actual coroutine handle and flags. - llvm::PointerIntPair data; + uint8_t flags; + + void* data; /// The coroutine handle that is waiting for the task to complete. /// If this is a top-level coroutine, it is empty. @@ -39,12 +43,12 @@ struct promise_base { template void set(std::coroutine_handle handle) { - data.setInt(Empty); - data.setPointer(handle.address()); + flags = Empty; + data = handle.address(); } auto handle() const noexcept { - return std::coroutine_handle<>::from_address(data.getPointer()); + return std::coroutine_handle<>::from_address(data); } void schedule(); @@ -60,21 +64,29 @@ struct promise_base { void cancel() { auto p = this; while(p) { - p->data.setInt(Flags(data.getInt() | Flags::Cancelled)); + p->flags |= Flags::Cancelled; p = p->next; } } bool cancelled() const noexcept { - return data.getInt() & Flags::Cancelled; + return flags & Flags::Cancelled; } void dispose() { - data.setInt(Flags(data.getInt() | Flags::Disposable)); + flags |= Flags::Disposable; } bool disposable() const noexcept { - return data.getInt() & Flags::Disposable; + return flags & Flags::Disposable; + } + + void finish() { + flags |= Flags::Finished; + } + + bool finished() { + return flags & Flags::Finished; } std::coroutine_handle<> resume_handle() { @@ -83,11 +95,16 @@ struct promise_base { auto p = this; while(p && p->cancelled()) { auto con = p->continuation; + if(p->disposable()) { p->destroy(); + } else { + p->finish(); } + p = con; } + return std::noop_coroutine(); } else { /// Otherwise, resume the coroutine handle. @@ -121,6 +138,9 @@ struct final { handle = continuation->resume_handle(); } + /// Mark current coroutine as finished. + current.promise().finish(); + if(current.promise().disposable()) { /// If this task is disposable, destroy the coroutine handle. current.destroy(); @@ -270,12 +290,20 @@ public: core.promise().cancel(); } + bool cancelled() { + return core.promise().cancelled(); + } + /// Dispose the task, it will be destroyed when finished or cancelled. void dispose() { core.promise().dispose(); core = nullptr; } + bool finished() { + return core.promise().finished(); + } + T result() { if constexpr(!std::is_void_v) { return std::move(core.promise().value.value()); diff --git a/include/Async/libuv.h b/include/Async/libuv.h index 68f2eb38..99871b1e 100644 --- a/include/Async/libuv.h +++ b/include/Async/libuv.h @@ -74,4 +74,6 @@ void init(); void run(); +void stop(); + } // namespace clice::async diff --git a/include/Compiler/Preamble.h b/include/Compiler/Preamble.h index 7ecdccc3..c48e7487 100644 --- a/include/Compiler/Preamble.h +++ b/include/Compiler/Preamble.h @@ -13,20 +13,20 @@ class CompilationUnit; struct CompilationParams; struct PCHInfo { - /// The building time. - std::int64_t mtime; - /// The path of the output PCH file. std::string path; + /// The building time of this PCH. + std::int64_t mtime; + /// The content used to build this PCH. std::string preamble; - /// The command used to build this PCH. - std::string command; - /// All files involved in building this PCH. std::vector deps; + + /// The command arguments used to build this PCH. + std::vector arguments; }; /// Compute the preamble bound of given content. We just diff --git a/include/Protocol/Initialize.h b/include/Protocol/Lifecycle.h similarity index 97% rename from include/Protocol/Initialize.h rename to include/Protocol/Lifecycle.h index 3714eb21..b4b868af 100644 --- a/include/Protocol/Initialize.h +++ b/include/Protocol/Lifecycle.h @@ -183,4 +183,16 @@ struct InitializeResult { ServerCapabilities capabilities; }; +struct Empty {}; + +using InitializedParams = Empty; + +using ShutdownParams = Empty; + +using ShutdownResult = Empty; + +using ExitParams = Empty; + +using ExitResult = Empty; + } // namespace clice::proto diff --git a/include/Protocol/Protocol.h b/include/Protocol/Protocol.h index 6dd6f762..ab248b0d 100644 --- a/include/Protocol/Protocol.h +++ b/include/Protocol/Protocol.h @@ -1,3 +1,3 @@ #pragma once -#include "Initialize.h" +#include "Lifecycle.h" diff --git a/include/Server/Server.h b/include/Server/Server.h index 644bf06f..dffc4324 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -89,13 +89,28 @@ private: private: async::Task on_initialize(proto::InitializeParams params); -private: - async::Task add_document(std::string path, std::string content); + async::Task<> on_initialized(proto::InitializedParams); - async::Task<> build_pch(std::string file, std::string preamble); + async::Task on_shutdown(proto::ShutdownParams params); + + async::Task<> on_exit(proto::ExitParams params); + +private: + /// Load the cache info from disk. + void load_cache_info(); + + /// Save the cache info to disk. + void save_cache_info(); + + async::Task build_pch(std::string file, std::string preamble); async::Task<> build_ast(std::string file, std::string content); + async::Task add_document(std::string path, std::string content); + +private: + async::Task<> publish_diagnostics(std::string path, OpenFile* file); + async::Task<> on_did_open(proto::DidOpenTextDocumentParams params); async::Task<> on_did_change(proto::DidChangeTextDocumentParams params); diff --git a/src/AST/FilterASTVisitor.cpp b/src/AST/FilterASTVisitor.cpp deleted file mode 100644 index 9f45e1ba..00000000 --- a/src/AST/FilterASTVisitor.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "AST/FilterASTVisitor.h" - -namespace clice { - -bool RAVFileter::filterable(clang::SourceRange range) const { - 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] = unit.decompose_location(unit.expansion_location(begin)); - - /// For builtin files, we don't want to visit them. - if(unit.is_builtin_file(fid)) { - return true; - } - - /// Filter out if the location is not in the interested file. - if(interestedOnly) { - auto interested = unit.interested_file(); - if(fid != interested) { - return true; - } - - if(limit && !limit->contains(offset)) { - return true; - } - } - } else { - auto [beginFID, beginOffset] = unit.decompose_location(unit.expansion_location(begin)); - auto [endFID, endOffset] = unit.decompose_location(unit.expansion_location(end)); - - if(unit.is_builtin_file(beginFID) || unit.is_builtin_file(endFID)) { - return true; - } - - if(interestedOnly) { - auto interested = unit.interested_file(); - if(beginFID != interested && endFID != interested) { - return true; - } - - if(limit && !limit->intersects({beginOffset, endOffset})) { - return true; - } - } - } - - return false; -} - -} // namespace clice diff --git a/src/AST/Selection.cpp b/src/AST/Selection.cpp index ad65b8db..8d106449 100644 --- a/src/AST/Selection.cpp +++ b/src/AST/Selection.cpp @@ -23,74 +23,69 @@ namespace clice { -using namespace clang; - namespace { using Node = SelectionTree::Node; -#ifndef NDEBUG -#define nlog(...) log::debug(__VA_ARGS__) -#endif +std::vector get_attributes(const clang::DynTypedNode& node) { + std::vector attrs; -std::vector get_attributes(const DynTypedNode& N) { - std::vector result; - - if(const auto* TL = N.get()) { - for(auto ATL = TL->getAs(); !ATL.isNull(); - ATL = ATL.getModifiedLoc().getAs()) { - if(const Attr* A = ATL.getAttr()) { - result.push_back(A); + if(const auto* TL = node.get()) { + for(auto atl = TL->getAs(); !atl.isNull(); + atl = atl.getModifiedLoc().getAs()) { + if(const clang::Attr* A = atl.getAttr()) { + attrs.push_back(A); } - assert(!ATL.getModifiedLoc().isNull()); + assert(!atl.getModifiedLoc().isNull()); } } - if(const auto* S = N.get()) { - for(; S != nullptr; S = dyn_cast(S->getSubStmt())) { - for(const Attr* A: S->getAttrs()) { + if(const auto* S = node.get()) { + for(; S != nullptr; S = dyn_cast(S->getSubStmt())) { + for(const clang::Attr* A: S->getAttrs()) { if(A) { - result.push_back(A); + attrs.push_back(A); } } } } - if(const auto* D = N.get()) { - for(const Attr* A: D->attrs()) { + if(const auto* D = node.get()) { + for(const clang::Attr* A: D->attrs()) { if(A) { - result.push_back(A); + attrs.push_back(A); } } } - return result; + + return attrs; } // Measure the fraction of selections that were enabled by recovery AST. -void recordMetrics(const SelectionTree& S, const LangOptions& Lang) { +void record_metrics(const SelectionTree& tree, const clang::LangOptions& lang_opts) { /// if(!trace::enabled()) /// return; - /// const char* LanguageLabel = Lang.CPlusPlus ? "C++" : Lang.ObjC ? "ObjC" : "C"; - /// constexpr static trace::Metric SelectionUsedRecovery("selection_recovery", + /// const char* language_label = lang_opts.CPlusPlus ? "C++" : lang_opts.ObjC ? "ObjC" : "C"; + /// constexpr static trace::Metric selection_used_recovery("selection_recovery", /// trace::Metric::Distribution, /// "language"); - /// constexpr static trace::Metric RecoveryType("selection_recovery_type", + /// constexpr static trace::Metric recovery_type("selection_recovery_type", /// trace::Metric::Distribution, /// "language"); - /// const auto* Common = S.commonAncestor(); - /// for(const auto* N = Common; N; N = N->Parent) { + /// const auto* common = selection_tree.commonAncestor(); + /// for(const auto* N = common; N; N = N->Parent) { /// if(const auto* RE = N->ASTNode.get()) { - /// SelectionUsedRecovery.record(1, LanguageLabel); // used recovery ast. - /// RecoveryType.record(RE->isTypeDependent() ? 0 : 1, LanguageLabel); + /// selection_used_recovery.record(1, language_label); // used recovery ast. + /// recovery_type.record(RE->isTypeDependent() ? 0 : 1, language_label); /// return; /// } /// } - /// if(Common) - /// SelectionUsedRecovery.record(0, LanguageLabel); // unused. + /// if(common) + /// selection_used_recovery.record(0, language_label); // unused. } // Return the range covering a node and all its children. -SourceRange getSourceRange(const DynTypedNode& N) { +clang::SourceRange get_source_range(const clang::DynTypedNode& node) { // MemberExprs to implicitly access anonymous fields should not claim any // tokens for themselves. Given: // struct A { struct { int b; }; }; @@ -102,12 +97,13 @@ SourceRange getSourceRange(const DynTypedNode& N) { // For our purposes, we don't want the second MemberExpr to own any tokens, // so we reduce its range to match the CXXConstructExpr. // (It's not clear that changing the clang AST would be correct in general). - if(const auto* ME = N.get()) { - if(!ME->getMemberDecl()->getDeclName()) - return ME->getBase() ? getSourceRange(DynTypedNode::create(*ME->getBase())) - : SourceRange(); + if(const auto* ME = node.get()) { + if(!ME->getMemberDecl()->getDeclName()) { + return ME->getBase() ? get_source_range(clang::DynTypedNode::create(*ME->getBase())) + : clang::SourceRange(); + } } - return N.getSourceRange(); + return node.getSourceRange(); } // An IntervalSet maintains a set of disjoint subranges of an array. @@ -116,144 +112,156 @@ SourceRange getSourceRange(const DynTypedNode& N) { // [-----------------------------------------------------------] // // When a range is erased(), it will typically split the array in two. -// Claim: [--------------------] +// claim: [--------------------] // after: [----------------] [-------------------] // // erase() returns the segments actually erased. Given the state above: -// Claim: [---------------------------------------] -// Out: [---------] [------] -// After: [-----] [-----------] +// claim: [---------------------------------------] +// out: [---------] [------] +// after: [-----] [-----------] // // It is used to track (expanded) tokens not yet associated with an AST node. // On traversing an AST node, its token range is erased from the unclaimed set. // The tokens actually removed are associated with that node, and hit-tested // against the selection to determine whether the node is selected. -template class IntervalSet { public: - IntervalSet(llvm::ArrayRef Range) { - UnclaimedRanges.insert(Range); + using Token = clang::syntax::Token; + using TokenRange = llvm::ArrayRef; + + IntervalSet(TokenRange range) { + unclaimed_ranges.insert(range); } // Removes the elements of Claim from the set, modifying or removing ranges // that overlap it. // Returns the continuous subranges of Claim that were actually removed. - llvm::SmallVector> erase(llvm::ArrayRef Claim) { - llvm::SmallVector> Out; - if(Claim.empty()) - return Out; + llvm::SmallVector erase(TokenRange claim) { + llvm::SmallVector out; + if(claim.empty()) + return out; // General case: // Claim: [-----------------] - // UnclaimedRanges: [-A-] [-B-] [-C-] [-D-] [-E-] [-F-] [-G-] + // unclaimed_ranges: [-A-] [-B-] [-C-] [-D-] [-E-] [-F-] [-G-] // Overlap: ^first ^second // Ranges C and D are fully included. Ranges B and E must be trimmed. - auto Overlap = - std::make_pair(UnclaimedRanges.lower_bound({Claim.begin(), Claim.begin()}), // C - UnclaimedRanges.lower_bound({Claim.end(), Claim.end()})); // F + auto overlap = + std::make_pair(unclaimed_ranges.lower_bound({claim.begin(), claim.begin()}), // C + unclaimed_ranges.lower_bound({claim.end(), claim.end()})); // F // Rewind to cover B. - if(Overlap.first != UnclaimedRanges.begin()) { - --Overlap.first; + if(overlap.first != unclaimed_ranges.begin()) { + --overlap.first; // ...unless B isn't selected at all. - if(Overlap.first->end() <= Claim.begin()) - ++Overlap.first; + if(overlap.first->end() <= claim.begin()) { + ++overlap.first; + } + } + + if(overlap.first == overlap.second) { + return out; } - if(Overlap.first == Overlap.second) - return Out; // First, copy all overlapping ranges into the output. - auto OutFirst = Out.insert(Out.end(), Overlap.first, Overlap.second); + auto out_first = out.insert(out.end(), overlap.first, overlap.second); // If any of the overlapping ranges were sliced by the claim, split them: // - restrict the returned range to the claimed part // - save the unclaimed part so it can be reinserted - llvm::ArrayRef RemainingHead, RemainingTail; - if(Claim.begin() > OutFirst->begin()) { - RemainingHead = {OutFirst->begin(), Claim.begin()}; - *OutFirst = {Claim.begin(), OutFirst->end()}; + TokenRange remaining_head, remaining_tail; + if(claim.begin() > out_first->begin()) { + remaining_head = {out_first->begin(), claim.begin()}; + *out_first = {claim.begin(), out_first->end()}; } - if(Claim.end() < Out.back().end()) { - RemainingTail = {Claim.end(), Out.back().end()}; - Out.back() = {Out.back().begin(), Claim.end()}; + if(claim.end() < out.back().end()) { + remaining_tail = {claim.end(), out.back().end()}; + out.back() = {out.back().begin(), claim.end()}; } // Erase all the overlapping ranges (invalidating all iterators). - UnclaimedRanges.erase(Overlap.first, Overlap.second); + unclaimed_ranges.erase(overlap.first, overlap.second); // Reinsert ranges that were merely trimmed. - if(!RemainingHead.empty()) - UnclaimedRanges.insert(RemainingHead); - if(!RemainingTail.empty()) - UnclaimedRanges.insert(RemainingTail); + if(!remaining_head.empty()) { + unclaimed_ranges.insert(remaining_head); + } + if(!remaining_tail.empty()) { + unclaimed_ranges.insert(remaining_tail); + } - return Out; + return out; } private: - using TokenRange = llvm::ArrayRef; - - struct RangeLess { - bool operator() (llvm::ArrayRef L, llvm::ArrayRef R) const { + struct range_less { + bool operator() (TokenRange L, TokenRange R) const { return L.begin() < R.begin(); } }; // Disjoint sorted unclaimed ranges of expanded tokens. - std::set, RangeLess> UnclaimedRanges; + std::set unclaimed_ranges; }; // Sentinel value for the selectedness of a node where we've seen no tokens yet. // This resolves to Unselected if no tokens are ever seen. -// But Unselected + Complete -> Partial, while NoTokens + Complete --> Complete. +// But Unselected + Complete -> Partial, while no_tokens + Complete --> Complete. // This value is never exposed publicly. -constexpr SelectionTree::SelectionKind NoTokens = static_cast( +constexpr SelectionTree::SelectionKind no_tokens = static_cast( static_cast(SelectionTree::Complete + 1)); -// Nodes start with NoTokens, and then use this function to aggregate the +// Nodes start with no_tokens, and then use this function to aggregate the // selectedness as more tokens are found. -void update(SelectionTree::SelectionKind& Result, SelectionTree::SelectionKind New) { - if(New == NoTokens) +void update(SelectionTree::SelectionKind& result, SelectionTree::SelectionKind new_kind) { + if(new_kind == no_tokens) { return; - if(Result == NoTokens) - Result = New; - else if(Result != New) + } + + if(result == no_tokens) { + result = new_kind; + } else if(result != new_kind) { // Can only be completely selected (or unselected) if all tokens are. - Result = SelectionTree::Partial; + result = SelectionTree::Partial; + } } // As well as comments, don't count semicolons as real tokens. // They're not properly claimed as expr-statement is missing from the AST. -bool should_ignore(const syntax::Token& token) { +bool should_ignore(const clang::syntax::Token& token) { switch(token.kind()) { // Even "attached" comments are not considered part of a node's range. - case tok::comment: + case clang::tok::comment: // The AST doesn't directly store locations for terminating semicolons. - case tok::semi: + case clang::tok::semi: // We don't have locations for cvr-qualifiers: see QualifiedTypeLoc. - case tok::kw_const: - case tok::kw_volatile: - case tok::kw_restrict: return true; + case clang::tok::kw_const: + case clang::tok::kw_volatile: + case clang::tok::kw_restrict: return true; default: return false; } } // Determine whether 'Target' is the first expansion of the macro // argument whose top-level spelling location is 'SpellingLoc'. -bool is_first_expansion(FileID Target, SourceLocation SpellingLoc, const SourceManager& SM) { - SourceLocation Prev = SpellingLoc; +bool is_first_expansion(clang::FileID target, + clang::SourceLocation spelling_loc, + const clang::SourceManager& source_manager) { + clang::SourceLocation prev = spelling_loc; while(true) { // If the arg is expanded multiple times, getMacroArgExpandedLocation() // returns the first expansion. - SourceLocation Next = SM.getMacroArgExpandedLocation(Prev); + clang::SourceLocation next = source_manager.getMacroArgExpandedLocation(prev); // So if we reach the target, target is the first-expansion of the // first-expansion ... - if(SM.getFileID(Next) == Target) + if(source_manager.getFileID(next) == target) { return true; + } // Otherwise, if the FileID stops changing, we've reached the innermost // macro expansion, and Target was on a different branch. - if(SM.getFileID(Next) == SM.getFileID(Prev)) + if(source_manager.getFileID(next) == source_manager.getFileID(prev)) { return false; + } - Prev = Next; + prev = next; } return false; } @@ -275,36 +283,39 @@ class SelectionTester { public: // The selection is offsets [SelBegin, SelEnd) in SelFile. SelectionTester(CompilationUnit& unit, - FileID selected_file, + clang::FileID selected_file_id, LocalSourceRange selected_range, - const SourceManager& SM) : - selected_file(selected_file), selected_file_range(SM.getLocForStartOfFile(selected_file), - SM.getLocForEndOfFile(selected_file)), - SM(SM) { + const clang::SourceManager& source_manager) : + selected_file(selected_file_id), + selected_file_range(source_manager.getLocForStartOfFile(selected_file_id), + source_manager.getLocForEndOfFile(selected_file_id)), + SM(source_manager) { // Find all tokens (partially) selected in the file. auto spelled_tokens = unit.spelled_tokens(selected_file); - const syntax::Token* first = - llvm::partition_point(spelled_tokens, [&](const syntax::Token& token) { + const clang::syntax::Token* first = + llvm::partition_point(spelled_tokens, [&](const clang::syntax::Token& token) { return unit.file_offset(token.endLocation()) <= selected_range.begin; }); - const syntax::Token* last = - std::partition_point(first, spelled_tokens.end(), [&](const syntax::Token& token) { - return unit.file_offset(token.location()) < selected_range.end; - }); + const clang::syntax::Token* last = + std::partition_point(first, + spelled_tokens.end(), + [&](const clang::syntax::Token& token) { + return unit.file_offset(token.location()) < selected_range.end; + }); auto selected_tokens = llvm::ArrayRef(first, last); // Find which of these are preprocessed to nothing and should be ignored. - llvm::BitVector PPIgnored(selected_tokens.size(), false); + llvm::BitVector pp_ignored(selected_tokens.size(), false); - for(const syntax::TokenBuffer::Expansion& expansion: + for(const clang::syntax::TokenBuffer::Expansion& expansion: unit.expansions_overlapping(selected_tokens)) { if(expansion.Expanded.empty()) { - for(const syntax::Token& token: expansion.Spelled) { + for(const clang::syntax::Token& token: expansion.Spelled) { if(&token >= first && &token < last) { - PPIgnored[&token - first] = true; + pp_ignored[&token - first] = true; } } } @@ -312,7 +323,7 @@ public: // Precompute selectedness and offset for selected spelled tokens. for(unsigned I = 0; I < selected_tokens.size(); ++I) { - if(should_ignore(selected_tokens[I]) || PPIgnored[I]) { + if(should_ignore(selected_tokens[I]) || pp_ignored[I]) { continue; } @@ -328,17 +339,19 @@ public: } } - maybe_selected_expanded = computeMaybeSelectedExpandedTokens(unit.token_buffer()); + maybe_selected_expanded = compute_maybe_selected_expanded_tokens(unit.token_buffer()); } // Test whether a consecutive range of tokens is selected. // The tokens are taken from the expanded token stream. - SelectionTree::SelectionKind test(llvm::ArrayRef ExpandedTokens) const { - if(ExpandedTokens.empty()) - return NoTokens; + SelectionTree::SelectionKind test(llvm::ArrayRef expanded_tokens) const { + if(expanded_tokens.empty()) { + return no_tokens; + } - if(selected_spelled.empty()) + if(selected_spelled.empty()) { return SelectionTree::Unselected; + } // Cheap (pointer) check whether any of the tokens could touch selection. // In most cases, the node's overall source range touches ExpandedTokens, @@ -347,8 +360,8 @@ public: // This is a significant performance improvement when a lot of nodes // surround the selection, including when generated by macros. if(maybe_selected_expanded.empty() || - &ExpandedTokens.front() > &maybe_selected_expanded.back() || - &ExpandedTokens.back() < &maybe_selected_expanded.front()) { + &expanded_tokens.front() > &maybe_selected_expanded.back() || + &expanded_tokens.back() < &maybe_selected_expanded.front()) { return SelectionTree::Unselected; } @@ -357,51 +370,62 @@ public: // but it could occur for unmatched-bracket cases. // FIXME: fix it in TokenBuffer, expandedTokens(SourceRange) should not // return the eof token. - if(ExpandedTokens.back().kind() == tok::eof) - ExpandedTokens = ExpandedTokens.drop_back(); + if(expanded_tokens.back().kind() == clang::tok::eof) { + expanded_tokens = expanded_tokens.drop_back(); + } - SelectionTree::SelectionKind result = NoTokens; + SelectionTree::SelectionKind result = no_tokens; - while(!ExpandedTokens.empty()) { + while(!expanded_tokens.empty()) { // Take consecutive tokens from the same context together for efficiency. - SourceLocation Start = ExpandedTokens.front().location(); - FileID FID = SM.getFileID(Start); + clang::SourceLocation start = expanded_tokens.front().location(); + clang::FileID fid = SM.getFileID(start); // Comparing SourceLocations against bounds is cheaper than getFileID(). - SourceLocation Limit = SM.getComposedLoc(FID, SM.getFileIDSize(FID)); - auto Batch = ExpandedTokens.take_while([&](const syntax::Token& T) { - return T.location() >= Start && T.location() < Limit; + clang::SourceLocation limit = SM.getComposedLoc(fid, SM.getFileIDSize(fid)); + auto batch = expanded_tokens.take_while([&](const clang::syntax::Token& T) { + return T.location() >= start && T.location() < limit; }); - assert(!Batch.empty()); - ExpandedTokens = ExpandedTokens.drop_front(Batch.size()); + assert(!batch.empty()); + expanded_tokens = expanded_tokens.drop_front(batch.size()); - update(result, testChunk(FID, Batch)); + update(result, test_chunk(fid, batch)); } return result; } // Cheap check whether any of the tokens in R might be selected. - // If it returns false, test() will return NoTokens or Unselected. + // If it returns false, test() will return no_tokens or Unselected. // If it returns true, test() may return any value. - bool mayHit(SourceRange R) const { - if(selected_spelled.empty() || maybe_selected_expanded.empty()) + bool may_hit(clang::SourceRange range) const { + if(selected_spelled.empty() || maybe_selected_expanded.empty()) { return false; + } + // If the node starts after the selection ends, it is not selected. // Tokens a macro location might claim are >= its expansion start. // So if the expansion start > last selected token, we can prune it. // (This is particularly helpful for GTest's TEST macro). - if(auto B = offsetInSelFile(getExpansionStart(R.getBegin()))) - if(*B > selected_spelled.back().offset) + if(auto B = offset_in_sel_file(get_expansion_start(range.getBegin()))) { + if(*B > selected_spelled.back().offset) { return false; + } + } + // If the node ends before the selection begins, it is not selected. - SourceLocation EndLoc = R.getEnd(); - while(EndLoc.isMacroID()) - EndLoc = SM.getImmediateExpansionRange(EndLoc).getEnd(); - // In the rare case that the expansion range is a char range, EndLoc is + clang::SourceLocation end_loc = range.getEnd(); + while(end_loc.isMacroID()) { + end_loc = SM.getImmediateExpansionRange(end_loc).getEnd(); + } + + // In the rare case that the expansion range is a char range, end_loc is // ~one token too far to the right. We may fail to prune, that's OK. - if(auto E = offsetInSelFile(EndLoc)) - if(*E < selected_spelled.front().offset) + if(auto E = offset_in_sel_file(end_loc)) { + if(*E < selected_spelled.front().offset) { return false; + } + } + return true; } @@ -409,70 +433,80 @@ private: // Plausible expanded tokens that might be affected by the selection. // This is an overestimate, it may contain tokens that are not selected. // The point is to allow cheap pruning in test() - llvm::ArrayRef - computeMaybeSelectedExpandedTokens(const syntax::TokenBuffer& Toks) { + llvm::ArrayRef + compute_maybe_selected_expanded_tokens(const clang::syntax::TokenBuffer& token_buffer) { if(selected_spelled.empty()) return {}; - auto LastAffectedToken = [&](SourceLocation Loc) { - auto Offset = offsetInSelFile(Loc); - while(Loc.isValid() && !Offset) { - Loc = Loc.isMacroID() ? SM.getImmediateExpansionRange(Loc).getEnd() - : SM.getIncludeLoc(SM.getFileID(Loc)); - Offset = offsetInSelFile(Loc); + auto last_affected_token = [&](clang::SourceLocation location) { + auto offset = offset_in_sel_file(location); + while(location.isValid() && !offset) { + location = location.isMacroID() ? SM.getImmediateExpansionRange(location).getEnd() + : SM.getIncludeLoc(SM.getFileID(location)); + offset = offset_in_sel_file(location); } - return Offset; + return offset; }; - auto FirstAffectedToken = [&](SourceLocation Loc) { - auto Offset = offsetInSelFile(Loc); - while(Loc.isValid() && !Offset) { - Loc = Loc.isMacroID() ? SM.getImmediateExpansionRange(Loc).getBegin() - : SM.getIncludeLoc(SM.getFileID(Loc)); - Offset = offsetInSelFile(Loc); + auto first_affected_token = [&](clang::SourceLocation location) { + auto offset = offset_in_sel_file(location); + while(location.isValid() && !offset) { + location = location.isMacroID() ? SM.getImmediateExpansionRange(location).getBegin() + : SM.getIncludeLoc(SM.getFileID(location)); + offset = offset_in_sel_file(location); } - return Offset; + return offset; }; - const syntax::Token* Start = llvm::partition_point( - Toks.expandedTokens(), - [&, First = selected_spelled.front().offset](const syntax::Token& Tok) { - if(Tok.kind() == tok::eof) + const clang::syntax::Token* start = llvm::partition_point( + token_buffer.expandedTokens(), + [&, first = selected_spelled.front().offset](const clang::syntax::Token& token) { + if(token.kind() == clang::tok::eof) { return false; + } + // Implausible if upperbound(Tok) < First. - if(auto Offset = LastAffectedToken(Tok.location())) - return *Offset < First; + if(auto offset = last_affected_token(token.location())) { + return *offset < first; + } + // A prefix of the expanded tokens may be from an implicit // inclusion (e.g. preamble patch, or command-line -include). return true; }); - bool EndInvalid = false; - const syntax::Token* End = std::partition_point( - Start, - Toks.expandedTokens().end(), - [&, Last = selected_spelled.back().offset](const syntax::Token& Tok) { - if(Tok.kind() == tok::eof) + bool end_invalid = false; + const clang::syntax::Token* end = std::partition_point( + start, + token_buffer.expandedTokens().end(), + [&, last = selected_spelled.back().offset](const clang::syntax::Token& token) { + if(token.kind() == clang::tok::eof) { return false; + } + // Plausible if lowerbound(Tok) <= Last. - if(auto Offset = FirstAffectedToken(Tok.location())) - return *Offset <= Last; + if(auto offset = first_affected_token(token.location())) { + return *offset <= last; + } + // Shouldn't happen: once we've seen tokens traceable to the main // file, there shouldn't be any more implicit inclusions. assert(false && "Expanded token could not be resolved to main file!"); - EndInvalid = true; + end_invalid = true; return true; // conservatively assume this token can overlap }); - if(EndInvalid) - End = Toks.expandedTokens().end(); + if(end_invalid) { + end = token_buffer.expandedTokens().end(); + } - return llvm::ArrayRef(Start, End); + return llvm::ArrayRef(start, end); } // Hit-test a consecutive range of tokens from a single file ID. - SelectionTree::SelectionKind testChunk(FileID FID, llvm::ArrayRef Batch) const { - assert(!Batch.empty()); - SourceLocation StartLoc = Batch.front().location(); + SelectionTree::SelectionKind test_chunk(clang::FileID fid, + llvm::ArrayRef batch) const { + assert(!batch.empty()); + clang::SourceLocation start_loc = batch.front().location(); // There are several possible categories of FileID depending on how the // preprocessor was used to generate these tokens: // main file, #included file, macro args, macro bodies. @@ -481,30 +515,30 @@ private: // represent one AST construct, but a macro invocation can represent many. // Handle tokens written directly in the main file. - if(FID == selected_file) { - return testTokenRange(*offsetInSelFile(Batch.front().location()), - *offsetInSelFile(Batch.back().location())); + if(fid == selected_file) { + return test_token_range(*offset_in_sel_file(batch.front().location()), + *offset_in_sel_file(batch.back().location())); } // Handle tokens in another file #included into the main file. // Check if the #include is selected, but don't claim it exclusively. - if(StartLoc.isFileID()) { - for(SourceLocation Loc = Batch.front().location(); Loc.isValid(); + if(start_loc.isFileID()) { + for(clang::SourceLocation Loc = batch.front().location(); Loc.isValid(); Loc = SM.getIncludeLoc(SM.getFileID(Loc))) { - if(auto Offset = offsetInSelFile(Loc)) + if(auto offset = offset_in_sel_file(Loc)) // FIXME: use whole #include directive, not just the filename string. - return testToken(*Offset); + return test_token(*offset); } - return NoTokens; + return no_tokens; } - assert(StartLoc.isMacroID()); + assert(start_loc.isMacroID()); // Handle tokens that were passed as a macro argument. - SourceLocation ArgStart = SM.getTopMacroCallerLoc(StartLoc); - if(auto ArgOffset = offsetInSelFile(ArgStart)) { - if(is_first_expansion(FID, ArgStart, SM)) { - SourceLocation ArgEnd = SM.getTopMacroCallerLoc(Batch.back().location()); - return testTokenRange(*ArgOffset, *offsetInSelFile(ArgEnd)); + clang::SourceLocation arg_start = SM.getTopMacroCallerLoc(start_loc); + if(auto arg_offset = offset_in_sel_file(arg_start)) { + if(is_first_expansion(fid, arg_start, SM)) { + clang::SourceLocation arg_end = SM.getTopMacroCallerLoc(batch.back().location()); + return test_token_range(*arg_offset, *offset_in_sel_file(arg_end)); } else { // NOLINT(llvm-else-after-return) /* fall through and treat as part of the macro body */ } @@ -512,64 +546,76 @@ private: // Handle tokens produced by non-argument macro expansion. // Check if the macro name is selected, don't claim it exclusively. - if(auto ExpansionOffset = offsetInSelFile(getExpansionStart(StartLoc))) + if(auto expansion_offset = offset_in_sel_file(get_expansion_start(start_loc))) { // FIXME: also check ( and ) for function-like macros? - return testToken(*ExpansionOffset); - return NoTokens; + return test_token(*expansion_offset); + } + + return no_tokens; } // Is the closed token range [Begin, End] selected? - SelectionTree::SelectionKind testTokenRange(unsigned Begin, unsigned End) const { - assert(Begin <= End); + SelectionTree::SelectionKind test_token_range(unsigned begin, unsigned end) const { + assert(begin <= end); // Outside the selection entirely? - if(End < selected_spelled.front().offset || Begin > selected_spelled.back().offset) + if(end < selected_spelled.front().offset || begin > selected_spelled.back().offset) { return SelectionTree::Unselected; + } // Compute range of tokens. auto B = - llvm::partition_point(selected_spelled, [&](const Tok& T) { return T.offset < Begin; }); + llvm::partition_point(selected_spelled, [&](const Tok& T) { return T.offset < begin; }); auto E = std::partition_point(B, selected_spelled.end(), [&](const Tok& T) { - return T.offset <= End; + return T.offset <= end; }); // Aggregate selectedness of tokens in range. - bool ExtendsOutsideSelection = - Begin < selected_spelled.front().offset || End > selected_spelled.back().offset; - SelectionTree::SelectionKind Result = - ExtendsOutsideSelection ? SelectionTree::Unselected : NoTokens; - for(auto It = B; It != E; ++It) - update(Result, It->selected); - return Result; + bool extends_outside_selection = + begin < selected_spelled.front().offset || end > selected_spelled.back().offset; + SelectionTree::SelectionKind result = + extends_outside_selection ? SelectionTree::Unselected : no_tokens; + for(auto It = B; It != E; ++It) { + update(result, It->selected); + } + + return result; } - // Is the token at `Offset` selected? - SelectionTree::SelectionKind testToken(unsigned Offset) const { + // Is the token at `offset` selected? + SelectionTree::SelectionKind test_token(unsigned offset) const { // Outside the selection entirely? - if(Offset < selected_spelled.front().offset || Offset > selected_spelled.back().offset) + if(offset < selected_spelled.front().offset || offset > selected_spelled.back().offset) { return SelectionTree::Unselected; + } + // Find the token, if it exists. auto It = llvm::partition_point(selected_spelled, - [&](const Tok& T) { return T.offset < Offset; }); - if(It != selected_spelled.end() && It->offset == Offset) + [&](const Tok& T) { return T.offset < offset; }); + if(It != selected_spelled.end() && It->offset == offset) { return It->selected; - return NoTokens; + } + + return no_tokens; } // Decomposes Loc and returns the offset if the file ID is SelFile. - std::optional offsetInSelFile(SourceLocation Loc) const { + std::optional offset_in_sel_file(clang::SourceLocation location) const { // Decoding Loc with SM.getDecomposedLoc is relatively expensive. // But SourceLocations for a file are numerically contiguous, so we // can use cheap integer operations instead. - if(Loc < selected_file_range.getBegin() || Loc >= selected_file_range.getEnd()) + if(location < selected_file_range.getBegin() || location >= selected_file_range.getEnd()) { return std::nullopt; + } + // FIXME: subtracting getRawEncoding() is dubious, move this logic into SM. - return Loc.getRawEncoding() - selected_file_range.getBegin().getRawEncoding(); + return location.getRawEncoding() - selected_file_range.getBegin().getRawEncoding(); } - SourceLocation getExpansionStart(SourceLocation Loc) const { - while(Loc.isMacroID()) - Loc = SM.getImmediateExpansionRange(Loc).getBegin(); - return Loc; + clang::SourceLocation get_expansion_start(clang::SourceLocation location) const { + while(location.isMacroID()) { + location = SM.getImmediateExpansionRange(location).getBegin(); + } + return location; } struct Tok { @@ -578,64 +624,75 @@ private: }; std::vector selected_spelled; - llvm::ArrayRef maybe_selected_expanded; - FileID selected_file; - SourceRange selected_file_range; - const SourceManager& SM; + llvm::ArrayRef maybe_selected_expanded; + clang::FileID selected_file; + clang::SourceRange selected_file_range; + const clang::SourceManager& SM; }; // Show the type of a node for debugging. -void printNodeKind(llvm::raw_ostream& OS, const DynTypedNode& N) { - if(const TypeLoc* TL = N.get()) { +void print_node_kind(llvm::raw_ostream& os, const clang::DynTypedNode& node) { + if(const clang::TypeLoc* TL = node.get()) { // TypeLoc is a hierarchy, but has only a single ASTNodeKind. // Synthesize the name from the Type subclass (except for QualifiedTypeLoc). - if(TL->getTypeLocClass() == TypeLoc::Qualified) - OS << "QualifiedTypeLoc"; - else - OS << TL->getType()->getTypeClassName() << "TypeLoc"; + if(TL->getTypeLocClass() == clang::TypeLoc::Qualified) { + os << "QualifiedTypeLoc"; + } else { + os << TL->getType()->getTypeClassName() << "TypeLoc"; + } } else { - OS << N.getNodeKind().asStringRef(); + os << node.getNodeKind().asStringRef(); } } /// FIXME: Remove in release mode? -std::string printNodeToString(const DynTypedNode& N, const PrintingPolicy& PP) { +std::string print_node_to_string(const clang::DynTypedNode& node, + const clang::PrintingPolicy& printing_policy) { std::string S; llvm::raw_string_ostream OS(S); - printNodeKind(OS, N); + print_node_kind(OS, node); return std::move(OS.str()); } -bool isImplicit(const Stmt* S) { +bool is_implicit(const clang::Stmt* statement) { // Some Stmts are implicit and shouldn't be traversed, but there's no // "implicit" attribute on Stmt/Expr. // Unwrap implicit casts first if present (other nodes too?). - if(auto* ICE = llvm::dyn_cast(S)) - S = ICE->getSubExprAsWritten(); + if(auto* ICE = llvm::dyn_cast(statement)) { + statement = ICE->getSubExprAsWritten(); + } + // Implicit this in a MemberExpr is not filtered out by RecursiveASTVisitor. // It would be nice if RAV handled this (!shouldTraverseImplicitCode()). - if(auto* CTI = llvm::dyn_cast(S)) - if(CTI->isImplicit()) + if(auto* CTI = llvm::dyn_cast(statement)) { + if(CTI->isImplicit()) { return true; + } + } + // Make sure implicit access of anonymous structs don't end up owning tokens. - if(auto* ME = llvm::dyn_cast(S)) { - if(auto* FD = llvm::dyn_cast(ME->getMemberDecl())) - if(FD->isAnonymousStructOrUnion()) + if(auto* ME = llvm::dyn_cast(statement)) { + if(auto* FD = llvm::dyn_cast(ME->getMemberDecl())) { + if(FD->isAnonymousStructOrUnion()) { // If Base is an implicit CXXThis, then the whole MemberExpr has no // tokens. If it's a normal e.g. DeclRef, we treat the MemberExpr like // an implicit cast. - return isImplicit(ME->getBase()); + return is_implicit(ME->getBase()); + } + } } + // Refs to operator() and [] are (almost?) always implicit as part of calls. - if(auto* DRE = llvm::dyn_cast(S)) { - if(auto* FD = llvm::dyn_cast(DRE->getDecl())) { + if(auto* DRE = llvm::dyn_cast(statement)) { + if(auto* FD = llvm::dyn_cast(DRE->getDecl())) { switch(FD->getOverloadedOperator()) { - case OO_Call: - case OO_Subscript: return true; + case clang::OO_Call: + case clang::OO_Subscript: return true; default: break; } } } + return false; } @@ -647,15 +704,15 @@ bool isImplicit(const Stmt* S) { // selected or contain some nodes that are. // // For simple cases (not inside macros) we prune subtrees that don't intersect. -class SelectionVisitor : public RecursiveASTVisitor { +class SelectionVisitor : public clang::RecursiveASTVisitor { public: // Runs the visitor to gather selected nodes and their ancestors. // If there is any selection, the root (TUDecl) is the first node. static std::deque collect(CompilationUnit& unit, - const PrintingPolicy& PP, + const clang::PrintingPolicy& printing_policy, LocalSourceRange range, - FileID fid) { - SelectionVisitor V(unit, PP, range, fid); + clang::FileID fid) { + SelectionVisitor V(unit, printing_policy, range, fid); V.TraverseAST(unit.context()); assert(V.stack.size() == 1 && "Unpaired push/pop?"); assert(V.stack.top() == &V.nodes.front()); @@ -672,10 +729,16 @@ public: // Two categories of nodes are not "well-behaved": // - those without source range information, we don't record those // - those that can't be stored in DynTypedNode. - bool TraverseDecl(Decl* X) { - if(llvm::isa_and_nonnull(X)) { - // Already pushed by constructor. - return Base::TraverseDecl(X); + bool TraverseDecl(clang::Decl* X) { + // Already pushed by constructor. + if(llvm::isa_and_nonnull(X)) { + /// Top level decls cover the selection. + for(auto decl: unit.top_level_decls()) { + if(!TraverseDecl(decl)) { + return false; + } + } + return true; } // Base::TraverseDecl will suppress children, but not this node itself. @@ -689,50 +752,55 @@ public: return traverse_node(X, [&] { return Base::TraverseDecl(X); }); } - bool TraverseTypeLoc(TypeLoc X) { + bool TraverseTypeLoc(clang::TypeLoc X) { return traverse_node(&X, [&] { return Base::TraverseTypeLoc(X); }); } - bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc& X) { + bool TraverseTemplateArgumentLoc(const clang::TemplateArgumentLoc& X) { return traverse_node(&X, [&] { return Base::TraverseTemplateArgumentLoc(X); }); } - bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc X) { + bool TraverseNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc X) { return traverse_node(&X, [&] { return Base::TraverseNestedNameSpecifierLoc(X); }); } - bool TraverseConstructorInitializer(CXXCtorInitializer* X) { + bool TraverseConstructorInitializer(clang::CXXCtorInitializer* X) { return traverse_node(X, [&] { return Base::TraverseConstructorInitializer(X); }); } - bool TraverseCXXBaseSpecifier(const CXXBaseSpecifier& X) { + bool TraverseCXXBaseSpecifier(const clang::CXXBaseSpecifier& X) { return traverse_node(&X, [&] { return Base::TraverseCXXBaseSpecifier(X); }); } - bool TraverseAttr(Attr* X) { + bool TraverseAttr(clang::Attr* X) { return traverse_node(X, [&] { return Base::TraverseAttr(X); }); } - bool TraverseConceptReference(ConceptReference* X) { + bool TraverseConceptReference(clang::ConceptReference* X) { return traverse_node(X, [&] { return Base::TraverseConceptReference(X); }); } // Stmt is the same, but this form allows the data recursion optimization. - bool dataTraverseStmtPre(Stmt* X) { - if(!X || isImplicit(X)) + bool dataTraverseStmtPre(clang::Stmt* X) { + if(!X || is_implicit(X)) { return false; - auto N = DynTypedNode::create(*X); - if(safely_skipable(N)) + } + + auto N = clang::DynTypedNode::create(*X); + if(safely_skipable(N)) { return false; + } + push(std::move(N)); - if(shouldSkipChildren(X)) { + if(should_skip_children(X)) { pop(); return false; } + return true; } - bool dataTraverseStmtPost(Stmt* X) { + bool dataTraverseStmtPost(clang::Stmt* X) { pop(); return true; } @@ -742,27 +810,25 @@ public: // This means we'd never see 'int' in 'const int'! Work around that here. // (The reason for the behavior is to avoid traversing the nested Type twice, // but we ignore TraverseType anyway). - bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QX) { - return traverse_node(&QX, [&] { return TraverseTypeLoc(QX.getUnqualifiedLoc()); }); + bool TraverseQualifiedTypeLoc(clang::QualifiedTypeLoc QX) { + return traverse_node(&QX, [&] { + return TraverseTypeLoc(QX.getUnqualifiedLoc()); + }); } - bool TraverseObjCProtocolLoc(ObjCProtocolLoc PL) { - return traverse_node(&PL, [&] { return Base::TraverseObjCProtocolLoc(PL); }); - } - - // Uninteresting parts of the AST that don't have locations within them. - bool TraverseNestedNameSpecifier(NestedNameSpecifier*) { + bool TraverseType(clang::QualType) { return true; } - bool TraverseType(QualType) { + // Uninteresting parts of the AST that don't have locations within them. + bool TraverseNestedNameSpecifier(clang::NestedNameSpecifier*) { return true; } // The DeclStmt for the loop variable claims to cover the whole range // inside the parens, this causes the range-init expression to not be hit. // Traverse the loop VarDecl instead, which has the right source range. - bool TraverseCXXForRangeStmt(CXXForRangeStmt* S) { + bool TraverseCXXForRangeStmt(clang::CXXForRangeStmt* S) { return traverse_node(S, [&] { return TraverseStmt(S->getInit()) && TraverseDecl(S->getLoopVariable()) && TraverseStmt(S->getRangeInit()) && TraverseStmt(S->getBody()); @@ -770,16 +836,16 @@ public: } // OpaqueValueExpr blocks traversal, we must explicitly traverse it. - bool TraverseOpaqueValueExpr(OpaqueValueExpr* E) { + bool TraverseOpaqueValueExpr(clang::OpaqueValueExpr* E) { return traverse_node(E, [&] { return TraverseStmt(E->getSourceExpr()); }); } // We only want to traverse the *syntactic form* to understand the selection. - bool TraversePseudoObjectExpr(PseudoObjectExpr* E) { + bool TraversePseudoObjectExpr(clang::PseudoObjectExpr* E) { return traverse_node(E, [&] { return TraverseStmt(E->getSyntacticForm()); }); } - bool TraverseTypeConstraint(const TypeConstraint* C) { + bool TraverseTypeConstraint(const clang::TypeConstraint* C) { if(auto* E = C->getImmediatelyDeclaredConstraint()) { // Technically this expression is 'implicit' and not traversed by the RAV. // However, the range is correct, so we visit expression to avoid adding @@ -794,23 +860,23 @@ public: // PredefinedExpr like __func__ has a StringLiteral child for its value. // It's not written, so don't traverse it. - Stmt::child_range getStmtChildren(PredefinedExpr*) { - return {StmtIterator{}, StmtIterator{}}; + clang::Stmt::child_range getStmtChildren(clang::PredefinedExpr*) { + return {clang::StmtIterator{}, clang::StmtIterator{}}; } private: using Base = RecursiveASTVisitor; SelectionVisitor(CompilationUnit& unit, - const PrintingPolicy& PP, + const clang::PrintingPolicy& printing_policy, LocalSourceRange range, - FileID SelFile) : + clang::FileID selected_file) : unit(unit), SM(unit.context().getSourceManager()), lang_opts(unit.context().getLangOpts()), - print_policy(PP), checker(unit, SelFile, range, SM), + print_policy(printing_policy), checker(unit, selected_file, range, SM), unclaimed_expanded_tokens(unit.expanded_tokens()) { // Ensure we have a node for the TU decl, regardless of traversal scope. nodes.emplace_back(); - nodes.back().data = DynTypedNode::create(*unit.context().getTranslationUnitDecl()); + nodes.back().data = clang::DynTypedNode::create(*unit.context().getTranslationUnitDecl()); nodes.back().parent = nullptr; nodes.back().selected = SelectionTree::Unselected; stack.push(&nodes.back()); @@ -819,16 +885,20 @@ private: // Generic case of TraverseFoo. Func should be the call to Base::TraverseFoo. // Node is always a pointer so the generic code can handle any null checks. template - bool traverse_node(T* Node, const Func& Body) { - if(Node == nullptr) + bool traverse_node(T* node, const Func& Body) { + if(node == nullptr) { return true; - auto N = DynTypedNode::create(*Node); - if(safely_skipable(N)) + } + + auto N = clang::DynTypedNode::create(*node); + if(safely_skipable(N)) { return true; - push(DynTypedNode::create(*Node)); - bool Ret = Body(); + } + + push(std::move(N)); + bool ret = Body(); pop(); - return Ret; + return ret; } // HIT TESTING @@ -857,67 +927,71 @@ private: // An optimization for a common case: nodes outside macro expansions that // don't intersect the selection may be recursively skipped. - bool safely_skipable(const DynTypedNode& N) { - SourceRange S = getSourceRange(N); - if(auto* TL = N.get()) { + bool safely_skipable(const clang::DynTypedNode& N) { + clang::SourceRange S = get_source_range(N); + if(auto* TL = N.get()) { // FIXME: TypeLoc::getBeginLoc()/getEndLoc() are pretty fragile // heuristics. We should consider only pruning critical TypeLoc nodes, to // be more robust. // AttributedTypeLoc may point to the attribute's range, NOT the modified // type's range. - if(auto AT = TL->getAs()) + if(auto AT = TL->getAs()) { S = AT.getModifiedLoc().getSourceRange(); + } } // SourceRange often doesn't manage to accurately cover attributes. // Fortunately, attributes are rare. - if(llvm::any_of(get_attributes(N), [](const Attr* A) { return !A->isImplicit(); })) { + if(llvm::any_of(get_attributes(N), [](const clang::Attr* A) { return !A->isImplicit(); })) { return false; } - if(!checker.mayHit(S)) { + if(!checker.may_hit(S)) { log::debug("{2}skip: {0} {1}", - printNodeToString(N, print_policy), + print_node_to_string(N, print_policy), S.printToString(SM), indent()); return true; } + return false; } // There are certain nodes we want to treat as leaves in the SelectionTree, // although they do have children. - bool shouldSkipChildren(const Stmt* X) const { + bool should_skip_children(const clang::Stmt* X) const { // UserDefinedLiteral (e.g. 12_i) has two children (12 and _i). // Unfortunately TokenBuffer sees 12_i as one token and can't split it. // So we treat UserDefinedLiteral as a leaf node, owning the token. - return llvm::isa(X); + return llvm::isa(X); } // Pushes a node onto the ancestor stack. Pairs with pop(). // Performs early hit detection for some nodes (on the earlySourceRange). - void push(DynTypedNode node) { - SourceRange Early = earlySourceRange(node); + void push(clang::DynTypedNode node) { + clang::SourceRange Early = early_source_range(node); log::debug("{2}push: {0} {1}", - printNodeToString(node, print_policy), + print_node_to_string(node, print_policy), node.getSourceRange().printToString(SM), indent()); nodes.emplace_back(); nodes.back().data = std::move(node); nodes.back().parent = stack.top(); - nodes.back().selected = NoTokens; + nodes.back().selected = no_tokens; stack.push(&nodes.back()); - claimRange(Early, nodes.back().selected); + claim_range(Early, nodes.back().selected); } // Pops a node off the ancestor stack, and finalizes it. Pairs with push(). // Performs primary hit detection. void pop() { Node& N = *stack.top(); - log::debug("{1}pop: {0}", printNodeToString(N.data, print_policy), indent(-1)); - claimTokensFor(N.data, N.selected); - if(N.selected == NoTokens) + log::debug("{1}pop: {0}", print_node_to_string(N.data, print_policy), indent(-1)); + claim_tokens_for(N.data, N.selected); + if(N.selected == no_tokens) { N.selected = SelectionTree::Unselected; + } + if(N.selected || !N.children.empty()) { // Attach to the tree. N.parent->children.push_back(&N); @@ -926,14 +1000,15 @@ private: assert(&N == &nodes.back()); nodes.pop_back(); } + stack.pop(); } // Returns the range of tokens that this node will claim directly, and // is not available to the node's children. // Usually empty, but sometimes children cover tokens but shouldn't own them. - SourceRange earlySourceRange(const DynTypedNode& N) { - if(const Decl* VD = N.get()) { + clang::SourceRange early_source_range(const clang::DynTypedNode& N) { + if(const clang::Decl* VD = N.get()) { // We want the name in the var-decl to be claimed by the decl itself and // not by any children. Ususally, we don't need this, because source // ranges of children are not overlapped with their parent's. @@ -949,33 +1024,37 @@ private: // rather than the TypeLoc nested inside it. // We still traverse the TypeLoc, because it may contain other targeted // things like the T in ~Foo(). - if(const auto* CDD = N.get()) + if(const auto* CDD = N.get()) { return CDD->getNameInfo().getNamedTypeInfo()->getTypeLoc().getBeginLoc(); - - if(const auto* ME = N.get()) { - auto NameInfo = ME->getMemberNameInfo(); - if(NameInfo.getName().getNameKind() == DeclarationName::CXXDestructorName) - return NameInfo.getNamedTypeInfo()->getTypeLoc().getBeginLoc(); } - return SourceRange(); + if(const auto* ME = N.get()) { + auto name_info = ME->getMemberNameInfo(); + if(name_info.getName().getNameKind() == clang::DeclarationName::CXXDestructorName) { + return name_info.getNamedTypeInfo()->getTypeLoc().getBeginLoc(); + } + } + + return clang::SourceRange(); } // Claim tokens for N, after processing its children. // By default this claims all unclaimed tokens in getSourceRange(). // We override this if we want to claim fewer tokens (e.g. there are gaps). - void claimTokensFor(const DynTypedNode& N, SelectionTree::SelectionKind& Result) { + void claim_tokens_for(const clang::DynTypedNode& N, SelectionTree::SelectionKind& result) { // CXXConstructExpr often shows implicit construction, like `string s;`. // Don't associate any tokens with it unless there's some syntax like {}. // This prevents it from claiming 's', its primary location. - if(const auto* CCE = N.get()) { - claimRange(CCE->getParenOrBraceRange(), Result); + if(const auto* CCE = N.get()) { + claim_range(CCE->getParenOrBraceRange(), result); return; } + // ExprWithCleanups is always implicit. It often wraps CXXConstructExpr. // Prevent it claiming 's' in the case above. - if(N.get()) + if(N.get()) { return; + } // Declarators nest "inside out", with parent types inside child ones. // Instead of claiming the whole range (clobbering parent tokens), carefully @@ -1002,93 +1081,104 @@ private: // In each row // --- represents unclaimed parts of the SourceRange. // ### represents parts that children already claimed. - if(const auto* TL = N.get()) { - if(auto PTL = TL->getAs()) { - claimRange(PTL.getLParenLoc(), Result); - claimRange(PTL.getRParenLoc(), Result); + if(const auto* TL = N.get()) { + if(auto PTL = TL->getAs()) { + claim_range(PTL.getLParenLoc(), result); + claim_range(PTL.getRParenLoc(), result); return; } - if(auto ATL = TL->getAs()) { - claimRange(ATL.getBracketsRange(), Result); + + if(auto ATL = TL->getAs()) { + claim_range(ATL.getBracketsRange(), result); return; } - if(auto PTL = TL->getAs()) { - claimRange(PTL.getStarLoc(), Result); + + if(auto PTL = TL->getAs()) { + claim_range(PTL.getStarLoc(), result); return; } - if(auto FTL = TL->getAs()) { - claimRange(SourceRange(FTL.getLParenLoc(), FTL.getEndLoc()), Result); + + if(auto FTL = TL->getAs()) { + claim_range(clang::SourceRange(FTL.getLParenLoc(), FTL.getEndLoc()), result); return; } } - claimRange(getSourceRange(N), Result); + claim_range(get_source_range(N), result); } // Perform hit-testing of a complete Node against the selection. // This runs for every node in the AST, and must be fast in common cases. // This is usually called from pop(), so we can take children into account. // The existing state of Result is relevant. - void claimRange(SourceRange S, SelectionTree::SelectionKind& Result) { - for(const auto& ClaimedRange: unclaimed_expanded_tokens.erase(unit.expanded_tokens(S))) - update(Result, checker.test(ClaimedRange)); + void claim_range(clang::SourceRange S, SelectionTree::SelectionKind& result) { + for(const auto& claimed_range: unclaimed_expanded_tokens.erase(unit.expanded_tokens(S))) { + update(result, checker.test(claimed_range)); + } - if(Result && Result != NoTokens) + if(result && result != no_tokens) { log::debug("{1}hit selection: {0}", S.printToString(SM), indent()); + } } - std::string indent(int Offset = 0) { + std::string indent(int offset = 0) { // Cast for signed arithmetic. - int Amount = int(stack.size()) + Offset; - assert(Amount >= 0); - return std::string(Amount, ' '); + int amount = int(stack.size()) + offset; + assert(amount >= 0); + return std::string(amount, ' '); } - SourceManager& SM; - const LangOptions& lang_opts; - const PrintingPolicy& print_policy; + clang::SourceManager& SM; + const clang::LangOptions& lang_opts; + const clang::PrintingPolicy& print_policy; CompilationUnit& unit; std::stack stack; SelectionTester checker; - IntervalSet unclaimed_expanded_tokens; + IntervalSet unclaimed_expanded_tokens; std::deque nodes; // Stable pointers as we add more nodes. }; } // namespace -llvm::SmallString<256> abbreviatedString(DynTypedNode N, const PrintingPolicy& PP) { - llvm::SmallString<256> Result; +llvm::SmallString<256> abbreviated_string(clang::DynTypedNode node, + const clang::PrintingPolicy& printing_policy) { + llvm::SmallString<256> result; { - llvm::raw_svector_ostream OS(Result); - N.print(OS, PP); + llvm::raw_svector_ostream os(result); + node.print(os, printing_policy); } - auto Pos = Result.find('\n'); - if(Pos != llvm::StringRef::npos) { - bool MoreText = !llvm::all_of(Result.str().drop_front(Pos), llvm::isSpace); - Result.resize(Pos); - if(MoreText) { - Result.append(" …"); + auto pos = result.find('\n'); + if(pos != llvm::StringRef::npos) { + bool more_text = !llvm::all_of(result.str().drop_front(pos), llvm::isSpace); + result.resize(pos); + if(more_text) { + result.append(" …"); } } - return Result; + return result; } -void SelectionTree::print(llvm::raw_ostream& OS, const SelectionTree::Node& N, int Indent) const { - if(N.selected) - OS.indent(Indent - 1) << (N.selected == SelectionTree::Complete ? '*' : '.'); - else - OS.indent(Indent); - printNodeKind(OS, N.data); - OS << ' ' << abbreviatedString(N.data, print_policy) << "\n"; - for(const Node* Child: N.children) - print(OS, *Child, Indent + 2); +void SelectionTree::print(llvm::raw_ostream& os, + const SelectionTree::Node& node, + int indent) const { + if(node.selected) { + os.indent(indent - 1) << (node.selected == SelectionTree::Complete ? '*' : '.'); + } else { + os.indent(indent); + } + + print_node_kind(os, node.data); + os << ' ' << abbreviated_string(node.data, print_policy) << "\n"; + for(const Node* child: node.children) { + print(os, *child, indent + 2); + } } std::string SelectionTree::Node::kind() const { std::string S; llvm::raw_string_ostream OS(S); - printNodeKind(OS, data); + print_node_kind(OS, data); return std::move(OS.str()); } @@ -1110,7 +1200,7 @@ bool SelectionTree::create_each(CompilationUnit& unit, auto location = unit.create_location(unit.interested_file(), begin); // Prefer right token over left. - for(const syntax::Token& token: llvm::reverse(unit.spelled_tokens_touch(location))) { + for(const clang::syntax::Token& token: llvm::reverse(unit.spelled_tokens_touch(location))) { if(should_ignore(token)) { continue; } @@ -1146,19 +1236,19 @@ SelectionTree::SelectionTree(CompilationUnit& unit, LocalSourceRange range) : print_policy(unit.context().getLangOpts()) { // No fundamental reason the selection needs to be in the main file, // but that's all clice has needed so far. - const SourceManager& SM = unit.context().getSourceManager(); - FileID fid = SM.getMainFileID(); + const clang::SourceManager& SM = unit.context().getSourceManager(); + clang::FileID fid = SM.getMainFileID(); print_policy.TerseOutput = true; print_policy.IncludeNewlines = false; auto [begin, end] = range; - log::debug( - "Computing selection for {0}", - SourceRange(SM.getComposedLoc(fid, begin), SM.getComposedLoc(fid, end)).printToString(SM)); + log::debug("Computing selection for {0}", + clang::SourceRange(SM.getComposedLoc(fid, begin), SM.getComposedLoc(fid, end)) + .printToString(SM)); nodes = SelectionVisitor::collect(unit, print_policy, range, fid); m_root = nodes.empty() ? nullptr : &nodes.front(); - recordMetrics(*this, unit.context().getLangOpts()); + record_metrics(*this, unit.context().getLangOpts()); /// FIXME: dlog("Built selection tree\n{0}", *this); } @@ -1174,34 +1264,45 @@ const Node* SelectionTree::common_ancestor() const { return ancestor != m_root ? ancestor : nullptr; } -const DeclContext& SelectionTree::Node::decl_context() const { - for(const Node* CurrentNode = this; CurrentNode != nullptr; CurrentNode = CurrentNode->parent) { - if(const Decl* Current = CurrentNode->get()) { - if(CurrentNode != this) - if(auto* DC = dyn_cast(Current)) +const clang::DeclContext& SelectionTree::Node::decl_context() const { + for(const Node* current_node = this; current_node != nullptr; + current_node = current_node->parent) { + if(const clang::Decl* current = current_node->get()) { + if(current_node != this) { + if(auto* DC = dyn_cast(current)) { return *DC; - return *Current->getLexicalDeclContext(); + } + } + + return *current->getLexicalDeclContext(); } - if(const auto* LE = CurrentNode->get()) - if(CurrentNode != this) + + if(const auto* LE = current_node->get()) { + if(current_node != this) { return *LE->getCallOperator(); + } + } } llvm_unreachable("A tree must always be rooted at TranslationUnitDecl."); } clang::SourceRange SelectionTree::Node::source_range() const { - return getSourceRange(data); + return get_source_range(data); } const SelectionTree::Node& SelectionTree::Node::ignore_implicit() const { - if(children.size() == 1 && children.front()->source_range() == source_range()) + if(children.size() == 1 && children.front()->source_range() == source_range()) { return children.front()->ignore_implicit(); + } + return *this; } const SelectionTree::Node& SelectionTree::Node::outer_implicit() const { - if(parent && parent->source_range() == source_range()) + if(parent && parent->source_range() == source_range()) { return parent->outer_implicit(); + } + return *this; } diff --git a/src/AST/Utility.cpp b/src/AST/Utility.cpp index fb41d52a..04dc98b9 100644 --- a/src/AST/Utility.cpp +++ b/src/AST/Utility.cpp @@ -4,9 +4,9 @@ #include "clang/AST/DeclTemplate.h" #include "clang/Basic/SourceManager.h" -namespace clice { +namespace clice::ast { -bool isDefinition(const clang::Decl* decl) { +bool is_definition(const clang::Decl* decl) { if(auto VD = llvm::dyn_cast(decl)) { return VD->isThisDeclarationADefinition(); } @@ -29,7 +29,7 @@ bool isDefinition(const clang::Decl* decl) { return false; } -bool isTemplated(const clang::Decl* decl) { +bool is_templated(const clang::Decl* decl) { if(decl->getDescribedTemplate()) { return true; } @@ -55,7 +55,7 @@ const static clang::CXXRecordDecl* getDeclContextForTemplateInstationPattern(con return nullptr; } -const clang::NamedDecl* instantiatedFrom(const clang::NamedDecl* decl) { +const clang::NamedDecl* instantiated_from(const clang::NamedDecl* decl) { if(auto CTSD = llvm::dyn_cast(decl)) { auto kind = CTSD->getTemplateSpecializationKind(); @@ -134,14 +134,14 @@ const clang::NamedDecl* normalize(const clang::NamedDecl* decl) { decl = llvm::cast(decl->getCanonicalDecl()); - if(auto ND = instantiatedFrom(llvm::cast(decl))) { + if(auto ND = instantiated_from(llvm::cast(decl))) { return llvm::cast(ND->getCanonicalDecl()); } return decl; } -std::string getDeclName(const clang::NamedDecl* decl) { +std::string name_of(const clang::NamedDecl* decl) { llvm::SmallString<128> result; auto name = decl->getDeclName(); @@ -203,7 +203,7 @@ std::string getDeclName(const clang::NamedDecl* decl) { return result.str().str(); } -clang::QualType typeForDecl(const clang::NamedDecl* decl) { +clang::QualType type_of(const clang::NamedDecl* decl) { if(auto VD = llvm::dyn_cast(decl)) { return VD->getType(); } @@ -235,7 +235,7 @@ clang::QualType typeForDecl(const clang::NamedDecl* decl) { return clang::QualType(); } -const clang::NamedDecl* declForType(clang::QualType type) { +const clang::NamedDecl* decl_of(clang::QualType type) { if(type.isNull()) { return nullptr; } @@ -265,18 +265,10 @@ const clang::NamedDecl* declForType(clang::QualType type) { return decl; } - return instantiatedFrom(TST->getAsCXXRecordDecl()); + return instantiated_from(TST->getAsCXXRecordDecl()); } return nullptr; } -void dumpInclude(clang::SourceManager& srcMgr, clang::SourceLocation loc) { - auto file = srcMgr.getFileID(loc); - for(auto includeLoc = srcMgr.getIncludeLoc(file); includeLoc.isValid(); - includeLoc = srcMgr.getIncludeLoc(file)) { - includeLoc.dump(srcMgr); - file = srcMgr.getFileID(includeLoc); - } -} -} // namespace clice +} // namespace clice::ast diff --git a/src/Async/Async.cpp b/src/Async/Async.cpp index 147c3205..0a347809 100644 --- a/src/Async/Async.cpp +++ b/src/Async/Async.cpp @@ -57,13 +57,33 @@ void run() { uv_check_result(uv_run(loop, UV_RUN_DEFAULT)); - uv_close(reinterpret_cast(&idle), nullptr); + stop(); /// Run agian to cleanup the loop. uv_check_result(uv_run(loop, UV_RUN_DEFAULT)); uv_check_result(uv_loop_close(loop)); + /// Clear all unfinished tasks. + for(auto task: tasks) { + if(task->cancelled()) { + task->resume(); + } else { + task->destroy(); + } + } + loop = nullptr; } +void stop() { + auto walk_cb = [](uv_handle_s* handle, void* arg) { + if(!uv_is_closing(handle)) { + uv_close(handle, nullptr); + } + }; + + /// Close all handles. + uv_walk(async::loop, walk_cb, nullptr); +} + } // namespace clice::async diff --git a/src/Compiler/Command.cpp b/src/Compiler/Command.cpp index 3b0fdf12..72a218eb 100644 --- a/src/Compiler/Command.cpp +++ b/src/Compiler/Command.cpp @@ -429,7 +429,10 @@ auto CompilationDatabase::get_command(this Self& self, } else { /// FIXME: Use a better way to handle fallback command. info.dictionary = {}; - info.arguments = {"clang++", "-std=c++20"}; + info.arguments = { + self.save_string("clang++").data(), + self.save_string("-std=c++20").data(), + }; } auto append_argument = [&](llvm::StringRef argument) { diff --git a/src/Compiler/Compilation.cpp b/src/Compiler/Compilation.cpp index 1b011f3d..89ef2ec7 100644 --- a/src/Compiler/Compilation.cpp +++ b/src/Compiler/Compilation.cpp @@ -274,8 +274,9 @@ CompilationResult compile(CompilationParams& params) { CompilationResult compile(CompilationParams& params, PCHInfo& out) { assert(!params.output_file.empty() && "PCH file path cannot be empty"); - /// out.command = params.arguments.str(); - /// FIXME: out.deps = info->deps(); + /// Record the begin time of PCH building. + auto now = std::chrono::system_clock::now().time_since_epoch(); + out.mtime = std::chrono::duration_cast(now).count(); return run_clang( params, @@ -294,11 +295,14 @@ CompilationResult compile(CompilationParams& params, PCHInfo& out) { [&](CompilationUnit& unit) { out.path = params.output_file.str(); out.preamble = unit.interested_content(); + out.deps = unit.deps(); + out.arguments = params.arguments; }); } CompilationResult compile(CompilationParams& params, PCMInfo& out) { assert(!params.output_file.empty() && "PCM file path cannot be empty"); + return run_clang( params, [&](clang::CompilerInstance& instance) { diff --git a/src/Compiler/CompilationUnit.cpp b/src/Compiler/CompilationUnit.cpp index 1a8b7382..cb7a6000 100644 --- a/src/Compiler/CompilationUnit.cpp +++ b/src/Compiler/CompilationUnit.cpp @@ -233,7 +233,7 @@ index::SymbolID CompilationUnit::getSymbolID(const clang::NamedDecl* decl) { hash = llvm::xxh3_64bits(USR); impl->symbolHashCache.try_emplace(decl, hash); } - return index::SymbolID{hash, getDeclName(decl)}; + return index::SymbolID{hash, ast::name_of(decl)}; } index::SymbolID CompilationUnit::getSymbolID(const clang::MacroInfo* macro) { diff --git a/src/Driver/clice.cc b/src/Driver/clice.cc index 4356a771..a1da3a80 100644 --- a/src/Driver/clice.cc +++ b/src/Driver/clice.cc @@ -161,6 +161,8 @@ int main(int argc, const char** argv) { async::run(); + log::info("clice exit normally!"); + return 0; } diff --git a/src/Feature/CodeCompletion.cpp b/src/Feature/CodeCompletion.cpp index 51ea1d83..d4dd9d9d 100644 --- a/src/Feature/CodeCompletion.cpp +++ b/src/Feature/CodeCompletion.cpp @@ -211,7 +211,7 @@ public: /// Render edit text for declaration. std::string render(const clang::NamedDecl* decl) { - return getDeclName(decl); + return ast::name_of(decl); } void process_candidate(clang::CodeCompletionResult& candidate) { @@ -269,7 +269,7 @@ public: auto declaration = candidate.Declaration; /// if(declaration) /// auto name = getDeclName(); - if(!check_name(getDeclName(declaration))) { + if(!check_name(ast::name_of(declaration))) { return; } @@ -316,7 +316,7 @@ public: /// Add overload set. for(auto& [_, overload_set]: overloads) { CompletionItem item; - item.label = getDeclName(overload_set.first); + item.label = ast::name_of(overload_set.first); item.kind = CompletionItemKind::Function; item.score = overload_set.score; item.edit.range = prefix.range; diff --git a/src/Feature/DocumentSymbol.cpp b/src/Feature/DocumentSymbol.cpp index 4d1504e2..8371a9bd 100644 --- a/src/Feature/DocumentSymbol.cpp +++ b/src/Feature/DocumentSymbol.cpp @@ -15,10 +15,10 @@ class DocumentSymbolCollector : public FilteredASTVisitor; - DocumentSymbolCollector(CompilationUnit& unit, bool interestedOnly) : - Base(unit, interestedOnly, std::nullopt) {} + DocumentSymbolCollector(CompilationUnit& unit, bool interested_only) : + Base(unit, interested_only) {} - bool isInterested(clang::Decl* decl) { + bool is_interested(clang::Decl* decl) { switch(decl->getKind()) { case clang::Decl::Namespace: case clang::Decl::Enum: @@ -44,8 +44,8 @@ public: } } - bool hookTraverseDecl(clang::Decl* decl, auto MF) { - if(!isInterested(decl)) { + bool on_traverse_decl(clang::Decl* decl, auto MF) { + if(!is_interested(decl)) { return (this->*MF)(decl); } @@ -53,13 +53,13 @@ public: auto [fid, selectionRange] = unit.decompose_range(unit.expansion_location(ND->getLocation())); - auto& frame = interestedOnly ? result : sharedResult[fid]; + auto& frame = interested_only ? result : sharedResult[fid]; auto cursor = frame.cursor; /// Add new symbol. auto& symbol = frame.cursor->emplace_back(); symbol.kind = SymbolKind::from(decl); - symbol.name = getDeclName(ND); + symbol.name = ast::name_of(ND); symbol.selectionRange = selectionRange; symbol.range = selectionRange; @@ -69,7 +69,7 @@ public: bool res = (this->*MF)(decl); /// When all children node are set, go back to last node. - (interestedOnly ? result : sharedResult[fid]).cursor = cursor; + (interested_only ? result : sharedResult[fid]).cursor = cursor; return res; } diff --git a/src/Feature/FoldingRange.cpp b/src/Feature/FoldingRange.cpp index 648bd9b6..78cfe122 100644 --- a/src/Feature/FoldingRange.cpp +++ b/src/Feature/FoldingRange.cpp @@ -9,8 +9,8 @@ namespace { class FoldingRangeCollector : public FilteredASTVisitor { public: - FoldingRangeCollector(CompilationUnit& unit, bool interestedOnly) : - FilteredASTVisitor(unit, interestedOnly, std::nullopt) {} + FoldingRangeCollector(CompilationUnit& unit, bool interested_only) : + FilteredASTVisitor(unit, interested_only) {} constexpr static auto LastColOfLine = std::numeric_limits::max(); @@ -106,17 +106,18 @@ public: bool VisitCallExpr(const clang::CallExpr* call) { auto tokens = unit.expanded_tokens(call->getSourceRange()); - if(tokens.back().kind() != clang::tok::r_paren) + if(tokens.empty() || tokens.back().kind() != clang::tok::r_paren) { return true; + } - auto rightParen = tokens.back().location(); + auto right_paren = tokens.back().location(); size_t depth = 0; while(!tokens.empty()) { auto kind = tokens.back().kind(); if(kind == clang::tok::r_paren) depth += 1; else if(kind == clang::tok::l_paren && --depth == 0) { - add_range({tokens.back().location(), rightParen}, + add_range({tokens.back().location(), right_paren}, FoldingRangeKind::FunctionCall, "(...)"); break; @@ -144,16 +145,16 @@ public: } auto build_for_file(CompilationUnit& unit) { - TraverseTranslationUnitDecl(unit.tu()); - collectDrectives(unit.directives()[unit.interested_file()]); + TraverseDecl(unit.tu()); + collect_drectives(unit.directives()[unit.interested_file()]); std::ranges::sort(result, refl::less); return std::move(result); } auto build_for_index(CompilationUnit& unit) { - TraverseTranslationUnitDecl(unit.tu()); + TraverseDecl(unit.tu()); for(auto& [fid, directive]: unit.directives()) { - collectDrectives(directive); + collect_drectives(directive); } for(auto& [fid, ranges]: index_result) { @@ -197,7 +198,7 @@ private: return; } - auto& ranges = interestedOnly ? result : index_result[fid]; + auto& ranges = interested_only ? result : index_result[fid]; ranges.emplace_back(local_range, kind, std::move(text)); } @@ -242,7 +243,7 @@ private: using ASTDirectives = std::remove_reference_t().directives())>; - void collectDrectives(const Directive& directive) { + void collect_drectives(const Directive& directive) { collect_condition_directive(directive.conditions); collect_pragma_region(directive.pragmas); diff --git a/src/Feature/Hover.cpp b/src/Feature/Hover.cpp index 10d55321..e60631c1 100644 --- a/src/Feature/Hover.cpp +++ b/src/Feature/Hover.cpp @@ -65,7 +65,7 @@ std::string getSourceCode(CompilationUnit& unit, const clang::NamedDecl* decl) { Hover hover(CompilationUnit& unit, const clang::NamedDecl* decl) { return Hover{ .kind = SymbolKind::from(decl), - .name = getDeclName(decl), + .name = ast::name_of(decl), .items = getHoverItems(unit, decl), .document = getDocument(unit, decl), .qualifier = getQualifier(unit, decl), diff --git a/src/Feature/InlayHint.cpp b/src/Feature/InlayHint.cpp index d6ff1a9c..d998c985 100644 --- a/src/Feature/InlayHint.cpp +++ b/src/Feature/InlayHint.cpp @@ -133,7 +133,7 @@ private: clang::SourceLocation location, std::vector labels) { auto [fid, offset] = unit.decompose_location(location); - auto& hints = interestedOnly ? result : sharedResult[fid]; + auto& hints = interested_only ? result : sharedResult[fid]; hints.emplace_back(offset, kind, labels); } @@ -172,7 +172,7 @@ public: } // namespace InlayHints inlayHints(CompilationUnit& unit, LocalSourceRange target) { - InlayHintsCollector collector(unit, true, target); + InlayHintsCollector collector(unit, true); collector.TraverseAST(unit.context()); ranges::sort(collector.result, refl::less); return std::move(collector.result); diff --git a/src/Feature/SemanticToken.cpp b/src/Feature/SemanticToken.cpp index fc34ae24..c3f527c0 100644 --- a/src/Feature/SemanticToken.cpp +++ b/src/Feature/SemanticToken.cpp @@ -24,7 +24,7 @@ public: modifiers |= SymbolModifiers::Declaration; } - if(isTemplated(decl)) { + if(ast::is_templated(decl)) { modifiers |= SymbolModifiers::Templated; } @@ -85,7 +85,7 @@ public: public: void add_token(clang::FileID fid, Token& token, SymbolKind kind) { - auto& tokens = interestedOnly ? result : sharedResult[fid]; + auto& tokens = interested_only ? result : sharedResult[fid]; tokens.emplace_back(token.range, kind, SymbolModifiers()); } @@ -110,7 +110,7 @@ public: return; } - auto& tokens = interestedOnly ? result : sharedResult[fid]; + auto& tokens = interested_only ? result : sharedResult[fid]; tokens.emplace_back(range, kind, modifiers); } diff --git a/src/Index/Index.cpp b/src/Index/Index.cpp index 039f9d58..f811b925 100644 --- a/src/Index/Index.cpp +++ b/src/Index/Index.cpp @@ -36,7 +36,7 @@ public: RelationKind kind, clang::SourceLocation location) { assert(decl && "Invalid decl"); - decl = normalize(decl); + decl = ast::normalize(decl); if(location.isMacroID()) { auto spelling = unit.spelling_location(location); @@ -117,10 +117,10 @@ public: relation.range = relationRange; relation.target_symbol = 0; } else if(kind.isBetweenSymbol()) { - auto symbol_id = unit.getSymbolID(normalize(target)); + auto symbol_id = unit.getSymbolID(ast::normalize(target)); relation.target_symbol = symbol_id.hash; } else if(kind.isCall()) { - auto symbol_id = unit.getSymbolID(normalize(target)); + auto symbol_id = unit.getSymbolID(ast::normalize(target)); relation.range = relationRange; relation.target_symbol = symbol_id.hash; } else { @@ -128,7 +128,7 @@ public: } auto& index = getIndex(fid); - auto symbol_id = unit.getSymbolID(normalize(decl)); + auto symbol_id = unit.getSymbolID(ast::normalize(decl)); auto& symbol = index.get_symbol(symbol_id.hash); index.add_relation(symbol, relation); } diff --git a/src/Server/Document.cpp b/src/Server/Document.cpp index 0fb877ac..ff4f599b 100644 --- a/src/Server/Document.cpp +++ b/src/Server/Document.cpp @@ -2,46 +2,176 @@ #include "Server/Server.h" #include "Compiler/Compilation.h" #include "Feature/Diagnostic.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/FileOutputBuffer.h" +#include "llvm/ADT/ScopeExit.h" namespace clice { -async::Task Server::add_document(std::string path, std::string content) { - auto& openFile = opening_files[path]; - openFile.content = content; - - auto& task = openFile.ast_build_task; - - /// If there is already an AST build task, cancel it. - if(!task.empty()) { - task.cancel(); - task.dispose(); +void Server::load_cache_info() { + auto path = path::join(config::cache.dir, "cache.json"); + auto file = llvm::MemoryBuffer::getFile(path); + if(!file) { + log::warn("Fail to load cache info, because: {}", file.getError()); + return; } - /// Create and schedule a new task. - task = build_ast(std::move(path), std::move(content)); - co_await task; + llvm::StringRef content = file.get()->getBuffer(); + auto json = json::parse(content); + if(!json) { + log::warn("Fail to load cache info, invalid json: {}", json.takeError()); + return; + } - co_return &opening_files[path]; + auto object = json->getAsObject(); + if(!object) { + return; + } + + auto version = object->getString("version"); + if(!version) { + log::info("Fail to load cache info, the cache info is outdated"); + return; + } + + if(auto array = object->getArray("pchs")) { + for(auto& pch: *array) { + auto object = pch.getAsObject(); + if(!object) { + continue; + } + + auto file = object->getString("file"); + auto path = object->getString("path"); + auto preamble = object->getString("preamble"); + auto mtime = object->getNumber("mtime"); + auto deps = object->getArray("deps"); + auto arguments = object->getArray("arguments"); + + if(!file || !path || !preamble || !mtime || !deps || !arguments) { + continue; + } + + PCHInfo info; + info.path = *path; + info.preamble = *preamble; + info.mtime = *mtime; + + for(auto& dep: *deps) { + info.deps.push_back(dep.getAsString()->str()); + } + + for(auto& argument: *arguments) { + auto carg = database.save_string(*argument.getAsString()); + info.arguments.emplace_back(carg.data()); + } + + /// Update the PCH info. + opening_files[*file].pch = std::move(info); + } + } + + log::info("Load cache info successfully"); } -async::Task<> Server::build_pch(std::string path, std::string content) { - auto bound = compute_preamble_bound(content); +void Server::save_cache_info() { + json::Object json; + json["version"] = "0.0.1"; + json["pchs"] = json::Array(); - auto openFile = &opening_files[path]; - bool outdated = true; - if(openFile->pch) { - /// FIXME: - /// outdated = co_await isPCHOutdated(path, llvm::StringRef(content).substr(0, bound)); + for(auto& [file, open_file]: opening_files) { + if(!open_file.pch) { + continue; + } + + auto& pch = *open_file.pch; + json::Object object; + object["file"] = file; + object["path"] = pch.path; + object["preamble"] = pch.preamble; + object["mtime"] = pch.mtime; + object["deps"] = json::serialize(pch.deps); + object["arguments"] = json::serialize(pch.arguments); + + json["pchs"].getAsArray()->emplace_back(std::move(object)); } - /// If not need update, return directly. - if(!outdated) { - co_return; + auto final_path = path::join(config::cache.dir, "cache.json"); + + llvm::SmallString<128> temp_path; + if(auto error = llvm::sys::fs::createTemporaryFile("cache", "json", temp_path)) { + log::warn("Fail to create temporary file for cache info: {}", error.message()); + return; + } + + auto clean_up = llvm::make_scope_exit([&temp_path]() { llvm::sys::fs::remove(temp_path); }); + + std::error_code EC; + llvm::raw_fd_ostream os(temp_path, EC, llvm::sys::fs::OF_None); + if(EC) { + log::warn("Fail to open temporary file for writing: {}", EC.message()); + return; + } + + os << json::Value(std::move(json)); + os.flush(); + os.close(); + + if(os.has_error()) { + log::warn("Fail to write cache info to temporary file"); + return; + } + + if(auto error = llvm::sys::fs::rename(temp_path, final_path)) { + log::warn("Fail to rename temporary file to final cache file: {}", error.message()); + return; + } + + clean_up.release(); + + log::info("Save cache info successfully"); +} + +async::Task Server::build_pch(std::string file, std::string content) { + auto bound = compute_preamble_bound(content); + + auto open_file = &opening_files[file]; + auto info = database.get_command(file, true, true); + + auto check_pch_update = [&content, &bound, &info](PCHInfo& pch) { + if(content.substr(0, bound) != pch.preamble) { + return true; + } + + if(info.arguments != pch.arguments) { + return true; + } + + /// Check deps. + for(auto& dep: pch.deps) { + fs::file_status status; + auto error = fs::status(dep, status, true); + if(error || std::chrono::duration_cast( + status.getLastModificationTime().time_since_epoch()) + .count() > pch.mtime) { + return true; + } + } + + return false; + }; + + /// Check update ... + if(open_file->pch && !check_pch_update(*open_file->pch)) { + /// If not need update, return directly. + log::info("PCH is already up-to-date for {}", file); + co_return true; } /// The actual PCH build task. constexpr static auto PCHBuildTask = - [](Server& server, + [](CompilationDatabase::LookupInfo& info, + OpenFile* open_file, std::string path, std::uint32_t bound, std::string content, @@ -59,11 +189,11 @@ async::Task<> Server::build_pch(std::string path, std::string content) { CompilationParams params; params.output_file = path::join(config::cache.dir, path::filename(path) + ".pch"); - params.arguments = server.database.get_command(path, true, true).arguments; + params.arguments = std::move(info.arguments); params.diagnostics = diagnostics; params.add_remapped_file(path, content, bound); - PCHInfo info; + PCHInfo pch; std::string command; for(auto argument: params.arguments) { @@ -73,64 +203,72 @@ async::Task<> Server::build_pch(std::string path, std::string content) { log::info("Start building PCH for {}, command: [{}]", path, command); + std::string message; std::vector links; - /// PCH file is written until destructing, Add a single block - /// for it. - bool cond = co_await async::submit([&] { - auto result = compile(params, info); - if(!result) { - log::warn("Building PCH fails for {}, Because: {}", path, result.error()); - - for(auto& diagnostic: *diagnostics) { - log::warn("{}", diagnostic.message); - } + bool success = co_await async::submit([¶ms, &pch, &message, &links] -> bool { + /// PCH file is written until destructing, Add a single block + /// for it. + auto unit = compile(params, pch); + if(!unit) { + message = std::move(unit.error()); return false; } - links = feature::document_links(*result); - - /// TODO: index PCH. - + links = feature::document_links(*unit); + /// TODO: index PCH file, etc return true; }); - if(!cond) { + if(!success) { + log::warn("Building PCH fails for {}, Because: {}", path, message); + for(auto& diagnostic: *diagnostics) { + log::warn("{}", diagnostic.message); + } co_return false; } - auto& openFile = server.opening_files[path]; + log::info("Building PCH successfully for {}", path); + /// Update the built PCH info. - openFile.pch = std::move(info); - openFile.pch_includes = std::move(links); + open_file->pch = std::move(pch); + open_file->pch_includes = std::move(links); /// Resume waiters on this event. - openFile.pch_built_event.set(); - openFile.pch_built_event.clear(); + open_file->pch_built_event.set(); + open_file->pch_built_event.clear(); co_return true; }; - openFile = &opening_files[path]; + open_file = &opening_files[file]; /// If there is already an PCH build task, cancel it. - auto& task = openFile->pch_build_task; + auto& task = open_file->pch_build_task; if(!task.empty()) { - task.cancel(); - task.dispose(); + if(task.finished()) { + task.release().destroy(); + log::info("Release old pch task!"); + } else { + task.cancel(); + task.dispose(); + } + log::info("Cancel old PCH building task!"); } /// Schedule the new building task. - task = PCHBuildTask(*this, path, bound, std::move(content), openFile->diagnostics); + task = PCHBuildTask(info, open_file, file, bound, std::move(content), open_file->diagnostics); if(co_await task) { - log::info("Building PCH successfully for {}", path); + /// FIXME: At this point, task has already been finished, destroy it + /// directly. + task.release().destroy(); - /// Dispose the task so that it will destroyed when task complete. - task.dispose(); + co_return true; } - /// TODO: report diagnostics in the preamble. + /// FIXME: report diagnostics in the preamble. + co_return false; } async::Task<> Server::build_ast(std::string path, std::string content) { @@ -141,7 +279,10 @@ async::Task<> Server::build_ast(std::string path, std::string content) { auto guard = co_await file->ast_built_lock.try_lock(); /// PCH is already updated. - co_await build_pch(path, content); + bool success = co_await build_pch(path, content); + if(!success) { + co_return; + } auto pch = opening_files[path].pch; if(!pch) { @@ -173,18 +314,42 @@ async::Task<> Server::build_ast(std::string path, std::string content) { file = &opening_files[path]; /// Update built AST info. file->ast = std::make_shared(std::move(*ast)); + /// Dispose the task so that it will destroyed when task complete. file->ast_build_task.dispose(); log::info("Building AST successfully for {}", path); } -async::Task<> Server::on_did_open(proto::DidOpenTextDocumentParams params) { - auto path = mapping.to_path(params.textDocument.uri); - auto file = co_await add_document(path, std::move(params.textDocument.text)); - if(file->diagnostics) { - auto guard = co_await file->ast_built_lock.try_lock(); - file = &opening_files[path]; +async::Task Server::add_document(std::string path, std::string content) { + auto& openFile = opening_files[path]; + openFile.content = content; + + auto& task = openFile.ast_build_task; + + /// If there is already an AST build task, cancel it. + if(!task.empty()) { + if(task.finished()) { + task.release().destroy(); + log::info("Release old AST building Task!"); + } else { + task.cancel(); + task.dispose(); + } + log::info("Cancel old AST building Task!"); + } + + /// Create and schedule a new task. + task = build_ast(std::move(path), std::move(content)); + task.schedule(); + + co_return &opening_files[path]; +} + +async::Task<> Server::publish_diagnostics(std::string path, OpenFile* file) { + auto guard = co_await file->ast_built_lock.try_lock(); + file = &opening_files[path]; + if(file->ast) { auto diagnostics = feature::diagnostics(kind, mapping, *file->ast); co_await notify("textDocument/publishDiagnostics", json::Object{ @@ -192,6 +357,14 @@ async::Task<> Server::on_did_open(proto::DidOpenTextDocumentParams params) { {"diagnostics", std::move(diagnostics)}, }); } +} + +async::Task<> Server::on_did_open(proto::DidOpenTextDocumentParams params) { + auto path = mapping.to_path(params.textDocument.uri); + auto file = co_await add_document(path, std::move(params.textDocument.text)); + if(file->diagnostics) { + co_await publish_diagnostics(path, file); + } co_return; } @@ -199,14 +372,7 @@ async::Task<> Server::on_did_change(proto::DidChangeTextDocumentParams params) { auto path = mapping.to_path(params.textDocument.uri); auto file = co_await add_document(path, std::move(params.contentChanges[0].text)); if(file->diagnostics) { - auto guard = co_await file->ast_built_lock.try_lock(); - file = &opening_files[path]; - auto diagnostics = feature::diagnostics(kind, mapping, *file->ast); - co_await notify("textDocument/publishDiagnostics", - json::Object{ - {"uri", mapping.to_uri(path) }, - {"diagnostics", std::move(diagnostics)}, - }); + co_await publish_diagnostics(path, file); } co_return; } diff --git a/src/Server/Feature.cpp b/src/Server/Feature.cpp index 274ff273..6b586f55 100644 --- a/src/Server/Feature.cpp +++ b/src/Server/Feature.cpp @@ -13,7 +13,8 @@ namespace clice { async::Task Server::on_completion(proto::CompletionParams params) { auto path = mapping.to_path(params.textDocument.uri); auto opening_file = &opening_files[path]; - auto offset = to_offset(kind, opening_file->content, params.position); + auto content = opening_file->content; + auto offset = to_offset(kind, content, params.position); if(!opening_file->pch_build_task.empty()) { co_await opening_file->pch_built_event; @@ -26,28 +27,26 @@ async::Task Server::on_completion(proto::CompletionParams params) { /// Set compilation params ... . CompilationParams params; params.arguments = database.get_command(path, true).arguments; - params.add_remapped_file(path, opening_file->content); + params.add_remapped_file(path, content); params.pch = {pch->path, pch->preamble.size()}; params.completion = {path, offset}; - co_return co_await async::submit( - [kind = this->kind, content = opening_file->content, ¶ms] { - auto items = feature::code_complete(params, {}); - return proto::to_json(kind, content, items); - }); + co_return co_await async::submit([kind = this->kind, &content, ¶ms] { + auto items = feature::code_complete(params, {}); + return proto::to_json(kind, content, items); + }); } } async::Task Server::on_hover(proto::HoverParams params) { auto path = mapping.to_path(params.textDocument.uri); - auto opening_file = &opening_files[path]; + auto guard = co_await opening_file->ast_built_lock.try_lock(); auto offset = to_offset(kind, opening_file->content, params.position); - opening_file = &opening_files[path]; - auto content = opening_file->content; + auto ast = opening_file->ast; if(!ast) { co_return json::Value(nullptr); @@ -66,17 +65,17 @@ async::Task Server::on_hover(proto::HoverParams params) { async::Task Server::on_document_symbol(proto::DocumentSymbolParams params) { auto path = mapping.to_path(params.textDocument.uri); - auto opening_file = &opening_files[path]; - auto guard = co_await opening_file->ast_built_lock.try_lock(); + auto guard = co_await opening_file->ast_built_lock.try_lock(); opening_file = &opening_files[path]; - auto content = opening_file->content; + auto ast = opening_file->ast; if(!ast) { co_return json::Value(nullptr); } + llvm::StringRef content = ast->interested_content(); auto to_range = [&](LocalSourceRange range) { auto c = PositionConverter(content, kind); auto begin = c.toPosition(range.begin); @@ -116,12 +115,11 @@ async::Task Server::on_document_symbol(proto::DocumentSymbolParams async::Task Server::on_document_link(proto::DocumentLinkParams params) { auto path = mapping.to_path(params.textDocument.uri); - auto opening_file = &opening_files[path]; + opening_file = &opening_files[path]; + auto guard = co_await opening_file->ast_built_lock.try_lock(); - opening_file = &opening_files[path]; - auto content = opening_file->content; auto ast = opening_file->ast; if(!ast) { co_return json::Value(nullptr); @@ -134,6 +132,7 @@ async::Task Server::on_document_link(proto::DocumentLinkParams para auto links = feature::document_links(*ast); links.insert(links.begin(), pch_links.begin(), pch_links.end()); + llvm::StringRef content = ast->interested_content(); PositionConverter converter(content, kind); converter.to_positions(links, [](feature::DocumentLink& link) { return link.range; }); @@ -147,18 +146,17 @@ async::Task Server::on_document_link(proto::DocumentLinkParams para async::Task Server::on_folding_range(proto::FoldingRangeParams params) { auto path = mapping.to_path(params.textDocument.uri); - auto opening_file = &opening_files[path]; auto guard = co_await opening_file->ast_built_lock.try_lock(); opening_file = &opening_files[path]; - auto content = opening_file->content; auto ast = opening_file->ast; if(!ast) { co_return json::Value(nullptr); } co_return co_await async::submit([&, kind = this->kind] { + llvm::StringRef content = ast->interested_content(); auto foldings = feature::folding_ranges(*ast); PositionConverter converter(content, kind); converter.to_positions(foldings, @@ -187,12 +185,12 @@ async::Task Server::on_folding_range(proto::FoldingRangeParams para async::Task Server::on_semantic_token(proto::SemanticTokensParams params) { auto path = mapping.to_path(params.textDocument.uri); - auto opening_file = &opening_files[path]; + auto guard = co_await opening_file->ast_built_lock.try_lock(); opening_file = &opening_files[path]; - auto content = opening_file->content; + auto ast = opening_file->ast; if(!ast) { co_return json::Value(nullptr); diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index 57cbc12c..531e10be 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -26,6 +26,9 @@ async::Task Server::on_initialize(proto::InitializeParams params) { } } + /// Load cache info. + load_cache_info(); + proto::InitializeResult result; auto& [info, capabilities] = result; info.name = "clice"; @@ -68,4 +71,18 @@ async::Task Server::on_initialize(proto::InitializeParams params) { co_return json::serialize(result); } +async::Task<> Server::on_initialized(proto::InitializedParams) { + co_return; +} + +async::Task Server::on_shutdown(proto::ShutdownParams params) { + co_return json::Value(nullptr); +} + +async::Task<> Server::on_exit(proto::ExitParams params) { + save_cache_info(); + async::stop(); + co_return; +} + } // namespace clice diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 5d3c2a14..d2936a8e 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -57,6 +57,9 @@ async::Task<> Server::registerCapacity(llvm::StringRef id, Server::Server() { register_callback<&Server::on_initialize>("initialize"); + register_callback<&Server::on_initialized>("initialized"); + register_callback<&Server::on_shutdown>("shutdown"); + register_callback<&Server::on_exit>("exit"); register_callback<&Server::on_did_open>("textDocument/didOpen"); register_callback<&Server::on_did_change>("textDocument/didChange"); diff --git a/tests/fixtures/client.py b/tests/fixtures/client.py index 36e235f6..9d64ebec 100644 --- a/tests/fixtures/client.py +++ b/tests/fixtures/client.py @@ -1,12 +1,19 @@ +import asyncio from pathlib import Path from .transport import LSPTransport +class OpeningFile: + def __init__(self, content: str): + self.version = 0 + self.content = content + + class LSPClient(LSPTransport): def __init__(self, commands, mode="stdio", host="127.0.0.1", port=2087): super().__init__(commands, mode, host, port) self.workspace = "" - self.opening_files: dict[Path, str] = {} + self.opening_files: dict[Path, OpeningFile] = {} async def initialize(self, workspace: str): self.workspace = workspace @@ -21,9 +28,13 @@ class LSPClient(LSPTransport): await self.send_notification("exit") await self.stop() - def get_abs_path(self, relative_path): + def get_abs_path(self, relative_path: str): return Path(self.workspace, relative_path) + def get_file(self, relative_path: str): + path = self.get_abs_path(relative_path) + return self.opening_files[path] + async def did_open(self, relative_path: str): path = self.get_abs_path(relative_path) @@ -32,9 +43,9 @@ class LSPClient(LSPTransport): content = file.read() if path in self.opening_files: - raise f"Cannot open same file multiple times: {path}" + raise RuntimeError(f"Cannot open same file multiple times: {path}") - self.opening_files[path] = content + self.opening_files[path] = OpeningFile(content) params = { "textDocument": { @@ -46,3 +57,24 @@ class LSPClient(LSPTransport): } await self.send_notification("textDocument/didOpen", params) + + async def did_change(self, relative_path: str, content: str): + path = self.get_abs_path(relative_path) + + if path not in self.opening_files: + raise RuntimeError(f"Cannot change closed file: {path}") + + file = self.opening_files[path] + file.version += 1 + file.content = content + params = { + "textDocument": { + "uri": path.as_uri(), + "version": file.version + }, + "contentChanges": [ + {"text": content, } + ] + } + + await self.send_notification("textDocument/didChange", params) diff --git a/tests/fixtures/transport.py b/tests/fixtures/transport.py index 39cf5b91..553e44b1 100644 --- a/tests/fixtures/transport.py +++ b/tests/fixtures/transport.py @@ -63,10 +63,10 @@ class LSPTransport: async def stop(self): if self.mode == "stdio" and self.process: - logging.info("Terminating LSP server process.") - self.process.terminate() - await self.process.wait() - logging.info("LSP server process terminated.") + return_code = await self.process.wait() + if return_code != 0: + raise RuntimeError("Server exit with error!") + elif self.mode == "socket" and self.writer: logging.info("Closing socket connection to LSP server.") self.writer.close() diff --git a/tests/integration/test_file_operation.py b/tests/integration/test_file_operation.py index dddfb385..1f25d85e 100644 --- a/tests/integration/test_file_operation.py +++ b/tests/integration/test_file_operation.py @@ -1,6 +1,7 @@ import sys import pytest import asyncio +import logging from ..fixtures.client import LSPClient @@ -13,5 +14,29 @@ async def test_did_open(executable, test_data_dir, resource_dir): await client.initialize(test_data_dir / "hello_world") await client.did_open("main.cpp") + await asyncio.sleep(5) await client.exit() + + +@pytest.mark.asyncio +async def test_did_change(executable, test_data_dir, resource_dir): + client = LSPClient([ + executable, "--mode=pipe", f"--resource-dir={resource_dir}" + ]) + await client.start() + + await client.initialize(test_data_dir / "hello_world") + await client.did_open("main.cpp") + + # Test frequently change content will not make server crash. + content = client.get_file("main.cpp").content + + for _ in range(0, 20): + content += "\n" + await asyncio.sleep(0.2) + await client.did_change("main.cpp", content) + + await asyncio.sleep(5) + logging.info("Send exit") + await client.exit() diff --git a/tests/integration/test_lifecycle.py b/tests/integration/test_lifecycle.py index 8e24f152..e017c7df 100644 --- a/tests/integration/test_lifecycle.py +++ b/tests/integration/test_lifecycle.py @@ -13,4 +13,5 @@ async def test_initialize(executable, test_data_dir, resource_dir): result = await client.initialize(test_data_dir) assert "serverInfo" in result assert result["serverInfo"]["name"] == "clice" + await client.exit() diff --git a/tests/unit/Async/Lock.cpp b/tests/unit/Async/Lock.cpp index 780f2260..bca9c9c9 100644 --- a/tests/unit/Async/Lock.cpp +++ b/tests/unit/Async/Lock.cpp @@ -42,6 +42,39 @@ TEST(Async, Lock) { async::run(task1(), task2(), task3()); } +TEST(Async, LockCancel) { + async::Lock lock; + + int x = 0; + int y = 0; + + auto task = [&]() -> async::Task<> { + x += 1; + auto guard = co_await lock.try_lock(); + co_await async::sleep(100); + y += 1; + }; + + auto task1 = task(); + auto task2 = task(); + auto task3 = task(); + + auto cancel = [&task2]() -> async::Task<> { + co_await async::sleep(10); + task2.cancel(); + task2.dispose(); + }; + + task1.schedule(); + task2.schedule(); + task3.schedule(); + + async::run(cancel()); + + EXPECT_EQ(x, 3); + EXPECT_EQ(y, 2); +} + } // namespace } // namespace clice::testing diff --git a/tests/unit/Feature/DocumentSymbol.cpp b/tests/unit/Feature/DocumentSymbol.cpp index 87ca11f4..4a705d75 100644 --- a/tests/unit/Feature/DocumentSymbol.cpp +++ b/tests/unit/Feature/DocumentSymbol.cpp @@ -1,5 +1,6 @@ #include "Test/Tester.h" #include "Feature/DocumentSymbol.h" +#include "AST/Utility.h" namespace clice::testing { @@ -9,7 +10,7 @@ struct DocumentSymbol : TestFixture { protected: auto run(llvm::StringRef code) { add_main("main.cpp", code); - Tester::compile(); + Tester::compile_with_pch(); EXPECT_TRUE(unit.has_value()); return feature::document_symbols(*unit); @@ -71,8 +72,6 @@ struct _3 { auto symbols = run(main); EXPECT_EQ(total_size(symbols), 5); - // tester->info->tu()->dump(); - // println("{}", pretty_dump(symbols)); } TEST_F(DocumentSymbol, Field) { diff --git a/xmake.lua b/xmake.lua index 5dec65d5..8e7dcc9c 100644 --- a/xmake.lua +++ b/xmake.lua @@ -128,7 +128,7 @@ target("integration_tests") add_deps("clice") add_packages("python", "llvm") - add_tests("default", {run_timeout = 1000 * 10}) + add_tests("default") on_test(function (target, opt) import("private.action.run.runenvs") @@ -144,7 +144,7 @@ target("integration_tests") "--executable=" .. target:dep("clice"):targetfile(), "--resource-dir=" .. path.join(target:pkg("llvm"):installdir(), "lib/clang/20"), } - local opt = {envs = envs, timeout = opt.run_timeout, curdir = os.projectdir()} + local opt = {envs = envs, curdir = os.projectdir()} if has_config("ci") and is_plat("macosx") then os.vrun("pip install pytest pytest-asyncio pytest-xdist")