diff --git a/clang-tools-extra/clangd/SourceCode.h b/clang-tools-extra/clangd/SourceCode.h --- a/clang-tools-extra/clangd/SourceCode.h +++ b/clang-tools-extra/clangd/SourceCode.h @@ -20,8 +20,8 @@ #include "clang/Basic/SourceManager.h" #include "clang/Format/Format.h" #include "clang/Tooling/Core/Replacement.h" -#include "llvm/ADT/StringSet.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" #include "llvm/Support/SHA1.h" namespace clang { @@ -65,6 +65,9 @@ /// FIXME: This should return an error if the location is invalid. Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc); +/// Turn a SourceLocation into a pair of positions +Range sourceRangeToRange(const SourceManager &SM, SourceRange SrcRange); + /// Returns the taken range at \p TokLoc. llvm::Optional getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, diff --git a/clang-tools-extra/clangd/SourceCode.cpp b/clang-tools-extra/clangd/SourceCode.cpp --- a/clang-tools-extra/clangd/SourceCode.cpp +++ b/clang-tools-extra/clangd/SourceCode.cpp @@ -12,6 +12,8 @@ #include "Logger.h" #include "Protocol.h" #include "clang/AST/ASTContext.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TokenKinds.h" #include "clang/Format/Format.h" @@ -196,6 +198,11 @@ return P; } +Range sourceRangeToRange(const SourceManager &SM, SourceRange SrcRange) { + return Range{sourceLocToPosition(SM, SrcRange.getBegin()), + sourceLocToPosition(SM, SrcRange.getEnd())}; +} + llvm::Optional getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc) { @@ -242,20 +249,97 @@ return L == R.getEnd() || halfOpenRangeContains(Mgr, R, L); } -llvm::Optional toHalfOpenFileRange(const SourceManager &Mgr, +// FIXME: Here we check whether the token at the location is a greatergreater +// (>>) token and consider it as a single greater (>). This is to get it working +// for templates but it isn't correct for the right shift operator. We can avoid +// this by using half open char ranges in getFileRange() but getting token +// ending is not well supported in macroIDs. +static unsigned getTokenLengthAtLoc(SourceLocation Loc, const SourceManager &SM, + const LangOptions &LangOpts) { + Token TheTok; + if (!Lexer::getRawToken(Loc, TheTok, SM, LangOpts)) { + if (TheTok.is(tok::greatergreater)) + return 1; + else + return TheTok.getLength(); + } + return 0; +} +// Returns location of the last character of the token at a given loc +static SourceLocation getLocForTokenEnd(SourceLocation BeginLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + unsigned Len = getTokenLengthAtLoc(BeginLoc, SM, LangOpts); + return BeginLoc.getLocWithOffset(Len ? Len - 1 : 0); +} + +// Returns location of the starting of the token at a given EndLoc +static SourceLocation getLocForTokenBegin(SourceLocation EndLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + return EndLoc.getLocWithOffset( + -(signed)getTokenLengthAtLoc(EndLoc, SM, LangOpts)); +} + +// Converts a char source range to a token range. +static SourceRange toTokenRange(CharSourceRange Range, const SourceManager &SM, + const LangOptions &LangOpts) { + if (!Range.isTokenRange()) + Range.setEnd(getLocForTokenBegin(Range.getEnd(), SM, LangOpts)); + return Range.getAsRange(); +} +// Returns the union of two token ranges. +// To find the maximum of the Ends of the ranges, we compare the location of the +// last character of the token. +static SourceRange unionTokenRange(SourceRange R1, SourceRange R2, + const SourceManager &SM, + const LangOptions &LangOpts) { + SourceLocation E1 = getLocForTokenEnd(R1.getEnd(), SM, LangOpts); + SourceLocation E2 = getLocForTokenEnd(R2.getEnd(), SM, LangOpts); + return SourceRange(std::min(R1.getBegin(), R2.getBegin()), + E1 < E2 ? R2.getEnd() : R1.getEnd()); +} + +// returns the tokenFileRange for a given Location as a Token Range +static SourceRange getTokenFileRange(SourceLocation Loc, + const SourceManager &SM, + const LangOptions &LangOpts) { + SourceRange FileRange = Loc; + while (!FileRange.getBegin().isFileID()) { + assert(!FileRange.getEnd().isFileID() && + "Both Begin and End should be MacroIDs."); + if (SM.isMacroArgExpansion(FileRange.getBegin())) { + FileRange.setBegin(SM.getImmediateSpellingLoc(FileRange.getBegin())); + FileRange.setEnd(SM.getImmediateSpellingLoc(FileRange.getEnd())); + } else { + SourceRange ExpansionRangeForBegin = toTokenRange( + SM.getImmediateExpansionRange(FileRange.getBegin()), SM, LangOpts); + SourceRange ExpansionRangeForEnd = toTokenRange( + SM.getImmediateExpansionRange(FileRange.getEnd()), SM, LangOpts); + FileRange = unionTokenRange(ExpansionRangeForBegin, ExpansionRangeForEnd, + SM, LangOpts); + } + } + return FileRange; +} + +llvm::Optional toHalfOpenFileRange(const SourceManager &SM, const LangOptions &LangOpts, SourceRange R) { - auto Begin = Mgr.getFileLoc(R.getBegin()); - if (Begin.isInvalid()) + SourceRange R1 = getTokenFileRange(R.getBegin(), SM, LangOpts); + if (!isValidFileRange(SM, R1)) return llvm::None; - auto End = Mgr.getFileLoc(R.getEnd()); - if (End.isInvalid()) + + SourceRange R2 = getTokenFileRange(R.getEnd(), SM, LangOpts); + if (!isValidFileRange(SM, R2)) return llvm::None; - End = Lexer::getLocForEndOfToken(End, 0, Mgr, LangOpts); - SourceRange Result(Begin, End); - if (!isValidFileRange(Mgr, Result)) + SourceRange Result = unionTokenRange(R1, R2, SM, LangOpts); + unsigned TokLen = getTokenLengthAtLoc(Result.getEnd(), SM, LangOpts); + Result.setEnd(Result.getEnd().getLocWithOffset(TokLen)); + if (!isValidFileRange(SM, Result)) return llvm::None; + return Result; } @@ -603,7 +687,6 @@ Found.push_back(Used.getKey()); } - llvm::sort(Found, [&](const std::string &LHS, const std::string &RHS) { if (Current == RHS) return false; diff --git a/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp b/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp --- a/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp +++ b/clang-tools-extra/clangd/unittests/SourceCodeTests.cpp @@ -9,6 +9,8 @@ #include "Context.h" #include "Protocol.h" #include "SourceCode.h" +#include "TestTU.h" +#include "clang/Basic/LangOptions.h" #include "clang/Format/Format.h" #include "llvm/Support/Error.h" #include "llvm/Support/raw_os_ostream.h" @@ -404,6 +406,40 @@ } } +// Test for functions toHalfOpenFileRange and getHalfOpenFileRange +// FIXME: Need better testing support to be able to check more than just Decls. +TEST(SourceCodeTests, HalfOpenFileRange) { + Annotations Test(R"cpp( + #define FOO(X, Y) int Y = ++X + #define BAR(X) X + 1 + template + class P {}; + void f() { + $a[[P>>>> a]]; + $b[[int b = 1]]; + $c[[FOO(b, c)]]; + $d[[FOO(BAR(BAR(b)), d)]]; + } + )cpp"); + + ParsedAST AST = TestTU::withCode(Test.code()).build(); + const SourceManager &SM = AST.getSourceManager(); + const LangOptions &LangOpts = AST.getASTContext().getLangOpts(); + + auto CheckRange = [&](llvm::StringRef Name) { + const NamedDecl &Decl = findUnqualifiedDecl(AST, Name); + auto FileRange = toHalfOpenFileRange(SM, LangOpts, Decl.getSourceRange()); + ASSERT_NE(FileRange, llvm::None); + Range HalfOpenRange = sourceRangeToRange(SM, *FileRange); + EXPECT_EQ(HalfOpenRange, Test.ranges(Name)[0]); + }; + + CheckRange("a"); + CheckRange("b"); + CheckRange("c"); + CheckRange("d"); +} + } // namespace } // namespace clangd } // namespace clang