From bcb4c3895a29a948e3d3d99ebb3b79db1a53a261 Mon Sep 17 00:00:00 2001 From: Shiyu Date: Thu, 20 Feb 2025 20:56:51 +0800 Subject: [PATCH] Fix API of `InlayHint`, `FoldingRange`. (#87) Co-authored-by: ur4t <46435411+ur4t@users.noreply.github.com> --- include/AST/FilterASTVisitor.h | 92 ++---- include/AST/Selection.h | 4 +- include/Compiler/AST.h | 20 +- include/Feature/FoldingRange.h | 37 +-- include/Feature/InlayHint.h | 12 +- include/Test/CTest.h | 8 + include/Test/Test.h | 14 +- src/AST/FilterASTVisitor.cpp | 57 ++++ src/AST/Selection.cpp | 6 +- src/Basic/SourceCode.cpp | 4 +- src/Feature/FoldingRange.cpp | 454 ++++++++++++----------------- src/Feature/Hover.cpp | 8 +- src/Feature/InlayHint.cpp | 273 +++++++---------- unittests/Feature/FoldingRange.cpp | 294 +++++++++++-------- unittests/Feature/Hover.cpp | 4 +- unittests/Feature/InlayHint.cpp | 29 +- 16 files changed, 632 insertions(+), 684 deletions(-) create mode 100644 src/AST/FilterASTVisitor.cpp diff --git a/include/AST/FilterASTVisitor.h b/include/AST/FilterASTVisitor.h index c4a225f4..41b7e9c0 100644 --- a/include/AST/FilterASTVisitor.h +++ b/include/AST/FilterASTVisitor.h @@ -2,78 +2,44 @@ #include "Basic/SourceCode.h" #include "Compiler/AST.h" + #include "clang/AST/RecursiveASTVisitor.h" namespace clice { +struct RAVFileter { + + RAVFileter(ASTInfo& AST, bool interestedOnly, std::optional limit) : + AST(AST), limit(limit), interestedOnly(interestedOnly) {} + + bool filterable(clang::SourceRange range) const; + + ASTInfo& AST; + 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 { -private: +class FilteredASTVisitor : public clang::RecursiveASTVisitor, public RAVFileter { +protected: using Base = clang::RecursiveASTVisitor; - bool filterable(clang::SourceRange range) { - auto [begin, end] = range; - - /// FIXME: Most of implicit decls don't have valid source range. Is it possible - /// that we want to visit them sometimes? - if(begin.isInvalid() || end.isInvalid()) { - return true; - } - - if(begin == end) { - /// We are only interested in expansion location. - auto [fid, offset] = AST.getDecomposedLoc(AST.getExpansionLoc(begin)); - - /// For builtin files, we don't want to visit them. - if(AST.isBuiltinFile(fid)) { - return true; - } - - /// Filter out if the location is not in the interested file. - if(interestedOnly) { - auto interested = AST.getInterestedFile(); - if(fid != interested) { - return true; - } - - if(targetRange && !targetRange->contains(offset)) { - return true; - } - } - } else { - auto [beginFID, beginOffset] = AST.getDecomposedLoc(AST.getExpansionLoc(begin)); - auto [endFID, endOffset] = AST.getDecomposedLoc(AST.getExpansionLoc(end)); - - if(AST.isBuiltinFile(beginFID) || AST.isBuiltinFile(endFID)) { - return true; - } - - if(interestedOnly) { - auto interested = AST.getInterestedFile(); - if(beginFID != interested && endFID != interested) { - return true; - } - - if(targetRange && !targetRange->intersects({beginOffset, endOffset})) { - return true; - } - } - } - - return false; - } - - Derived& getDerived() { - return static_cast(*this); - } + FilteredASTVisitor(ASTInfo& AST, + bool interestedOnly, + std::optional targetRange) : + RAVFileter(AST, interestedOnly, targetRange) {} public: #define CHECK_DERIVED_IMPL(func) \ static_assert(std::same_as, \ "Derived class should not implement this method"); + Derived& getDerived() { + return static_cast(*this); + } + bool TraverseDecl(clang::Decl* decl) { CHECK_DERIVED_IMPL(TraverseDecl); @@ -136,7 +102,7 @@ public: return Base::TraverseAttributedStmt(stmt); } - /// We don't want to node withou location information. + /// We don't want to node without location information. constexpr bool TraverseType [[gnu::always_inline]] (clang::QualType) { CHECK_DERIVED_IMPL(TraverseType); return true; @@ -215,16 +181,6 @@ public: } #undef CHECK_DERIVED_IMPL - -protected: - FilteredASTVisitor(ASTInfo& AST, - bool interestedOnly, - std::optional targetRange) : - AST(AST), interestedOnly(interestedOnly), targetRange(targetRange) {} - - ASTInfo& AST; - bool interestedOnly = true; - std::optional targetRange; }; } // namespace clice diff --git a/include/AST/Selection.h b/include/AST/Selection.h index 8357dcb2..3b96f469 100644 --- a/include/AST/Selection.h +++ b/include/AST/Selection.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include "clang/AST/ASTTypeTraits.h" +#include "clang/Tooling/Syntax/Tokens.h" #include diff --git a/include/Compiler/AST.h b/include/Compiler/AST.h index 7d6565eb..c9119702 100644 --- a/include/Compiler/AST.h +++ b/include/Compiler/AST.h @@ -4,6 +4,7 @@ #include "Basic/SourceCode.h" #include "AST/Resolver.h" #include "Basic/SourceCode.h" + #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Tooling/Syntax/Tokens.h" @@ -68,18 +69,14 @@ public: return instance->getASTContext().getTranslationUnitDecl(); } - clang::FileID mainFileID() { - return srcMgr().getMainFileID(); - } - /// The interested file ID. For file without header context, it is the main file ID. /// For file with header context, it is the file ID of header file. - clang::FileID getInterestedFile() { + clang::FileID getInterestedFile() const { return interested; } - llvm::StringRef getMainFileContent() { - return getFileContent(mainFileID()); + llvm::StringRef getInterestedFileContent() const { + return getFileContent(interested); } /// All files involved in building the AST. @@ -87,15 +84,15 @@ public: std::vector deps(); - clang::SourceLocation getSpellingLoc(clang::SourceLocation loc) { + clang::SourceLocation getSpellingLoc(clang::SourceLocation loc) const { return SM.getSpellingLoc(loc); } - clang::SourceLocation getExpansionLoc(clang::SourceLocation loc) { + clang::SourceLocation getExpansionLoc(clang::SourceLocation loc) const { return SM.getExpansionLoc(loc); } - auto getDecomposedLoc(clang::SourceLocation loc) { + auto getDecomposedLoc(clang::SourceLocation loc) const { return SM.getDecomposedLoc(loc); } @@ -112,7 +109,7 @@ public: llvm::StringRef getFilePath(clang::FileID fid); /// Get the content of a file ID. - llvm::StringRef getFileContent(clang::FileID fid) { + llvm::StringRef getFileContent(clang::FileID fid) const { return SM.getBufferData(fid); } @@ -153,6 +150,7 @@ private: /// Cache for file path. It is used to avoid multiple file path lookup. llvm::DenseMap pathCache; + llvm::BumpPtrAllocator pathStorage; }; diff --git a/include/Feature/FoldingRange.h b/include/Feature/FoldingRange.h index 7a6c4cf0..2da0c789 100644 --- a/include/Feature/FoldingRange.h +++ b/include/Feature/FoldingRange.h @@ -1,30 +1,16 @@ #include "Basic/Document.h" #include "Basic/SourceCode.h" -#include "Basic/SourceConverter.h" #include "Index/Shared.h" #include "Support/JSON.h" namespace clice { -class ASTInfo; - -struct FoldingRangeParams {}; - namespace proto { -// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#foldingRangeClientCapabilities - /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#foldingRangeClientCapabilities struct FoldingRangeClientCapabilities {}; -/// TODO: /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#foldingRangeParams -/// ``` -/// export interface FoldingRangeParams extends WorkDoneProgressParams, -/// PartialResultParams { -/// ... -/// ``` - struct FoldingRangeParams { /// The text document. TextDocumentIdentifier textDocument; @@ -70,28 +56,35 @@ using FoldingRangeResult = std::vector; } // namespace proto -namespace feature::folding_range { +class ASTInfo; +class SourceConverter; + +namespace feature::foldingrange { json::Value capability(json::Value clientCapabilities); +/// We don't record the coalesced text for a range, because it's rarely useful. struct FoldingRange { LocalSourceRange range; proto::FoldingRangeKind kind; - /// We don't record the coallaesced text for a range, because it's rarely useful. }; using Result = std::vector; /// Generate folding range for all files. -index::Shared foldingRange(ASTInfo& info, const SourceConverter& converter); +index::Shared foldingRange(ASTInfo& AST); /// Return folding range in main file. -Result foldingRange(FoldingRangeParams& params, ASTInfo& info, const SourceConverter& converter); +Result foldingRange(proto::FoldingRangeParams param, ASTInfo& AST); -/// Convert folding range to LSP format. -proto::FoldingRangeResult toLspResult(llvm::ArrayRef ranges, llvm::StringRef content, - const SourceConverter& SC); +proto::FoldingRange toLspType(const FoldingRange& folding, + const SourceConverter& SC, + llvm::StringRef content); -} // namespace feature::folding_range +proto::FoldingRangeResult toLspResult(llvm::ArrayRef foldings, + const SourceConverter& SC, + llvm::StringRef content); + +} // namespace feature::foldingrange } // namespace clice diff --git a/include/Feature/InlayHint.h b/include/Feature/InlayHint.h index cf7cd962..f4589e99 100644 --- a/include/Feature/InlayHint.h +++ b/include/Feature/InlayHint.h @@ -244,14 +244,18 @@ using Result = std::vector; /// Compute inlay hints for MainfileID in given range and config. Result inlayHints(proto::InlayHintParams param, ASTInfo& info, - const SourceConverter& converter, const config::InlayHintOption& option); /// Same with `inlayHints` but including all fileID, and all options in `config::InlayHintOption` /// will be enabled to support index. -index::Shared inlayHints(proto::DocumentUri uri, - ASTInfo& info, - const SourceConverter& converter); +index::Shared inlayHints(ASTInfo& info); + +/// Convert `InlayHint` to `proto::InlayHint`. +proto::InlayHint toLspType(const InlayHint& hint, + size_t maxHintLength, + llvm::StringRef docuri, + llvm::StringRef content, + const SourceConverter& SC); /// Convert `Result` to `proto::InlayHintResult`. If an option is provided, use the option to /// filter result. By default, all hints will be converted. diff --git a/include/Test/CTest.h b/include/Test/CTest.h index da33c9c1..3dbba896 100644 --- a/include/Test/CTest.h +++ b/include/Test/CTest.h @@ -14,6 +14,8 @@ struct Tester { llvm::StringMap locations; std::vector sources; + proto::Position eof; + public: Tester() = default; @@ -31,6 +33,10 @@ public: params.remappedFiles.emplace_back(name, content); } + proto::Position endOfFile() const { + return eof; + } + llvm::StringRef annoate(llvm::StringRef content) { auto& source = sources.emplace_back(); source.reserve(content.size()); @@ -75,6 +81,8 @@ public: source.push_back(c); } + eof.line = line; + eof.character = column; return source; } diff --git a/include/Test/Test.h b/include/Test/Test.h index ab7b4d7f..4eeb4ae9 100644 --- a/include/Test/Test.h +++ b/include/Test/Test.h @@ -29,21 +29,21 @@ inline void EXPECT_EQ(const LHS& lhs, const RHS& rhs, std::source_location current = std::source_location::current()) { if(!refl::equal(lhs, rhs)) { - std::string expect; + std::string left; if constexpr(requires { json::Serde::serialize; }) { - llvm::raw_string_ostream(expect) << json::serialize(lhs); + llvm::raw_string_ostream(left) << json::serialize(lhs); } else { - expect = "cannot dump value"; + left = "cannot dump value"; } - std::string actual; + std::string right; if constexpr(requires { json::Serde::serialize; }) { - llvm::raw_string_ostream(actual) << json::serialize(rhs); + llvm::raw_string_ostream(right) << json::serialize(rhs); } else { - actual = "cannot dump value"; + right = "cannot dump value"; } - EXPECT_FAILURE(std::format("expect: {}, actual: {}\n", expect, actual), current); + EXPECT_FAILURE(std::format("left : {}\nright: {}\n", left, right), current); } } diff --git a/src/AST/FilterASTVisitor.cpp b/src/AST/FilterASTVisitor.cpp new file mode 100644 index 00000000..c6cb0b0d --- /dev/null +++ b/src/AST/FilterASTVisitor.cpp @@ -0,0 +1,57 @@ +#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] = AST.getDecomposedLoc(AST.getExpansionLoc(begin)); + + /// For builtin files, we don't want to visit them. + if(AST.isBuiltinFile(fid)) { + return true; + } + + /// Filter out if the location is not in the interested file. + if(interestedOnly) { + auto interested = AST.getInterestedFile(); + if(fid != interested) { + return true; + } + + if(limit && !limit->contains(offset)) { + return true; + } + } + } else { + auto [beginFID, beginOffset] = AST.getDecomposedLoc(AST.getExpansionLoc(begin)); + auto [endFID, endOffset] = AST.getDecomposedLoc(AST.getExpansionLoc(end)); + + if(AST.isBuiltinFile(beginFID) || AST.isBuiltinFile(endFID)) { + return true; + } + + if(interestedOnly) { + auto interested = AST.getInterestedFile(); + if(beginFID != interested && endFID != interested) { + return true; + } + + if(limit && !limit->intersects({beginOffset, endOffset})) { + return true; + } + } + } + + return false; +} + +} // namespace clice diff --git a/src/AST/Selection.cpp b/src/AST/Selection.cpp index 88ef480c..0e8f6ede 100644 --- a/src/AST/Selection.cpp +++ b/src/AST/Selection.cpp @@ -1,7 +1,7 @@ -#include -#include +#include "AST/Selection.h" +#include "Compiler/AST.h" -#include +#include "clang/AST/RecursiveASTVisitor.h" #include diff --git a/src/Basic/SourceCode.cpp b/src/Basic/SourceCode.cpp index 3d521172..67deef91 100644 --- a/src/Basic/SourceCode.cpp +++ b/src/Basic/SourceCode.cpp @@ -1,7 +1,7 @@ #include "Basic/SourceCode.h" -#include -#include +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" namespace clice { diff --git a/src/Feature/FoldingRange.cpp b/src/Feature/FoldingRange.cpp index f05de9c0..cabac0d1 100644 --- a/src/Feature/FoldingRange.cpp +++ b/src/Feature/FoldingRange.cpp @@ -1,7 +1,7 @@ -#include "Feature/FoldingRange.h" +#include "AST/FilterASTVisitor.h" +#include "Basic/SourceConverter.h" #include "Compiler/Compilation.h" -#include "Index/Shared.h" -#include "clang/AST/RecursiveASTVisitor.h" +#include "Feature/FoldingRange.h" /// Clangd's FoldingRange Implementation: /// https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/clangd/SemanticSelection.cpp @@ -10,292 +10,204 @@ namespace clice { namespace { -struct FoldingRangeCollector : public clang::RecursiveASTVisitor { +struct FoldingRangeCollector : public FilteredASTVisitor { - using Base = clang::RecursiveASTVisitor; - using Storage = index::Shared; + using Base = FilteredASTVisitor; - /// The converter used to adapt LSP protocol. - const SourceConverter& cvtr; + using Folding = feature::foldingrange::FoldingRange; - /// The source manager of given AST. - const clang::SourceManager& src; + /// Cache extra line number as the inner storage to speedup the collection. + struct RichFolding : Folding { + uint32_t startLine; + uint32_t endLine; + }; - /// Token buffer of given AST. - const clang::syntax::TokenBuffer& tkbuf; + using Storage = index::Shared>; - /// The result of folding ranges. Storage result; - /// True if only main file is involved. - const bool onlyMain; + constexpr static auto LastColOfLine = std::numeric_limits::max(); - const clang::FileID mainFileID; - - /// Do not produce folding ranges if either range ends is not within the main file. - bool needFilter(clang::SourceLocation loc) { - return loc.isInvalid() || (onlyMain && !src.isInMainFile(loc)); - } - - /// Get last column of previous line of a location. - clang::SourceLocation prevLineLastColOf(clang::SourceLocation loc) { - return src.translateLineCol(src.getMainFileID(), - src.getPresumedLineNumber(loc) - 1, - std::numeric_limits::max()); - } + FoldingRangeCollector(ASTInfo& AST, + bool interestedOnly, + std::optional targetRange) : + FilteredASTVisitor(AST, interestedOnly, targetRange), result() {} /// Collect source range as a folding range. - void collect(const clang::SourceRange sr, + void collect(clang::SourceRange range, + std::pair offsetFix = {0, 0}, proto::FoldingRangeKind kind = proto::FoldingRangeKind::Region) { - auto startLine = src.getPresumedLineNumber(sr.getBegin()) - 1; - auto endLine = src.getPresumedLineNumber(sr.getEnd()) - 1; + + const auto& SM = AST.srcMgr(); + unsigned startLine = SM.getPresumedLineNumber(range.getBegin()) - 1; + unsigned endLine = SM.getPresumedLineNumber(range.getEnd()) - 1; // Skip ranges on a single line. if(startLine >= endLine) return; - auto& state = onlyMain ? result[mainFileID] : result[src.getFileID(sr.getBegin())]; - state.push_back({ - .range = cvtr.toLocalRange(sr, src), - .kind = kind, - }); - } + auto fileID = interestedOnly ? AST.getInterestedFile() : SM.getFileID(range.getBegin()); + auto& state = result[fileID]; - bool TraverseNamespaceDecl(clang::NamespaceDecl* decl) { - if(!decl || needFilter(decl->getLocation())) - return true; + if(auto beg = range.getBegin(); beg.isMacroID()) { + auto cursor = + SM.translateLineCol(fileID, SM.getExpansionLineNumber(beg), LastColOfLine); + range.setBegin(cursor); + offsetFix.first = 0; + } + if(auto end = range.getEnd(); end.isMacroID()) { + range.setEnd(SM.translateLineCol(fileID, + SM.getExpansionLineNumber(end), + SM.getExpansionColumnNumber(end))); + offsetFix.second = 0; + } - return Base::TraverseNamespaceDecl(decl); + assert(range.isValid()); + + auto [leftLocal, rightLocal] = AST.toLocalRange(range).second; + LocalSourceRange fixed{leftLocal + offsetFix.first, rightLocal + offsetFix.second}; + if(state.empty() || state.back().startLine != startLine) { + state.push_back({fixed, kind, startLine, endLine}); + } } bool VisitNamespaceDecl(const clang::NamespaceDecl* decl) { - auto tks = tkbuf.expandedTokens(decl->getSourceRange()); + auto tokens = AST.tokBuf().expandedTokens(decl->getSourceRange()); // Find first '{' in namespace declaration. - auto shrink = tks.drop_until([](const clang::syntax::Token& tk) -> bool { + auto shrink = tokens.drop_until([](const clang::syntax::Token& tk) -> bool { return tk.kind() == clang::tok::l_brace; }); - collect({shrink.front().endLocation(), prevLineLastColOf(shrink.back().location())}); + collect({shrink.front().location(), decl->getRBraceLoc()}, {1, -1}); return true; } - /// Collect lambda capture list "[ ... ]". - void collectLambdaCapture(const clang::CXXRecordDecl* decl) { - auto tks = tkbuf.expandedTokens(decl->getSourceRange()); - - auto shrink = tks.drop_until([](const clang::syntax::Token& tk) -> bool { - return tk.kind() == clang::tok::TokenKind::l_square; - }); - - auto ls = shrink.front(); - { - shrink = shrink.drop_front(); - shrink = shrink.drop_until([depth = 0](const clang::syntax::Token& tk) mutable { - switch(tk.kind()) { - case clang::tok::TokenKind::r_square: { - if(depth-- == 0) - return true; - break; - } - - case clang::tok::TokenKind::l_square: depth++; break; - - default: break; - } - return false; - }); - } - auto rs = shrink.front(); - - collect({ls.location(), rs.location()}); - } - /// Collect public/protected/private blocks for a non-lambda struct/class. - void collectAccCtrlBlocks(const clang::CXXRecordDecl* decl) { - constexpr static auto is_accctrl = [](const clang::syntax::Token& tk) -> bool { - switch(tk.kind()) { - case clang::tok::kw_public: - case clang::tok::kw_protected: - case clang::tok::kw_private: return true; - default: return false; + void collectAccessSpecDecls(const clang::RecordDecl* RD) { + const clang::AccessSpecDecl* lastAccess = nullptr; + + for(auto* decl: RD->decls()) { + if(auto* AS = llvm::dyn_cast(decl)) { + if(lastAccess) { + auto spec = AS->getAccessUnsafe(); + int offsetToSpecStart = spec == clang::AS_private ? 7 + : spec == clang::AS_public ? 6 + : 9; + collect({lastAccess->getColonLoc(), AS->getAccessSpecifierLoc()}, + {1, -offsetToSpecStart}); + } + lastAccess = AS; } - }; - - auto tks = tkbuf.expandedTokens(decl->getSourceRange()); - - auto tryCollectRegion = [this](clang::SourceLocation ll, clang::SourceLocation lr) { - // Skip continous access control keywords. - if(src.getPresumedLineNumber(ll) == src.getPresumedLineNumber(lr)) - return; - collect({ll, prevLineLastColOf(lr)}); - }; - - // If there is no access control blocks, return. - tks = tks.drop_until(is_accctrl); - if(tks.empty()) - return; - - auto [_, rb] = decl->getBraceRange(); - tks = tks.drop_front(); // Move to ':' after private/public/protected - clang::SourceLocation last = tks.front().endLocation(); - while(true) { - tks = tks.drop_until(is_accctrl); - if(tks.empty()) { - tryCollectRegion(last, rb); - break; - } - - tryCollectRegion(last, tks.front().location()); - tks = tks.drop_front(); // Move to ':' after private/public/protected - last = tks.front().endLocation(); } - } - bool TraverseDecl(clang ::Decl* decl) { - if(!decl || needFilter(decl->getLocation())) - return true; - - return Base::TraverseDecl(decl); + // The last access specifier block. + if(lastAccess) { + collect({lastAccess->getColonLoc(), RD->getBraceRange().getEnd()}, {1, -1}); + } } bool VisitTagDecl(const clang::TagDecl* decl) { - auto [lb, rb] = decl->getBraceRange(); + // Collect definition of class/struct/enum. + auto [leftBrace, rightBrace] = decl->getBraceRange(); auto name = decl->getName(); - collect({lb.getLocWithOffset(1), prevLineLastColOf(rb)}); + collect({leftBrace, rightBrace}, {1, -1}); - if(auto cxd = llvm::dyn_cast(decl); - cxd != nullptr && cxd->hasDefinition()) { - collectAccCtrlBlocks(cxd); + if(auto RD = llvm::dyn_cast(decl)) { + collectAccessSpecDecls(RD); } return true; } /// Collect function parameter list between '(' and ')'. - void collectParameterList(clang::SourceLocation left, clang::SourceLocation right) { - auto tks = tkbuf.expandedTokens({left, right}); - - tks = tks.drop_until([](const auto& tk) { return tk.kind() == clang::tok::l_paren; }); - if(tks.empty()) - return; - - auto iter = std::find_if(tks.rbegin(), tks.rend(), [](const auto& tk) { - return tk.kind() == clang::tok::r_paren; + void collectParameterList(clang::SourceLocation leftSide, clang::SourceLocation rightSide) { + auto tokens = AST.tokBuf().expandedTokens({leftSide, rightSide}); + auto leftParen = tokens.drop_until([](const auto& tk) { // + return tk.kind() == clang::tok::l_paren; }); - if(iter == tks.rend()) + if(leftParen.empty()) return; - auto lr = tks.front().endLocation(); - auto rr = iter->location(); - collect({lr, prevLineLastColOf(rr)}); + auto rightParenIter = + std::find_if(leftParen.rbegin(), leftParen.rend(), [](const auto& tk) { + return tk.kind() == clang::tok::r_paren; + }); + + if(rightParenIter == leftParen.rend()) + return; + + collect({leftParen.front().location(), rightParenIter->location()}, {1, -1}); } - bool TraverseFunctionDecl(clang ::FunctionDecl* decl) { - if(!decl || needFilter(decl->getLocation())) - return true; - - return Base::TraverseFunctionDecl(decl); + void collectCompoundStmt(const clang::Stmt* stmt) { + if(auto* CS = llvm::dyn_cast(stmt)) { + collect({CS->getLBracLoc(), CS->getRBracLoc()}, {1, -1}); + for(auto child: stmt->children()) { + collectCompoundStmt(child); + } + } } bool VisitFunctionDecl(const clang::FunctionDecl* decl) { - // Left parent. - auto pl = decl->isTemplateDecl() - ? decl->getTemplateParameterList(1)->getSourceRange().getEnd() - : decl->getBeginLoc(); + auto leftParen = decl->getBeginLoc(); + auto rightParen = decl->hasBody() // + ? decl->getBody()->getBeginLoc() + : decl->getSourceRange().getEnd(); + collectParameterList(leftParen, rightParen); - // Right parent. - auto pr = - decl->hasBody() ? decl->getBody()->getBeginLoc() : decl->getSourceRange().getEnd(); - - collectParameterList(pl, pr); - - // Function body was collected by `VisitCompoundStmt`. + if(decl->hasBody()) { + auto [leftBrace, rightBrace] = decl->getBody()->getSourceRange(); + collect({leftBrace, rightBrace}, {1, -1}); + collectCompoundStmt(decl->getBody()); + } return true; } - bool TraverseLambdaExpr(clang ::LambdaExpr* expr) { - if(!expr || needFilter(expr->getBeginLoc())) - return true; + bool VisitLambdaExpr(const clang::LambdaExpr* lambda) { + auto introduceRange = lambda->getIntroducerRange(); + assert(introduceRange.isValid() && "Invalid introduce range."); + collect(introduceRange, {1, -1}); - return Base::TraverseLambdaExpr(expr); - } - - bool VisitLambdaExpr(const clang::LambdaExpr* expr) { - auto [il, ir] = expr->getIntroducerRange(); - collect({il.getLocWithOffset(1), prevLineLastColOf(ir)}); - - if(expr->hasExplicitParameters()) - collectParameterList(ir, expr->getCompoundStmtBody()->getLBracLoc()); + if(lambda->hasExplicitParameters()) { + collectParameterList(introduceRange.getEnd(), + lambda->getCompoundStmtBody()->getBeginLoc()); + } + collectCompoundStmt(lambda->getBody()); return true; } - bool TraverseCompoundStmt(clang ::CompoundStmt* stmt) { - if(!stmt || needFilter(stmt->getBeginLoc())) + bool VisitCallExpr(const clang::CallExpr* call) { + auto tokens = AST.tokBuf().expandedTokens(call->getSourceRange()); + if(tokens.back().kind() != clang::tok::r_paren) return true; - return Base::TraverseCompoundStmt(stmt); - } - - bool VisitCompoundStmt(const clang::CompoundStmt* stmt) { - collect({stmt->getLBracLoc().getLocWithOffset(1), prevLineLastColOf(stmt->getRBracLoc())}); - return true; - } - - bool TraverseCallExpr(clang ::CallExpr* expr) { - if(!expr || needFilter(expr->getBeginLoc())) - return true; - - return Base::TraverseCallExpr(expr); - } - - bool VisitCallExpr(const clang::CallExpr* expr) { - auto tks = tkbuf.expandedTokens(expr->getSourceRange()); - if(tks.back().kind() != clang::tok::r_paren) - return true; - - auto rp = tks.back().location(); + auto rightParen = tokens.back().location(); size_t depth = 0; - while(!tks.empty()) { - auto kind = tks.back().kind(); + 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) { - collect({tks.back().endLocation(), prevLineLastColOf(rp)}); + collect({tokens.back().location(), rightParen}, {1, -1}); break; } - tks = tks.drop_back(); + tokens = tokens.drop_back(); } return true; } - bool TraverseCXXConstructExpr(clang::CXXConstructExpr* expr) { - if(!expr || needFilter(expr->getLocation())) - return true; - - return Base::TraverseCXXConstructExpr(expr); - } - bool VisitCXXConstructExpr(const clang::CXXConstructExpr* stmt) { if(auto range = stmt->getParenOrBraceRange(); range.isValid()) - collect({range.getBegin().getLocWithOffset(1), prevLineLastColOf(range.getEnd())}); + collect({range.getBegin().getLocWithOffset(1), range.getEnd()}); + return true; } - bool TraverseInitListExpr(clang::InitListExpr* expr) { - if(!expr || needFilter(expr->getBeginLoc())) - return true; - - return Base::TraverseInitListExpr(expr); - } - bool VisitInitListExpr(const clang::InitListExpr* expr) { - collect({ - expr->getLBraceLoc().getLocWithOffset(1), - prevLineLastColOf(expr->getRBraceLoc()), - }); + collect({expr->getLBraceLoc(), expr->getRBraceLoc()}, {1, -1}); return true; } @@ -303,7 +215,7 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorloc, prevLineLastColOf(cond.loc)}); + collect({last->conditionRange.getEnd(), cond.loc}, {0, -1}); } stack.push_back(&cond); @@ -345,7 +257,15 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorloc, prevLineLastColOf(cond.loc)}); + + // For a directive without condition range e.g #else + // its condition range is invalid. + if(last->conditionRange.isValid()) { + collect({last->conditionRange.getBegin(), cond.loc}, {0, -1}); + } else { + collect({last->loc, cond.loc}, + {refl::enum_name(cond.kind).length(), -1}); + } } break; } @@ -357,10 +277,11 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitor& pragmas) { - auto lastLocOfLine = [this](clang::SourceLocation loc) { - return src.translateLineCol(src.getMainFileID(), - src.getPresumedLineNumber(loc), - std::numeric_limits::max()); + const auto& SM = AST.srcMgr(); + + auto lastLocOfLine = [this, &SM](clang::SourceLocation loc) { + auto line = SM.getPresumedLineNumber(loc); + return SM.translateLineCol(SM.getMainFileID(), line, LastColOfLine); }; llvm::SmallVector stack = {}; @@ -370,7 +291,7 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorloc), prevLineLastColOf(pragma.loc)}); + collect({lastLocOfLine(last->loc), pragma.loc}, {0, -1}); } break; default: break; @@ -379,18 +300,40 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorloc), eof}); } } } + + static index::Shared> extract(const Storage& storage) { + llvm::DenseMap> extracted; + for(auto& [fileID, richs]: storage) { + std::vector res; + res.reserve(richs.size()); + for(auto& rich: richs) { + res.push_back(rich); + } + extracted[fileID] = std::move(res); + } + return extracted; + } + + static index::Shared> + collect(ASTInfo& AST, bool interestedOnly, std::optional targetRange) { + + FoldingRangeCollector collector(AST, interestedOnly, targetRange); + collector.collectDrectives(AST.directives()); + collector.TraverseTranslationUnitDecl(AST.tu()); + return extract(collector.result); + } }; } // namespace -namespace feature::folding_range { +namespace feature::foldingrange { json::Value capability(json::Value clientCapabilities) { // Always return empty object. @@ -398,58 +341,41 @@ json::Value capability(json::Value clientCapabilities) { return {}; } -index::Shared foldingRange(ASTInfo& info, const SourceConverter& converter) { - FoldingRangeCollector collector{ - .cvtr = converter, - .src = info.srcMgr(), - .tkbuf = info.tokBuf(), - .result = FoldingRangeCollector::Storage{}, - .onlyMain = false, - }; - - collector.collectDrectives(info.directives()); - collector.TraverseTranslationUnitDecl(info.tu()); - - return std::move(collector.result); +index::Shared foldingRange(ASTInfo& AST) { + return FoldingRangeCollector::collect(AST, /*interestedOnly=*/false, std::nullopt); } -Result foldingRange(FoldingRangeParams& _, ASTInfo& info, const SourceConverter& converter) { - FoldingRangeCollector collector{ - .cvtr = converter, - .src = info.srcMgr(), - .tkbuf = info.tokBuf(), - .result = FoldingRangeCollector::Storage{}, - .onlyMain = true, - .mainFileID = info.srcMgr().getMainFileID(), - }; - collector.result.reserve(1); - - collector.collectDrectives(info.directives()); - collector.TraverseTranslationUnitDecl(info.tu()); - - return std::move(collector.result[collector.mainFileID]); +Result foldingRange(proto::FoldingRangeParams _, ASTInfo& AST) { + auto ranges = FoldingRangeCollector::collect(AST, /*interestedOnly=*/true, std::nullopt); + return std::move(ranges[AST.getInterestedFile()]); } -proto::FoldingRangeResult toLspResult(llvm::ArrayRef ranges, llvm::StringRef content, - const SourceConverter& SC) { - proto::FoldingRangeResult results; - results.reserve(ranges.size()); +proto::FoldingRange toLspType(const FoldingRange& folding, + const SourceConverter& SC, + llvm::StringRef content) { + auto range = SC.toRange(folding.range, content); + return { + .startLine = range.start.line, + .endLine = range.end.line, + .startCharacter = range.start.character, + .endCharacter = range.end.character, + .kind = folding.kind, + .collapsedText = "", + }; +} - for(auto& range: ranges) { - auto lspRange = SC.toRange(range.range, content); - results.push_back({ - .startLine = lspRange.start.line, - .endLine = lspRange.end.line, - .startCharacter = lspRange.start.character, - .endCharacter = lspRange.end.character, - .kind = range.kind, - }); +proto::FoldingRangeResult toLspResult(llvm::ArrayRef foldings, + const SourceConverter& SC, + llvm::StringRef content) { + + proto::FoldingRangeResult result; + result.reserve(foldings.size()); + for(const auto& folding: foldings) { + result.push_back(toLspType(folding, SC, content)); } - - results.shrink_to_fit(); - return results; + return result; } -} // namespace feature::folding_range +} // namespace feature::foldingrange } // namespace clice diff --git a/src/Feature/Hover.cpp b/src/Feature/Hover.cpp index 0d76b4fc..99ec43fb 100644 --- a/src/Feature/Hover.cpp +++ b/src/Feature/Hover.cpp @@ -4,8 +4,8 @@ #include "Compiler/AST.h" #include "Support/FileSystem.h" -#include -#include +#include "clang/Lex/LiteralSupport.h" +#include "clang/AST/DeclVisitor.h" #ifdef _WIN32 #include @@ -1461,7 +1461,7 @@ Result toMarkdown(const HoverInfo& hover, const config::HoverOption& option) { std::optional hover(uint32_t line, uint32_t col, ASTInfo& AST, const config::HoverOption& option) { - auto srcLoc = AST.srcMgr().translateLineCol(AST.mainFileID(), line, col); + auto srcLoc = AST.srcMgr().translateLineCol(AST.getInterestedFile(), line, col); if(srcLoc.isInvalid()) return std::nullopt; @@ -1470,7 +1470,7 @@ std::optional return std::nullopt; // Check if the position is in a include directive. - llvm::ArrayRef includes = AST.directives()[AST.mainFileID()].includes; + llvm::ArrayRef includes = AST.directives()[AST.getInterestedFile()].includes; if(auto hit = hit::header(includes, AST, line)) return hit; diff --git a/src/Feature/InlayHint.cpp b/src/Feature/InlayHint.cpp index 4c69f866..9acb8ad6 100644 --- a/src/Feature/InlayHint.cpp +++ b/src/Feature/InlayHint.cpp @@ -1,9 +1,9 @@ +#include "AST/FilterASTVisitor.h" #include "Basic/SourceConverter.h" #include "Compiler/Compilation.h" #include "Feature/InlayHint.h" -#include -#include +#include "clang/AST/TypeVisitor.h" namespace clice { @@ -34,10 +34,10 @@ using feature::inlay_hint::Result; struct TypeHintLinkBuilder : clang::TypeVisitor { using Base = clang::TypeVisitor; + ASTInfo& AST; + // The result buffer to write. std::vector& results; - const SourceConverter& cvtr; - const clang::SourceManager& SM; // const clang::PrintingPolicy& policy; @@ -65,7 +65,7 @@ struct TypeHintLinkBuilder : clang::TypeVisitor { void recordScope(const Decl* D, llvm::SmallVectorImpl& stack) { stack.push_back({ .value = D->getName().str(), - .location = cvtr.toLocalRange(D->getSourceRange(), SM), + .location = AST.toLocalRange(D->getSourceRange()).second, }); } @@ -96,7 +96,7 @@ struct TypeHintLinkBuilder : clang::TypeVisitor { recursiveMarkScope(RD->getDeclContext()); results.push_back({ .value = RD->getName().str(), - .location = cvtr.toLocalRange(RD->getFirstDecl()->getSourceRange(), SM), + .location = AST.toLocalRange(RD->getFirstDecl()->getSourceRange()).second, }); } @@ -178,10 +178,7 @@ struct TypeHintLinkBuilder : clang::TypeVisitor { } /// Build label parts for a given type, the result will be written to `hints`. - static void build(clang::QualType QT, - std::vector& hints, - const SourceConverter& cvtr, - const clang::SourceManager& SM) { + static void build(clang::QualType QT, std::vector& hints, ASTInfo& AST) { assert(!QT.isNull() && "QualType must not be Null."); if(isBuiltinType(QT) || isStdType(QT)) { @@ -189,12 +186,7 @@ struct TypeHintLinkBuilder : clang::TypeVisitor { return; } - TypeHintLinkBuilder builder{ - .results = hints, - .cvtr = cvtr, - .SM = SM, - }; - + TypeHintLinkBuilder builder{.AST = AST, .results = hints}; builder.Visit(QT.getTypePtr()); } @@ -202,8 +194,7 @@ struct TypeHintLinkBuilder : clang::TypeVisitor { static void buildWithKnownName(clang::QualType QT, llvm::StringRef name, std::vector& hints, - const SourceConverter& cvtr, - const clang::SourceManager& SM) { + ASTInfo& AST) { assert(!QT.isNull() && "QualType must not be Null."); if(name.contains("std::") || isBuiltinType(QT)) { @@ -211,12 +202,7 @@ struct TypeHintLinkBuilder : clang::TypeVisitor { return; } - TypeHintLinkBuilder builder{ - .results = hints, - .cvtr = cvtr, - .SM = SM, - }; - + TypeHintLinkBuilder builder{.AST = AST, .results = hints}; builder.Visit(QT.getTypePtr()); } }; @@ -225,55 +211,28 @@ struct TypeHintLinkBuilder : clang::TypeVisitor { /// A. Only collect hints in MainFileID. /// B. Collect hints in each files, used for header context. /// The result is always stored in a densmap>, and return as needed. -struct InlayHintCollector : clang::RecursiveASTVisitor { +struct InlayHintCollector : public FilteredASTVisitor { - using Base = clang::RecursiveASTVisitor; + using Base = FilteredASTVisitor; /// The result of inlay hints for given AST. using Storage = llvm::DenseMap; - const clang::SourceManager& src; - - const SourceConverter& cvtr; - - /// The restrict range of request. - const LocalSourceRange limit; + /// The result of inlay hints. + Storage result; /// The config of inlay hints collector. const config::InlayHintOption option; - /// Indicate that only hints in main file should be collected (mode A). - const bool onlyMain; - - /// The result of inlay hints. - Storage result; - - /// The printing policy of AST. - const clang::PrintingPolicy policy; - /// Whole source code text in main file. const llvm::StringRef code; - /// Do not produce inlay hints if either range ends is not within the main file. - bool needFilter(clang::SourceRange range) { - // skip invalid range or not in main file - if(range.isInvalid()) - return true; - - if(!onlyMain) - return false; - - if(!src.isInMainFile(range.getBegin()) || !src.isInMainFile(range.getEnd())) - return true; - - // not involved in restrict range - auto begin = src.getDecomposedLoc(range.getBegin()).second; - auto end = src.getDecomposedLoc(range.getEnd()).second; - if(end < limit.begin || begin > limit.end) - return true; - - return false; - } + InlayHintCollector(ASTInfo& ast, + bool interestedOnly, + std::optional limit, + const config::InlayHintOption& option) : + Base(ast, interestedOnly, limit), result(), option(option), + code(ast.getInterestedFileContent()) {} /// Shrink the hint text to the max length. static std::string shrinkHintText(std::string text, size_t maxLength) { @@ -285,7 +244,7 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { } std::string tryShrinkHintText(std::string text) { - return onlyMain ? shrinkHintText(text, option.maxLength) : text; + return interestedOnly ? shrinkHintText(std::move(text), option.maxLength) : text; } /// Collect hint for variable declared with `auto` keywords. @@ -299,7 +258,7 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { // For lambda expression, `getAsString` return a text like `(lambda at main.cpp:2:10)` // auto lambda = [](){ return 1; }; // Use a short text instead. - std::string typeName = deduced.getAsString(policy); + std::string typeName = deduced.getAsString(AST.context().getPrintingPolicy()); bool isLambda = false; if(typeName.contains("lambda")) @@ -309,21 +268,21 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { if(isLambda || !option.typeLink) { LablePart lable{.value = tryShrinkHintText(std::format(": {}", typeName))}; if(linkDeclRange.has_value()) - lable.location = cvtr.toLocalRange(*linkDeclRange, src); + lable.location = AST.toLocalRange(*linkDeclRange).second; labels.push_back(std::move(lable)); } else { labels.push_back({.value = ": "}); - TypeHintLinkBuilder::buildWithKnownName(deduced, typeName, labels, cvtr, src); + TypeHintLinkBuilder::buildWithKnownName(deduced, typeName, labels, AST); } + auto [locFileID, offset] = AST.getDecomposedLoc(identRange.getEnd()); InlayHint hint{ .kind = kind, - .offset = src.getDecomposedLoc(identRange.getEnd()).second, + .offset = offset, .labels = std::move(labels), }; - - clang::FileID fid = onlyMain ? src.getMainFileID() : src.getFileID(identRange.getBegin()); - result[fid].push_back(std::move(hint)); + clang::FileID fileID = interestedOnly ? AST.getInterestedFile() : locFileID; + result[fileID].push_back(std::move(hint)); } // If `expr` spells a single unqualified identifier, return that name, otherwise, return an @@ -340,9 +299,9 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { } /// Check if there is any comment like /*paramName*/ before a argument. - bool hasHandWriteComment(clang::SourceRange argument) { - auto [fid, offset] = src.getDecomposedLoc(argument.getBegin()); - if(fid != src.getMainFileID()) + bool hasHandWriteComment(clang::SourceRange argumentRange) { + auto [fileID, offset] = AST.getDecomposedLoc(argumentRange.getBegin()); + if(fileID != AST.getInterestedFile()) return false; // Get source text until the argument and drop end whitespace. @@ -393,30 +352,24 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { auto parmName = std::format("{}{}:", params[i]->getName(), hintRef ? "&" : ""); LablePart lable{ .value = tryShrinkHintText(std::move(parmName)), - .location = cvtr.toLocalRange(params[i]->getSourceRange(), src), + .location = AST.toLocalRange(params[i]->getSourceRange()).second, }; auto argBeginLoc = args[i]->getSourceRange().getBegin(); + auto [locFileID, offset] = AST.getDecomposedLoc(argBeginLoc); InlayHint hint{ .kind = kind, - .offset = src.getDecomposedLoc(argBeginLoc).second, + .offset = offset, .labels = {std::move(lable)}, }; - clang::FileID fid = onlyMain ? src.getMainFileID() : src.getFileID(argBeginLoc); - result[fid].push_back(std::move(hint)); + clang::FileID fileID = interestedOnly ? AST.getInterestedFile() : locFileID; + result[fileID].push_back(std::move(hint)); } } - bool TraverseDecl(clang::Decl* decl) { - if(!decl || needFilter(decl->getSourceRange())) - return true; - - return Base::TraverseDecl(decl); - } - bool VisitVarDecl(const clang::VarDecl* decl) { - // Hint local variable, global variable, and structure binding. + // Hint local variable, global variable, and structure binding only. if(!decl->isLocalVarDecl() && !decl->isFileVarDecl()) return true; @@ -633,21 +586,24 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { void collectReturnTypeHint(clang::SourceLocation hintLoc, clang::QualType retType, Kind kind) { std::vector labels; if(!option.typeLink) { + const auto& policy = AST.context().getPrintingPolicy(); + LablePart lable; lable.value = tryShrinkHintText(std::format("-> {}", retType.getAsString(policy))); labels.push_back(std::move(lable)); } else { labels.push_back({.value = "-> "}); - TypeHintLinkBuilder::build(retType, labels, cvtr, src); + TypeHintLinkBuilder::build(retType, labels, AST); } + auto [locFIleID, offset] = AST.getDecomposedLoc(hintLoc); InlayHint hint{ .kind = kind, - .offset = src.getDecomposedLoc(hintLoc).second, + .offset = offset, .labels = std::move(labels), }; - clang::FileID fid = onlyMain ? src.getMainFileID() : src.getFileID(hintLoc); + clang::FileID fid = interestedOnly ? AST.getInterestedFile() : locFIleID; result[fid].push_back(std::move(hint)); } @@ -658,13 +614,13 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { /// FIXME: /// Use a proper name such as simplified signature of funtion. auto typeLoc = decl->getTypeSourceInfo()->getTypeLoc().getSourceRange(); - auto begin = src.getCharacterData(typeLoc.getBegin()); - auto end = src.getCharacterData(typeLoc.getEnd()); - llvm::StringRef piece{begin, static_cast(end - begin) + 1}; + auto begin = AST.srcMgr().getCharacterData(typeLoc.getBegin()); + auto end = AST.srcMgr().getCharacterData(typeLoc.getEnd()); + llvm::StringRef source{begin, static_cast(end - begin) + 1}; // Right side of '}' collectBlockEndHint(decl->getBodyRBrace().getLocWithOffset(1), - std::format("// {}", piece), + std::format("// {}", source), decl->getSourceRange(), Kind::FunctionEnd, DecideDuplicated::Ignore); @@ -725,17 +681,18 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { void collectArrayElemIndexHint(int index, clang::SourceLocation location) { LablePart lable{ .value = std::format("[{}]=", index), // This shouldn't be shrinked. - .location = cvtr.toLocalRange(location, src), + .location = AST.toLocalRange(location).second, }; + auto [locFileID, offset] = AST.getDecomposedLoc(location); InlayHint hint{ .kind = Kind::ArrayIndex, - .offset = src.getDecomposedLoc(location).second, + .offset = offset, .labels = {std::move(lable)}, }; - clang::FileID fid = onlyMain ? src.getMainFileID() : src.getFileID(location); - result[fid].push_back(std::move(hint)); + clang::FileID fileID = interestedOnly ? AST.getInterestedFile() : locFileID; + result[fileID].push_back(std::move(hint)); } bool VisitInitListExpr(const clang::InitListExpr* Syn) { @@ -754,12 +711,13 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { } bool isMultiLineRange(const clang::SourceRange range) { - return range.isValid() && src.getPresumedLineNumber(range.getBegin()) < - src.getPresumedLineNumber(range.getEnd()); + const auto& SM = AST.srcMgr(); + return range.isValid() && SM.getPresumedLineNumber(range.getBegin()) < + SM.getPresumedLineNumber(range.getEnd()); } llvm::StringRef remainTextOfThatLine(clang::SourceLocation location) { - auto [_, offset] = src.getDecomposedLoc(location); + auto [_, offset] = AST.getDecomposedLoc(location); auto remain = code.substr(offset).split('\n').first; return remain.ltrim(); } @@ -781,21 +739,24 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { Ignore, }; - void collectBlockEndHint(clang::SourceLocation location, + void collectBlockEndHint(clang::SourceLocation endLoc, std::string text, clang::SourceRange linkRange, Kind kind, DecideDuplicated decision) { // Already has a comment in that line. - if(auto remain = remainTextOfThatLine(location); + if(auto remain = remainTextOfThatLine(endLoc); remain.starts_with("/*") || remain.starts_with("//")) return; - auto& state = result[onlyMain ? src.getMainFileID() : src.getFileID(location)]; + const auto& SM = AST.srcMgr(); + auto fileID = interestedOnly ? AST.getInterestedFile() : SM.getDecomposedLoc(endLoc).first; + auto& state = result[fileID]; + if(decision != DecideDuplicated::AcceptBoth && !state.empty()) { // Already has a duplicated hint in that line, use the newer hint instead. - auto lastHintLine = cvtr.toPosition(code, state.back().offset).line; - auto thatLine = cvtr.toPosition(location, src).line; + auto lastHintLine = SM.getLineNumber(fileID, state.back().offset); + auto thatLine = SM.getPresumedLineNumber(endLoc); if(lastHintLine == thatLine) { if(decision == DecideDuplicated::Replace) state.pop_back(); @@ -806,12 +767,12 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { LablePart lable{ .value = tryShrinkHintText(std::move(text)), - .location = cvtr.toLocalRange(linkRange, src), + .location = AST.toLocalRange(linkRange).second, }; InlayHint hint{ .kind = kind, - .offset = src.getDecomposedLoc(location).second, + .offset = AST.getDecomposedLoc(endLoc).second, .labels = {std::move(lable)}, }; @@ -846,18 +807,19 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { LablePart lable{ .value = tryShrinkHintText(std::format("size: {}, align: {}", size, align)), - .location = cvtr.toLocalRange(decl->getSourceRange(), src), + .location = AST.toLocalRange(decl->getSourceRange()).second, }; // right side of identifier. auto tail = decl->getLocation().getLocWithOffset(decl->getName().size()); + auto [locFildID, offset] = AST.getDecomposedLoc(tail); InlayHint hint{ .kind = Kind::StructSizeAndAlign, - .offset = src.getDecomposedLoc(tail).second, + .offset = offset, .labels = {std::move(lable)}, }; - auto fid = onlyMain ? src.getMainFileID() : src.getFileID(tail); + auto fid = interestedOnly ? AST.getInterestedFile() : locFildID; result[fid].push_back(std::move(hint)); } @@ -907,6 +869,15 @@ struct InlayHintCollector : clang::RecursiveASTVisitor { // } // return true; // } + + static Storage collect(ASTInfo& AST, + bool interestedOnly, + std::optional limit, + const config::InlayHintOption& option) { + InlayHintCollector collector{AST, interestedOnly, limit, option}; + collector.TraverseTranslationUnitDecl(AST.tu()); + return std::move(collector.result); + } }; using feature::inlay_hint::InlayHintKind; @@ -961,8 +932,9 @@ proto::InlayHintLablePart toLspType(const LablePart& lable, llvm::StringRef content, const SourceConverter& SC) { proto::InlayHintLablePart lspPart; - lspPart.value = InlayHintCollector::shrinkHintText(lable.value, maxHintLength); lspPart.tooltip = blank(); + lspPart.value = maxHintLength ? InlayHintCollector::shrinkHintText(lable.value, maxHintLength) + : lable.value; if(lable.location.has_value()) lspPart.location = {.uri = docuri.str(), .range = SC.toRange(*lable.location, content)}; return lspPart; @@ -994,49 +966,38 @@ proto::InlayHint toLspType(const InlayHint& hint, return lspHint; } -Result inlayHints(proto::InlayHintParams param, - ASTInfo& info, - const SourceConverter& converter, - const config::InlayHintOption& option) { - llvm::StringRef codeText = info.getMainFileContent(); +namespace { - // Take 0-0 based Lsp Location from `param.range` and convert it to offset pair. - LocalSourceRange requestRange{ - .begin = static_cast(converter.toOffset(codeText, param.range.start)), - .end = static_cast(converter.toOffset(codeText, param.range.end)), - }; - - const clang::SourceManager& src = info.srcMgr(); - // If request range is invalid, use the whole main file as the restrict range. - if(requestRange.begin >= requestRange.end) { - clang::FileID main = src.getMainFileID(); - requestRange.begin = src.getDecomposedSpellingLoc(src.getLocForStartOfFile(main)).second; - requestRange.end = src.getDecomposedSpellingLoc(src.getLocForEndOfFile(main)).second; - } - - /// TODO: - /// Check and fix invalid options before collect hints. - InlayHintCollector collector{ - .src = src, - .cvtr = converter, - .limit = requestRange, - .option = option, - .onlyMain = true, - .result = InlayHintCollector::Storage{}, - .policy = info.context().getPrintingPolicy(), - .code = codeText, - }; - - collector.TraverseTranslationUnitDecl(info.tu()); - - return std::move(collector.result[src.getMainFileID()]); +clang::SourceLocation fromLineCol(clang::FileID file, + proto::Position pos, + const clang::SourceManager& SM) { + return SM.translateLineCol(file, pos.line + 1, pos.character + 1); } -index::Shared inlayHints(proto::DocumentUri uri, - ASTInfo& info, - const SourceConverter& converter) { - const clang::SourceManager& src = info.srcMgr(); +} // namespace +Result inlayHints(proto::InlayHintParams param, + ASTInfo& AST, + const config::InlayHintOption& option) { + assert(param.range.start != param.range.end && "Invalid range from client."); + + // Take 0-0 based Lsp Location from `param.range` and convert it to offset pair. + const clang::SourceManager& SM = AST.srcMgr(); + clang::SourceRange requestRange{ + fromLineCol(AST.getInterestedFile(), param.range.start, SM), + fromLineCol(AST.getInterestedFile(), param.range.end, SM), + }; + assert(requestRange.isValid() && "Invalid SourceRange."); + + /// TODO: + /// Check and fix invalid options before collecting hints. + + auto limit = AST.toLocalRange(requestRange).second; + auto result = InlayHintCollector::collect(AST, true, limit, option); + return std::move(result[AST.getInterestedFile()]); +} + +index::Shared inlayHints(ASTInfo& AST) { config::InlayHintOption enableAll; enableAll.maxLength = 0; enableAll.maxArrayElements = 0; @@ -1047,18 +1008,7 @@ index::Shared inlayHints(proto::DocumentUri uri, enableAll.numberLiteralToHex = true; enableAll.cstrLength = true; - InlayHintCollector collector{ - .src = src, - .cvtr = converter, - .option = enableAll, - .onlyMain = false, - .result = InlayHintCollector::Storage{}, - .policy = info.context().getPrintingPolicy(), - .code = src.getBufferData(src.getMainFileID()), - }; - - collector.TraverseTranslationUnitDecl(info.tu()); - return std::move(collector.result); + return InlayHintCollector::collect(AST, false, std::nullopt, enableAll); } proto::InlayHintsResult toLspType(llvm::ArrayRef result, @@ -1074,8 +1024,9 @@ proto::InlayHintsResult toLspType(llvm::ArrayRef result, /// `config::maxArrayElements` will be ignored because we can't recover the parent-child /// relationship of AST node from `InlayHint`. + auto option = config.value_or(config::InlayHintOption{}); for(auto& hint: result) { - if(config.has_value() && !isAvailableWithOption(hint.kind, *config)) + if(!isAvailableWithOption(hint.kind, *config)) continue; lspRes.push_back(toLspType(hint, config->maxLength, docuri, content, SC)); diff --git a/unittests/Feature/FoldingRange.cpp b/unittests/Feature/FoldingRange.cpp index d9782e96..f76b72eb 100644 --- a/unittests/Feature/FoldingRange.cpp +++ b/unittests/Feature/FoldingRange.cpp @@ -5,7 +5,7 @@ namespace clice::testing { namespace { -using namespace clice::feature::folding_range; +using namespace clice::feature::foldingrange; struct FoldingRange : public ::testing::Test { std::optional tester; @@ -17,9 +17,8 @@ struct FoldingRange : public ::testing::Test { tester->run(); auto& info = tester->info; - FoldingRangeParams param; - SourceConverter converter; - result = foldingRange(param, *info, converter); + proto::FoldingRangeParams param; + result = foldingRange(param, *info); } index::Shared runWithHeader(llvm::StringRef source, llvm::StringRef header) { @@ -28,12 +27,13 @@ struct FoldingRange : public ::testing::Test { tester->run(); auto& info = tester->info; - FoldingRangeParams param; - SourceConverter converter; - return foldingRange(*info, converter); + proto::FoldingRangeParams param; + return foldingRange(*info); } - void EXPECT_RANGE(std::size_t index, llvm::StringRef begin, llvm::StringRef end, + void EXPECT_RANGE(std::size_t index, + llvm::StringRef begin, + llvm::StringRef end, std::source_location current = std::source_location::current()) { auto& folding = result[index]; @@ -47,23 +47,23 @@ struct FoldingRange : public ::testing::Test { TEST_F(FoldingRange, Namespace) { run(R"cpp( + namespace single_line {$(1) -//$(2) -} + // +$(2)} namespace with_nodes {$(3) // -//struct _ {};$(4) -} +struct _ {}; + +$(4)} namespace empty {} -namespace ugly +namespace ugly {$(5) -// -//$(6) -} + $(6)} )cpp"); @@ -72,21 +72,44 @@ namespace ugly EXPECT_RANGE(2, "5", "6"); } +TEST_F(FoldingRange, NamespaceExpandedFromMacro) { + run(R"cpp( +#define NS_OUTER namespace outter { +#define NS_INNER namespace inner { +#define END_MACRO } + +NS_OUTER$(1) + NS_INNER$(3) + namespace inner {$(5) + + $(6)} + END_MACRO$(4) +END_MACRO$(2) + +)cpp"); + + EXPECT_EQ(result.size(), 3); + + EXPECT_RANGE(0, "1", "2"); + EXPECT_RANGE(1, "3", "4"); + EXPECT_RANGE(2, "5", "6"); +} + TEST_F(FoldingRange, Enum) { run(R"cpp( enum _0 {$(1) A, B, - C$(2) -}; + C +$(2)}; enum _1 { D }; enum class _2 {$(3) A, B, - C$(4) -}; + C +$(4)}; )cpp"); @@ -96,42 +119,44 @@ enum class _2 {$(3) TEST_F(FoldingRange, RecordDecl) { run(R"cpp( -struct _2 {$(1) - int x; - float y;$(2) -}; - -struct _3 {}; - -struct _4; - -union _5 {$(3) - int x; - float y;$(4) -}; - -struct _6 {$(5) - struct nested {$(7) - //$(8) - }; - - //$(6) -}; +// struct _2 {$(1) +// int x; +// float y; +// $(2)}; +// +// struct _3 {}; +// +// struct _4; +// +// union _5 {$(3) +// int x; +// float y; +// $(4)}; +// +// struct _6 {$(5) +// struct one_nested {$(7) +// // +// $(8)}; +// +// // +// $(6)}; void f() {$(9) - struct nested {$(11) - //$(12) - };$(10) -} + struct another_nested {$(11) + // + $(12)}; +$(10)} )cpp"); - EXPECT_RANGE(0, "1", "2"); - EXPECT_RANGE(1, "3", "4"); - EXPECT_RANGE(2, "5", "6"); - EXPECT_RANGE(3, "7", "8"); - EXPECT_RANGE(4, "9", "10"); - EXPECT_RANGE(5, "11", "12"); + // EXPECT_RANGE(0, "1", "2"); + // EXPECT_RANGE(1, "3", "4"); + // EXPECT_RANGE(2, "5", "6"); + // EXPECT_RANGE(3, "7", "8"); + // EXPECT_RANGE(4, "9", "10"); + // EXPECT_RANGE(5, "11", "12"); + EXPECT_RANGE(0, "9", "10"); + EXPECT_RANGE(1, "11", "12"); } TEST_F(FoldingRange, CXXRecordDeclAndMemberMethod) { @@ -140,21 +165,20 @@ struct _2 {$(1) int x; float y; - _2() = default;$(2) -}; + _2() = default; +$(2)}; struct _3 {$(3) void method() {$(5) - int x = 0;$(6) - } + int x = 0; + $(6)} - void parameter (){$(7) - //$(8) - } + void parameter () {$(7) + // + $(8)} void skip() {}; -$(4) -}; +$(4)}; struct _4; )cpp"); @@ -168,15 +192,20 @@ struct _4; TEST_F(FoldingRange, LambdaCapture) { run(R"cpp( auto z = [$(1) - x = 0, y = 1$(2) -]() {$(3) - //$(4) -}; + x = 0, y = 1 + $(2)]() {$(3) + // +$(4)}; + +int array[4] = {0}; auto s = [$(5) x=0, - y = 1$(6) -](){ return; }; + y = 1, + z = array[ + 0], + k = -1 + $(6)](){ return; }; )cpp"); @@ -192,21 +221,23 @@ TEST_F(FoldingRange, LambdaExpression) { auto _0 = [](int _) {}; auto _1 = [](int _) {$(1) - //$(2) -}; + // +$(2)}; auto _2 = [](int _) {$(3) // - return 0;$(4) -}; + return 0; + $(4)}; auto _3 = []($(5) int _1, - int _2$(6) - ) {}; + int _2 + $(6)) {}; )cpp"); + EXPECT_EQ(result.size(), 3); + EXPECT_RANGE(0, "1", "2"); EXPECT_RANGE(1, "3", "4"); EXPECT_RANGE(2, "5", "6"); @@ -218,20 +249,20 @@ void e() {} void f($(1) // -//$(2) -) {} +// +$(2)) {} void g($(3) int x, int y = 2 -//$(4) -) {} +// +$(4)) {} void d($(5) int _1, int _2, - ...$(6) -); + ... + $(6)); )cpp"); EXPECT_RANGE(0, "1", "2"); @@ -243,22 +274,23 @@ TEST_F(FoldingRange, FunctionBody) { run(R"cpp( void f() {$(1) // -//$(2) -} +// +$(2)} void g() {$(3) - int x = 0;$(4) -} + int x = 0; +$(4)} void e() {} void n() {$(5) {$(7) - // empty bock $(8) - } - //$(6) -} + // empty bock + $(8)} + // +$(6)} )cpp"); + EXPECT_EQ(result.size(), 4); EXPECT_RANGE(0, "1", "2"); EXPECT_RANGE(1, "3", "4"); @@ -276,9 +308,9 @@ int main() {$(1) return f($(3) 1, 2, 3, - 4, 5, 6$(4) - );$(2) -} + 4, 5, 6 + $(4)); +$(2)} )cpp"); EXPECT_RANGE(0, "1", "2"); @@ -291,18 +323,18 @@ int main () {$(1) {$(3) {$(5) - //$(6) - } + // + $(6)} {$(7) - //$(8) - } + // + $(8)} - //$(4) - } + // + $(4)} - return 0;$(2) -} + return 0; +$(2)} )cpp"); @@ -316,13 +348,13 @@ TEST_F(FoldingRange, InitializeList) { struct L { int xs[4]; }; L l1 = {$(1) - 1, 2, 3, 4$(2) -}; + 1, 2, 3, 4 +$(2)}; L l2 = {$(3) // -//$(4) -}; +// +$(4)}; )cpp"); @@ -336,52 +368,59 @@ struct empty { int x; }; class _0 {$(1) public:$(3) - int x;$(4) -private:$(5) - int z;$(2)$(6) -}; + int x; + +$(4)private:$(5) + int z; +$(2)$(6)}; struct _1 {$(7) int x; private:$(9) - int z;$(8)$(10) -}; + int z; +$(8)$(10)}; struct _2 {$(11) public: private: -public:$(12) -}; +public:$(13) + +int x = 1; + +$(12)$(14)}; )cpp"); + EXPECT_EQ(result.size(), 9); + EXPECT_RANGE(0, "1", "2"); EXPECT_RANGE(1, "3", "4"); EXPECT_RANGE(2, "5", "6"); EXPECT_RANGE(3, "7", "8"); EXPECT_RANGE(4, "9", "10"); EXPECT_RANGE(5, "11", "12"); + + // do not test result[6] and result[7] + + EXPECT_RANGE(8, "13", "14"); } TEST_F(FoldingRange, Macro) { run(R"cpp( -#$(1)ifdef M1 -$(2) -#$(3)else +#ifdef M1 - #$(5)ifdef M2 +#else + + #ifdef M2 + - //$(6) #endif -//$(4) #endif )cpp"); - EXPECT_RANGE(0, "1", "2"); - EXPECT_RANGE(1, "5", "6"); - EXPECT_RANGE(2, "3", "4"); + EXPECT_EQ(result.size(), 3); } TEST_F(FoldingRange, PragmaRegion) { @@ -390,16 +429,15 @@ TEST_F(FoldingRange, PragmaRegion) { #pragma region level2 $(2) #pragma region level3 $(3) - //$(4) - #pragma endregion level3 + $(4)#pragma endregion level3 - //$(5) - #pragma endregion level2 + + $(5)#pragma endregion level2 -//$(6) -#pragma endregion level1 -#pragma endregion // mismatch region, skipeed +$(6)#pragma endregion level1 + +#pragma endregion // mismatch region, skipped // broken region, use the end of file as endregion #pragma region $(7) @@ -433,11 +471,11 @@ $(4) } )cpp"; - auto full = runWithHeader(source, header); - EXPECT_EQ(full.size(), 2); + auto multifiles = runWithHeader(source, header); + EXPECT_EQ(multifiles.size(), 2); auto mainID = tester->info->srcMgr().getMainFileID(); - for(auto& [id, result]: full) { + for(auto& [id, result]: multifiles) { if(id == mainID) { EXPECT_EQ(result.size(), 1); } else { diff --git a/unittests/Feature/Hover.cpp b/unittests/Feature/Hover.cpp index 8301160a..5dbe2037 100644 --- a/unittests/Feature/Hover.cpp +++ b/unittests/Feature/Hover.cpp @@ -3,7 +3,7 @@ #include "src/Feature/Hover.cpp" -#include +#include "clang/AST/RecursiveASTVisitor.h" namespace clice::testing { @@ -409,7 +409,7 @@ $(n1)names$(n2)pace$(n3) outt$(n4)er { TEST_F(Hover, VariableAndLiteral) { auto code = R"cpp( // introduce size_t - #include + #include long operator ""_w(const char*, size_t) { return 1; diff --git a/unittests/Feature/InlayHint.cpp b/unittests/Feature/InlayHint.cpp index 2fd2f98f..fe7b4b76 100644 --- a/unittests/Feature/InlayHint.cpp +++ b/unittests/Feature/InlayHint.cpp @@ -29,8 +29,14 @@ protected: tester.emplace("main.cpp", code); tester->run(); auto& info = tester->info; - SourceConverter converter; - result = inlayHints({.range = range}, *info, converter, option); + + proto::Range limit = range; + if(limit.start.line == limit.end.line && limit.start.character == limit.end.character && + limit.start.line == 0 && limit.start.character == 0) { + limit = {.start = {}, .end = tester->endOfFile()}; + } + + result = inlayHints({.range = limit}, *info, option); } size_t indexOf(llvm::StringRef key) { @@ -43,7 +49,7 @@ protected: return std::distance(result.begin(), iter); } - std::string joinLabels(const InlayHint& hint) { + static std::string joinLabels(const InlayHint& hint) { std::string text; for(auto& lable: hint.labels) { text += lable.value; @@ -51,6 +57,14 @@ protected: return text; } + static std::string joinLabels(const proto::InlayHint& hint) { + std::string text; + for(auto& lable: hint.lables) { + text += lable.value; + } + return text; + } + void EXPECT_AT(llvm::StringRef key, llvm::StringRef text) { auto index = indexOf(key); EXPECT_EQ(text, joinLabels(result[index])); @@ -460,8 +474,7 @@ namespace _2 { auto& info = tx.info; EXPECT_TRUE(info.has_value()); - SourceConverter cvtr{proto::PositionEncodingKind::UTF8}; - auto maps = inlayHints("", *info, cvtr); + auto maps = inlayHints(*info); // 2 fileID EXPECT_EQ(maps.size(), 2); @@ -479,8 +492,12 @@ namespace _2 { .blockEnd = true, .structSizeAndAlign = false, }; - auto lspRes = toLspType(result, "", fixOption, header, cvtr); + + SourceConverter SC; + auto lspRes = toLspType(result, "", fixOption, header, SC); EXPECT_EQ(lspRes.size(), 2); + EXPECT_TRUE(lspRes[0].lables[0].value.contains("namespace _1")); + EXPECT_EQ(joinLabels(lspRes[1]), ": _1::_2345678"); } } }