diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index c5586eac606a6..5eef43e93cb51 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -1491,6 +1491,46 @@ FuzzyFindRequest speculativeFuzzyFindRequestForCompletion( return CachedReq; } +// This function is similar to Lexer::findNextToken(), but assumes +// that the input SourceLocation is the completion point (which is +// a case findNextToken() does not handle). +std::optional +findTokenAfterCompletionPoint(SourceLocation CompletionPoint, + const SourceManager &SM, + const LangOptions &LangOpts) { + SourceLocation Loc = CompletionPoint; + if (Loc.isMacroID()) { + if (!Lexer::isAtEndOfMacroExpansion(Loc, SM, LangOpts, &Loc)) + return std::nullopt; + } + + // Advance to the next SourceLocation after the completion point. + // Lexer::findNextToken() would call MeasureTokenLength() here, + // which does not handle the completion point (and can't, because + // the Lexer instance it constructs internally doesn't have a + // Preprocessor and so doesn't know about the completion point). + Loc = Loc.getLocWithOffset(1); + + // Break down the source location. + std::pair LocInfo = SM.getDecomposedLoc(Loc); + + // Try to load the file buffer. + bool InvalidTemp = false; + StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp); + if (InvalidTemp) + return std::nullopt; + + const char *TokenBegin = File.data() + LocInfo.second; + + // Lex from the start of the given location. + Lexer TheLexer(SM.getLocForStartOfFile(LocInfo.first), LangOpts, File.begin(), + TokenBegin, File.end()); + // Find the token. + Token Tok; + TheLexer.LexFromRawLexer(Tok); + return Tok; +} + // Runs Sema-based (AST) and Index-based completion, returns merged results. // // There are a few tricky considerations: @@ -1589,7 +1629,7 @@ class CodeCompleteFlow { auto Style = getFormatStyleForFile(SemaCCInput.FileName, SemaCCInput.ParseInput.Contents, *SemaCCInput.ParseInput.TFS); - const auto NextToken = Lexer::findNextToken( + const auto NextToken = findTokenAfterCompletionPoint( Recorder->CCSema->getPreprocessor().getCodeCompletionLoc(), Recorder->CCSema->getSourceManager(), Recorder->CCSema->LangOpts); if (NextToken) diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 766998eb4f3c7..692c6db3c51be 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -3892,6 +3892,29 @@ TEST(CompletionTest, FunctionArgsExist) { kind(CompletionItemKind::Function)))); } +TEST(CompletionTest, FunctionArgsExist_Issue1785) { + // This is a scenario where the implementation of our check for + // "is there a function argument list right after the cursor" + // gave a bogus result. + clangd::CodeCompleteOptions Opts; + Opts.EnableSnippets = true; + // The whitespace in this testcase is important! + std::string Code = R"cpp( +void waldo(int); + +int main() +{ + wal^ + + + // ( ) +} + )cpp"; + EXPECT_THAT( + completions(Code, {}, Opts).Completions, + Contains(AllOf(labeled("waldo(int)"), snippetSuffix("(${1:int})")))); +} + TEST(CompletionTest, NoCrashDueToMacroOrdering) { EXPECT_THAT(completions(R"cpp( #define ECHO(X) X