Skip to content

Commit bdc6b6e

Browse files
committed
[clangd] SelectionTree treats TranslationUnitDecl (mostly) consistently with other containers.
Summary: Previously TranslationUnitDecl would never be selected. This means root() is never null, and returns a reference. commonAncestor() is in principle never null also, but returning TUDecl here requires tweaks to be careful not to traverse it (this was already possible when selecting multiple top-level decls, and there are associated bugs!) Instead, never allow commonAncestor() to return TUDecl, return null instead. Reviewers: hokein Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D65101 llvm-svn: 366893
1 parent aaad1a8 commit bdc6b6e

File tree

5 files changed

+48
-35
lines changed

5 files changed

+48
-35
lines changed

clang-tools-extra/clangd/Selection.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,14 @@ class SelectionVisitor : public RecursiveASTVisitor<SelectionVisitor> {
103103
V.TraverseAST(AST);
104104
assert(V.Stack.size() == 1 && "Unpaired push/pop?");
105105
assert(V.Stack.top() == &V.Nodes.front());
106-
if (V.Nodes.size() == 1) // TUDecl, but no nodes under it.
107-
V.Nodes.clear();
106+
// We selected TUDecl if characters were unclaimed (or the file is empty).
107+
if (V.Nodes.size() == 1 || V.Claimed.add(Begin, End)) {
108+
StringRef FileContent = AST.getSourceManager().getBufferData(File);
109+
// Don't require the trailing newlines to be selected.
110+
bool SelectedAll = Begin == 0 && End >= FileContent.rtrim().size();
111+
V.Stack.top()->Selected =
112+
SelectedAll ? SelectionTree::Complete : SelectionTree::Partial;
113+
}
108114
return std::move(V.Nodes);
109115
}
110116

@@ -424,12 +430,13 @@ SelectionTree::SelectionTree(ASTContext &AST, unsigned Offset)
424430
: SelectionTree(AST, Offset, Offset) {}
425431

426432
const Node *SelectionTree::commonAncestor() const {
427-
if (!Root)
428-
return nullptr;
429433
const Node *Ancestor = Root;
430434
while (Ancestor->Children.size() == 1 && !Ancestor->Selected)
431435
Ancestor = Ancestor->Children.front();
432-
return Ancestor;
436+
// Returning nullptr here is a bit unprincipled, but it makes the API safer:
437+
// the TranslationUnitDecl contains all of the preamble, so traversing it is a
438+
// performance cliff. Callers can check for null and use root() if they want.
439+
return Ancestor != Root ? Ancestor : nullptr;
433440
}
434441

