From 72f2fb1db4ea19b543265ceba67964174848a875 Mon Sep 17 00:00:00 2001 From: Sam McCall Date: Fri, 17 Jul 2020 12:03:24 +0200 Subject: [PATCH] [clangd] Exclude preprocessed-to-nothing tokens from selection This prevents selection of empty preprocessor entities (like #define directives, or text in disabled sections) creating a selection in the parent element. Summary: Based on D83508 by Aleksandr Platonov. Reviewers: ArcsinX, kadircet Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D84012 --- clang-tools-extra/clangd/Selection.cpp | 20 +++++++++++++---- .../clangd/unittests/SelectionTests.cpp | 25 +++++++++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp index e94a3ca..d1677e2 100644 --- a/clang-tools-extra/clangd/Selection.cpp +++ b/clang-tools-extra/clangd/Selection.cpp @@ -220,14 +220,26 @@ public: SelFirst, AllSpelledTokens.end(), [&](const syntax::Token &Tok) { return SM.getFileOffset(Tok.location()) < SelEnd; }); + auto Sel = llvm::makeArrayRef(SelFirst, SelLimit); + // Find which of these are preprocessed to nothing and should be ignored. + std::vector PPIgnored(Sel.size(), false); + for (const syntax::TokenBuffer::Expansion &X : + Buf.expansionsAffecting(Sel)) { + if (X.Expanded.empty()) { + for (const syntax::Token &Tok : X.Spelled) { + if (&Tok >= SelFirst && &Tok < SelLimit) + PPIgnored[&Tok - SelFirst] = true; + } + } + } // Precompute selectedness and offset for selected spelled tokens. - for (const syntax::Token *T = SelFirst; T < SelLimit; ++T) { - if (shouldIgnore(*T)) + for (unsigned I = 0; I < Sel.size(); ++I) { + if (shouldIgnore(Sel[I]) || PPIgnored[I]) continue; SpelledTokens.emplace_back(); Tok &S = SpelledTokens.back(); - S.Offset = SM.getFileOffset(T->location()); - if (S.Offset >= SelBegin && S.Offset + T->length() <= SelEnd) + S.Offset = SM.getFileOffset(Sel[I].location()); + if (S.Offset >= SelBegin && S.Offset + Sel[I].length() <= SelEnd) S.Selected = SelectionTree::Complete; else S.Selected = SelectionTree::Partial; diff --git a/clang-tools-extra/clangd/unittests/SelectionTests.cpp b/clang-tools-extra/clangd/unittests/SelectionTests.cpp index 051580b..6a9f587 100644 --- a/clang-tools-extra/clangd/unittests/SelectionTests.cpp +++ b/clang-tools-extra/clangd/unittests/SelectionTests.cpp @@ -177,13 +177,31 @@ TEST(SelectionTest, CommonAncestor) { { R"cpp( void foo(); - #define CALL_FUNCTION(X) X^()^ + #^define CALL_FUNCTION(X) X(^) void bar() { CALL_FUNCTION(foo); } )cpp", nullptr, }, { R"cpp( + void foo(); + #define CALL_FUNCTION(X) X() + void bar() { CALL_FUNCTION(foo^)^; } + )cpp", + nullptr, + }, + { + R"cpp( + namespace ns { + #if 0 + void fo^o() {} + #endif + } + )cpp", + nullptr, + }, + { + R"cpp( struct S { S(const char*); }; S [[s ^= "foo"]]; )cpp", @@ -388,7 +406,8 @@ TEST(SelectionTest, CommonAncestor) { void test(S2 s2) { s2[[-^>]]f(); } - )cpp", "DeclRefExpr"} // DeclRefExpr to the "operator->" method. + )cpp", + "DeclRefExpr"} // DeclRefExpr to the "operator->" method. }; for (const Case &C : Cases) { trace::TestTracer Tracer; @@ -538,7 +557,7 @@ TEST(SelectionTest, IncludedFile) { auto AST = TU.build(); auto T = makeSelectionTree(Case, AST); - EXPECT_EQ("WhileStmt", T.commonAncestor()->kind()); + EXPECT_EQ(nullptr, T.commonAncestor()); } TEST(SelectionTest, MacroArgExpansion) { -- 2.7.4