Re-land [clangd] Elide even more checks in SelectionTree.

This reverts commit 1093b9f2e9.

Fix added for implicit-include case.
This commit is contained in:
Sam McCall 2022-01-17 15:13:27 +01:00
parent 192f8d9700
commit 4dedd82cc9
2 changed files with 103 additions and 18 deletions

View File

@ -274,22 +274,37 @@ public:
for (unsigned I = 0; I < Sel.size(); ++I) {
if (shouldIgnore(Sel[I]) || PPIgnored[I])
continue;
SpelledTokens.emplace_back();
Tok &S = SpelledTokens.back();
SelectedSpelled.emplace_back();
Tok &S = SelectedSpelled.back();
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;
}
MaybeSelectedExpanded = computeMaybeSelectedExpandedTokens(Buf);
}
// Test whether a consecutive range of tokens is selected.
// The tokens are taken from the expanded token stream.
SelectionTree::Selection
test(llvm::ArrayRef<syntax::Token> ExpandedTokens) const {
if (SpelledTokens.empty())
if (ExpandedTokens.empty())
return NoTokens;
if (SelectedSpelled.empty())
return SelectionTree::Unselected;
// Cheap (pointer) check whether any of the tokens could touch selection.
// In most cases, the node's overall source range touches ExpandedTokens,
// or we would have failed mayHit(). However now we're only considering
// the *unclaimed* spans of expanded tokens.
// This is a significant performance improvement when a lot of nodes
// surround the selection, including when generated by macros.
if (MaybeSelectedExpanded.empty() ||
&ExpandedTokens.front() > &MaybeSelectedExpanded.back() ||
&ExpandedTokens.back() < &MaybeSelectedExpanded.front()) {
return SelectionTree::Unselected;
}
SelectionTree::Selection Result = NoTokens;
while (!ExpandedTokens.empty()) {
// Take consecutive tokens from the same context together for efficiency.
@ -312,14 +327,14 @@ public:
// If it returns false, test() will return NoTokens or Unselected.
// If it returns true, test() may return any value.
bool mayHit(SourceRange R) const {
if (SpelledTokens.empty())
if (SelectedSpelled.empty() || MaybeSelectedExpanded.empty())
return false;
// If the node starts after the selection ends, it is not selected.
// Tokens a macro location might claim are >= its expansion start.
// So if the expansion start > last selected token, we can prune it.
// (This is particularly helpful for GTest's TEST macro).
if (auto B = offsetInSelFile(getExpansionStart(R.getBegin())))
if (*B > SpelledTokens.back().Offset)
if (*B > SelectedSpelled.back().Offset)
return false;
// If the node ends before the selection begins, it is not selected.
SourceLocation EndLoc = R.getEnd();
@ -328,12 +343,73 @@ public:
// In the rare case that the expansion range is a char range, EndLoc is
// ~one token too far to the right. We may fail to prune, that's OK.
if (auto E = offsetInSelFile(EndLoc))
if (*E < SpelledTokens.front().Offset)
if (*E < SelectedSpelled.front().Offset)
return false;
return true;
}
private:
// Plausible expanded tokens that might be affected by the selection.
// This is an overestimate, it may contain tokens that are not selected.
// The point is to allow cheap pruning in test()
llvm::ArrayRef<syntax::Token>
computeMaybeSelectedExpandedTokens(const syntax::TokenBuffer &Toks) {
if (SelectedSpelled.empty())
return {};
auto LastAffectedToken = [&](SourceLocation Loc) {
auto Offset = offsetInSelFile(Loc);
while (Loc.isValid() && !Offset) {
Loc = Loc.isMacroID() ? SM.getImmediateExpansionRange(Loc).getEnd()
: SM.getIncludeLoc(SM.getFileID(Loc));
Offset = offsetInSelFile(Loc);
}
return Offset;
};
auto FirstAffectedToken = [&](SourceLocation Loc) {
auto Offset = offsetInSelFile(Loc);
while (Loc.isValid() && !Offset) {
Loc = Loc.isMacroID() ? SM.getImmediateExpansionRange(Loc).getBegin()
: SM.getIncludeLoc(SM.getFileID(Loc));
Offset = offsetInSelFile(Loc);
}
return Offset;
};
const syntax::Token *Start = llvm::partition_point(
Toks.expandedTokens(),
[&, First = SelectedSpelled.front().Offset](const syntax::Token &Tok) {
if (Tok.kind() == tok::eof)
return false;
// Implausible if upperbound(Tok) < First.
if (auto Offset = LastAffectedToken(Tok.location()))
return *Offset < First;
// A prefix of the expanded tokens may be from an an implicit
// inclusion (e.g. preamble patch, or command-line -include).
return true;
});
bool EndInvalid = false;
const syntax::Token *End = std::partition_point(
Start, Toks.expandedTokens().end(),
[&, Last = SelectedSpelled.back().Offset](const syntax::Token &Tok) {
if (Tok.kind() == tok::eof)
return false;
// Plausible if lowerbound(Tok) <= Last.
if (auto Offset = FirstAffectedToken(Tok.location()))
return *Offset <= Last;
// Shouldn't happen: once we've seen tokens traceable to the main
// file, there shouldn't be any more implicit inclusions.
assert(false && "Expanded token could not be resolved to main file!");
EndInvalid = true;
return true; // conservatively assume this token can overlap
});
if (EndInvalid)
End = Toks.expandedTokens().end();
return llvm::makeArrayRef(Start, End);
}
// Hit-test a consecutive range of tokens from a single file ID.
SelectionTree::Selection
testChunk(FileID FID, llvm::ArrayRef<syntax::Token> Batch) const {
@ -389,19 +465,20 @@ private:
SelectionTree::Selection testTokenRange(unsigned Begin, unsigned End) const {
assert(Begin <= End);
// Outside the selection entirely?
if (End < SpelledTokens.front().Offset ||
Begin > SpelledTokens.back().Offset)
if (End < SelectedSpelled.front().Offset ||
Begin > SelectedSpelled.back().Offset)
return SelectionTree::Unselected;
// Compute range of tokens.
auto B = llvm::partition_point(
SpelledTokens, [&](const Tok &T) { return T.Offset < Begin; });
auto E = std::partition_point(
B, SpelledTokens.end(), [&](const Tok &T) { return T.Offset <= End; });
SelectedSpelled, [&](const Tok &T) { return T.Offset < Begin; });
auto E = std::partition_point(B, SelectedSpelled.end(), [&](const Tok &T) {
return T.Offset <= End;
});
// Aggregate selectedness of tokens in range.
bool ExtendsOutsideSelection = Begin < SpelledTokens.front().Offset ||
End > SpelledTokens.back().Offset;
bool ExtendsOutsideSelection = Begin < SelectedSpelled.front().Offset ||
End > SelectedSpelled.back().Offset;
SelectionTree::Selection Result =
ExtendsOutsideSelection ? SelectionTree::Unselected : NoTokens;
for (auto It = B; It != E; ++It)
@ -412,13 +489,13 @@ private:
// Is the token at `Offset` selected?
SelectionTree::Selection testToken(unsigned Offset) const {
// Outside the selection entirely?
if (Offset < SpelledTokens.front().Offset ||
Offset > SpelledTokens.back().Offset)
if (Offset < SelectedSpelled.front().Offset ||
Offset > SelectedSpelled.back().Offset)
return SelectionTree::Unselected;
// Find the token, if it exists.
auto It = llvm::partition_point(
SpelledTokens, [&](const Tok &T) { return T.Offset < Offset; });
if (It != SpelledTokens.end() && It->Offset == Offset)
SelectedSpelled, [&](const Tok &T) { return T.Offset < Offset; });
if (It != SelectedSpelled.end() && It->Offset == Offset)
return It->Selected;
return NoTokens;
}
@ -444,7 +521,8 @@ private:
unsigned Offset;
SelectionTree::Selection Selected;
};
std::vector<Tok> SpelledTokens;
std::vector<Tok> SelectedSpelled;
llvm::ArrayRef<syntax::Token> MaybeSelectedExpanded;
FileID SelFile;
SourceRange SelFileBounds;
const SourceManager &SM;

View File

@ -201,6 +201,13 @@ TEST(SelectionTest, CommonAncestor) {
)cpp",
nullptr,
},
{
R"cpp(
#define TARGET void foo()
[[TAR^GET{ return; }]]
)cpp",
"FunctionDecl",
},
{
R"cpp(
struct S { S(const char*); };