435442
const DeclContext& SelectionTree::Node::getDeclContext() const {

clang-tools-extra/clangd/Selection.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ class ParsedAST;
5454
// - if you want to traverse the selected nodes, they are all under
5555
// commonAncestor() in the tree.
5656
//
57+
// SelectionTree tries to behave sensibly in the presence of macros, but does
58+
// not model any preprocessor concepts: the output is a subset of the AST.
59+
// Currently comments, directives etc are treated as part of the lexically
60+
// containing AST node, (though we may want to change this in future).
61+
//
5762
// The SelectionTree owns the Node structures, but the ASTNode attributes
5863
// point back into the AST it was constructed with.
5964
class SelectionTree {
@@ -100,11 +105,11 @@ class SelectionTree {
100105
std::string kind() const;
101106
};
102107
// The most specific common ancestor of all the selected nodes.
103-
// If there is no selection, this is nullptr.
108+
// Returns nullptr if the common ancestor is the root.
109+
// (This is to avoid accidentally traversing the TUDecl and thus preamble).
104110
const Node *commonAncestor() const;
105111
// The selection node corresponding to TranslationUnitDecl.
106-
// If there is no selection, this is nullptr.
107-
const Node *root() const { return Root; }
112+
const Node &root() const { return *Root; }
108113

109114
private:
110115
std::deque<Node> Nodes; // Stable-pointer storage.
@@ -114,10 +119,7 @@ class SelectionTree {
114119
void print(llvm::raw_ostream &OS, const Node &N, int Indent) const;
115120
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
116121
const SelectionTree &T) {
117-
if (auto R = T.root())
118-
T.print(OS, *R, 0);
119-
else
120-
OS << "(empty selection)\n";
122+
T.print(OS, T.root(), 1);
121123
return OS;
122124
}
123125
};

clang-tools-extra/clangd/refactor/tweaks/DumpAST.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,7 @@ class ShowSelectionTree : public Tweak {
8484
public:
8585
const char *id() const override final;
8686

87-
bool prepare(const Selection &Inputs) override {
88-
return Inputs.ASTSelection.root() != nullptr;
89-
}
87+
bool prepare(const Selection &Inputs) override { return true; }
9088
Expected<Effect> apply(const Selection &Inputs) override {
9189
return Effect::showMessage(llvm::to_string(Inputs.ASTSelection));
9290
}

clang-tools-extra/clangd/unittests/SelectionTests.cpp

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "Selection.h"
1010
#include "SourceCode.h"
1111
#include "TestTU.h"
12+
#include "clang/AST/Decl.h"
13+
#include "llvm/Support/Casting.h"
1214
#include "gmock/gmock.h"
1315
#include "gtest/gtest.h"
1416

@@ -40,6 +42,8 @@ Range nodeRange(const SelectionTree::Node *N, ParsedAST &AST) {
4042
const SourceManager &SM = AST.getSourceManager();
4143
const LangOptions &LangOpts = AST.getASTContext().getLangOpts();
4244
StringRef Buffer = SM.getBufferData(SM.getMainFileID());
45+
if (llvm::isa_and_nonnull<TranslationUnitDecl>(N->ASTNode.get<Decl>()))
46+
return Range{Position{}, offsetToPosition(Buffer, Buffer.size())};
4347
auto FileRange =
4448
toHalfOpenFileRange(SM, LangOpts, N->ASTNode.getSourceRange());
4549
assert(FileRange && "We should be able to get the File Range");
@@ -53,7 +57,7 @@ std::string nodeKind(const SelectionTree::Node *N) {
5357
}
5458

5559
std::vector<const SelectionTree::Node *> allNodes(const SelectionTree &T) {
56-
std::vector<const SelectionTree::Node *> Result = {T.root()};
60+
std::vector<const SelectionTree::Node *> Result = {&T.root()};
5761
for (unsigned I = 0; I < Result.size(); ++I) {
5862
const SelectionTree::Node *N = Result[I];
5963
Result.insert(Result.end(), N->Children.begin(), N->Children.end());
@@ -63,16 +67,16 @@ std::vector<const SelectionTree::Node *> allNodes(const SelectionTree &T) {
6367

6468
// Returns true if Common is a descendent of Root.
6569
// Verifies nothing is selected above Common.
66-
bool verifyCommonAncestor(const SelectionTree::Node *Root,
70+
bool verifyCommonAncestor(const SelectionTree::Node &Root,
6771
const SelectionTree::Node *Common,
6872
StringRef MarkedCode) {
69-
if (Root == Common)
73+
if (&Root == Common)
7074
return true;
71-
if (Root->Selected)
75+
if (Root.Selected)
7276
ADD_FAILURE() << "Selected nodes outside common ancestor\n" << MarkedCode;
7377
bool Seen = false;
74-
for (const SelectionTree::Node *Child : Root->Children)
75-
if (verifyCommonAncestor(Child, Common, MarkedCode)) {
78+
for (const SelectionTree::Node *Child : Root.Children)
79+
if (verifyCommonAncestor(*Child, Common, MarkedCode)) {
7680
if (Seen)
7781
ADD_FAILURE() << "Saw common ancestor twice\n" << MarkedCode;
7882
Seen = true;
@@ -239,6 +243,9 @@ TEST(SelectionTest, CommonAncestor) {
239243
{"int x = 42;^", nullptr},
240244
{"int x = 42^;", nullptr},
241245

246+
// Common ancestor is logically TUDecl, but we never return that.
247+
{"^int x; int y;^", nullptr},
248+
242249
// Node types that have caused problems in the past.
243250
{"template <typename T> void foo() { [[^T]] t; }",
244251
"TemplateTypeParmTypeLoc"},
@@ -256,18 +263,17 @@ TEST(SelectionTest, CommonAncestor) {
256263
Annotations Test(C.Code);
257264
auto AST = TestTU::withCode(Test.code()).build();
258265
auto T = makeSelectionTree(C.Code, AST);
266+
EXPECT_EQ("TranslationUnitDecl", nodeKind(&T.root())) << C.Code;
259267

260268
if (Test.ranges().empty()) {
261269
// If no [[range]] is marked in the example, there should be no selection.
262270
EXPECT_FALSE(T.commonAncestor()) << C.Code << "\n" << T;
263-
EXPECT_FALSE(T.root()) << C.Code << "\n" << T;
264271
} else {
265-
// If there is an expected selection, both common ancestor and root
266-
// should exist with the appropriate node types in them.
272+
// If there is an expected selection, common ancestor should exist
273+
// with the appropriate node type.
267274
EXPECT_EQ(C.CommonAncestorKind, nodeKind(T.commonAncestor()))
268275
<< C.Code << "\n"
269276
<< T;
270-
EXPECT_EQ("TranslationUnitDecl", nodeKind(T.root())) << C.Code;
271277
// Convert the reported common ancestor to a range and verify it.
272278
EXPECT_EQ(nodeRange(T.commonAncestor(), AST), Test.range())
273279
<< C.Code << "\n"
@@ -316,10 +322,10 @@ TEST(SelectionTest, Selected) {
316322
void foo(^$C[[unique_ptr<$C[[unique_ptr<$C[[int]]>]]>]]^ a) {}
317323
)cpp",
318324
R"cpp(int a = [[5 >^> 1]];)cpp",
319-
R"cpp(
325+
R"cpp([[
320326
#define ECHO(X) X
321327
ECHO(EC^HO([[$C[[int]]) EC^HO(a]]));
322-
)cpp",
328+
]])cpp",
323329
};
324330
for (const char *C : Cases) {
325331
Annotations Test(C);

clang-tools-extra/clangd/unittests/TweakTests.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -266,16 +266,16 @@ TEST(TweakTest, ShowSelectionTree) {
266266
llvm::StringLiteral ID = "ShowSelectionTree";
267267

268268
checkAvailable(ID, "^int f^oo() { re^turn 2 ^+ 2; }");
269-
checkNotAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }");
269+
checkAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }");
270270

271271
const char *Input = "int fcall(int); int x = fca[[ll(2 +]]2);";
272-
const char *Output = R"(TranslationUnitDecl
273-
VarDecl int x = fcall(2 + 2)
274-
.CallExpr fcall(2 + 2)
275-
ImplicitCastExpr fcall
276-
.DeclRefExpr fcall
277-
.BinaryOperator 2 + 2
278-
*IntegerLiteral 2
272+
const char *Output = R"( TranslationUnitDecl
273+
VarDecl int x = fcall(2 + 2)
274+
.CallExpr fcall(2 + 2)
275+
ImplicitCastExpr fcall
276+
.DeclRefExpr fcall
277+
.BinaryOperator 2 + 2
278+
*IntegerLiteral 2
279279
)";
280280
EXPECT_EQ(Output, getMessage(ID, Input));
281281
}

0 commit comments

Comments
 (0)