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 @@ -237,6 +237,20 @@ return halfOpenToRange(SM, CharSourceRange::getCharRange(TokLoc, End)); } +static bool isOverloadedOperator(const Token &Tok) { + switch (Tok.getKind()) { +#define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemOnly) \ + case tok::Token: +#define OVERLOADED_OPERATOR_MULTI(Name, Spelling, Unary, Binary, MemOnly) +#include "clang/Basic/OperatorKinds.def" + return true; + + default: + break; + } + return false; +} + SourceLocation getBeginningOfIdentifier(const Position &Pos, const SourceManager &SM, const LangOptions &LangOpts) { @@ -247,27 +261,63 @@ return SourceLocation(); } - // GetBeginningOfToken(pos) is almost what we want, but does the wrong thing - // if the cursor is at the end of the identifier. - // Instead, we lex at GetBeginningOfToken(pos - 1). The cases are: - // 1) at the beginning of an identifier, we'll be looking at something - // that isn't an identifier. - // 2) at the middle or end of an identifier, we get the identifier. - // 3) anywhere outside an identifier, we'll get some non-identifier thing. - // We can't actually distinguish cases 1 and 3, but returning the original - // location is correct for both! + // GetBeginningOfToken(InputLoc) is almost what we want, but does the wrong + // thing if the cursor is at the end of the token (identifier or operator). + // The cases are: + // 1) at the beginning of the token + // 2) at the middle of the token + // 3) at the end of the token + // 4) anywhere outside the identifier or operator + // To distinguish all cases, we lex both at the + // GetBeginningOfToken(InputLoc-1) and GetBeginningOfToken(InputLoc), for + // cases 1 and 4, we just return the original location. SourceLocation InputLoc = SM.getComposedLoc(FID, *Offset); - if (*Offset == 0) // Case 1 or 3. + if (*Offset == 0) // Case 1 or 4. return InputLoc; SourceLocation Before = SM.getComposedLoc(FID, *Offset - 1); + Token BeforeTok; + SourceLocation BeforeTokBeginning = + Lexer::GetBeginningOfToken(Before, SM, LangOpts); + if (Lexer::getRawToken(BeforeTokBeginning, BeforeTok, SM, LangOpts, false)) { + // Case 1 ("^foo;", "^++foo;") or Case 4. + return InputLoc; + } - Before = Lexer::GetBeginningOfToken(Before, SM, LangOpts); - Token Tok; - if (Before.isValid() && - !Lexer::getRawToken(Before, Tok, SM, LangOpts, false) && - Tok.is(tok::raw_identifier)) - return Before; // Case 2. - return InputLoc; // Case 1 or 3. + Token CurrentTok; + SourceLocation CurrentTokBeginning = + Lexer::GetBeginningOfToken(InputLoc, SM, LangOpts); + if (Lexer::getRawToken(CurrentTokBeginning, CurrentTok, SM, LangOpts, + false)) { + // Cases 3: "abc^ ++;", "++^ abc;". + if (BeforeTok.is(tok::raw_identifier) || isOverloadedOperator(BeforeTok)) + return BeforeTokBeginning; + return InputLoc; // Case 4. + } + + // Case 1 or 2. + if (CurrentTok.is(tok::raw_identifier)) { + // The cursor is at the beginning or middle of the identifier + return CurrentTokBeginning; + } else if (isOverloadedOperator(CurrentTok)) { + if (BeforeTok.is(tok::raw_identifier)) + // For case "foo^++;", we'd return the beginning of the identifier. + return BeforeTokBeginning; + // Cases: "^++foo;", "foo+^+;" + return CurrentTokBeginning; + } + + // Case 3. + if (BeforeTok.is(tok::raw_identifier)) { + // cases at the end of the identifier: "foo^;", "foo^++;" + return BeforeTokBeginning; + } else if (isOverloadedOperator(BeforeTok)) { + // For case "++^foo;", we'd return the beginning of the identifier. + if (CurrentTok.is(tok::raw_identifier)) + return CurrentTokBeginning; + // Normal cases at the end of the operator: "foo++^;" + return BeforeTokBeginning; + } + return InputLoc; // Case 4. } bool isValidFileRange(const SourceManager &Mgr, SourceRange R) { 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 @@ -319,14 +319,22 @@ Bar* bar; )cpp"; // First ^ is the expected beginning, last is the search position. - for (std::string Text : std::vector{ + for (const std::string &Text : std::vector{ "int ^f^oo();", // inside identifier "int ^foo();", // beginning of identifier "int ^foo^();", // end of identifier "int foo(^);", // non-identifier "^int foo();", // beginning of file (can't back up) "int ^f0^0();", // after a digit (lexing at N-1 is wrong) - "int ^λλ^λ();", // UTF-8 handled properly when backing up + "void f(int abc) { ^abc^++; }", // range of identifier + "void f(int abc) { ++^abc^; }", // range of identifier + "void f(int abc) { ^+^+abc; }", // range of operator + "void f(int abc) { ^abc^ ++; }", // range of identifier + "void f(int abc) { abc ^++^; }", // range of range + "void f(int abc) { ^++^ abc; }", // range of range + "void f(int abc) { ++ ^abc^; }", // range of identifier + "void f() {^ }", // outside of identifier and operator + "int ^λλ^λ();", // UTF-8 handled properly when backing up // identifier in macro arg "MACRO(bar->^func())", // beginning of identifier diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -434,6 +434,15 @@ auto x = m^akeX(); } )cpp", + + R"cpp( + struct X { + X& [[operator]]++() {} + }; + void foo(X& x) { + +^+x; + } + )cpp", }; for (const char *Test : Tests) { Annotations T(Test);