[clangd] Navigate go-to-definition through forwarding wrappers to the constructor (#199480)
When the user invokes **Go to Definition** on a call like `std::make_unique<T>(args...)` or `std::make_shared<T>(args...)`, surface the constructor of `T` that is actually invoked inside the wrapper, alongside the wrapper itself. The constructor is added before the wrapper so LSP clients that auto-jump to the first target land on it; clients that present a menu still let the user reach the wrapper. This is the forward-direction counterpart to the find-references work in #169742 (clangd/clangd#716): the same `isLikelyForwardingFunction` + `searchConstructorsInForwardingFunction` machinery, applied to `locateASTReferent`.
This commit is contained in:
parent
10755f42bb
commit
f0bbae6641
6 changed files with 163 additions and 38 deletions
|
|
@ -1115,5 +1115,21 @@ searchConstructorsInForwardingFunction(const FunctionDecl *FD) {
|
|||
return Result;
|
||||
}
|
||||
|
||||
ArrayRef<const CXXConstructorDecl *>
|
||||
getForwardedConstructors(const FunctionDecl *FD,
|
||||
ForwardingToConstructorCache &Cache) {
|
||||
assert(FD && "FD must not be null");
|
||||
if (!FD->isTemplateInstantiation())
|
||||
return {};
|
||||
if (auto It = Cache.find(FD); It != Cache.end())
|
||||
return It->getSecond();
|
||||
const auto *PT = FD->getPrimaryTemplate();
|
||||
if (!PT || !isLikelyForwardingFunction(PT))
|
||||
return {};
|
||||
auto Inserted =
|
||||
Cache.try_emplace(FD, searchConstructorsInForwardingFunction(FD));
|
||||
return Inserted.first->getSecond();
|
||||
}
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@
|
|||
#include "clang/AST/TypeLoc.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Lex/MacroInfo.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
|
@ -261,6 +264,24 @@ bool isLikelyForwardingFunction(const FunctionTemplateDecl *FT);
|
|||
SmallVector<const CXXConstructorDecl *, 1>
|
||||
searchConstructorsInForwardingFunction(const FunctionDecl *FD);
|
||||
|
||||
/// Cache mapping forwarding function instantiations (e.g. `make_unique<T>`)
|
||||
/// to the constructors they ultimately call.
|
||||
using ForwardingToConstructorCache =
|
||||
llvm::DenseMap<const FunctionDecl *,
|
||||
SmallVector<const CXXConstructorDecl *, 1>>;
|
||||
|
||||
/// Returns the constructors that FD forwards to, if FD is a template
|
||||
/// instantiation of a likely forwarding function (see
|
||||
/// `isLikelyForwardingFunction`). Results are memoized in `Cache`. Returns
|
||||
/// an empty range for non-forwarding functions; the cache is only populated
|
||||
/// for true forwarding instantiations to keep its size bounded.
|
||||
///
|
||||
/// FD must not be null. The returned ArrayRef is owned by `Cache`; consume
|
||||
/// it before any other call that may insert into the same cache.
|
||||
ArrayRef<const CXXConstructorDecl *>
|
||||
getForwardedConstructors(const FunctionDecl *FD,
|
||||
ForwardingToConstructorCache &Cache);
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
|
|
|
|||
|
|
@ -196,6 +196,22 @@ getDeclAtPosition(ParsedAST &AST, SourceLocation Pos, DeclRelationSet Relations,
|
|||
return Result;
|
||||
}
|
||||
|
||||
// Returns the deepest CallExpr whose selection-tree commonAncestor is the call
|
||||
// itself at `Loc` (i.e. the cursor lands on the call's parens area, not on a
|
||||
// child like the callee identifier or an argument). Returns null otherwise.
|
||||
const CallExpr *findEnclosingCallAt(ParsedAST &AST, SourceLocation Loc) {
|
||||
unsigned Offset = AST.getSourceManager().getDecomposedSpellingLoc(Loc).second;
|
||||
const CallExpr *Found = nullptr;
|
||||
SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), Offset,
|
||||
Offset, [&](SelectionTree ST) {
|
||||
if (const SelectionTree::Node *N =
|
||||
ST.commonAncestor())
|
||||
Found = N->ASTNode.get<CallExpr>();
|
||||
return true;
|
||||
});
|
||||
return Found;
|
||||
}
|
||||
|
||||
// Expects Loc to be a SpellingLocation, will bail out otherwise as it can't
|
||||
// figure out a filename.
|
||||
std::optional<Location> makeLocation(const ASTContext &AST, SourceLocation Loc,
|
||||
|
|
@ -419,6 +435,31 @@ locateASTReferent(SourceLocation CurLoc, const syntax::Token *TouchedIdentifier,
|
|||
AST.getASTContext(), nameLocation(*Def, SM), MainFilePath);
|
||||
};
|
||||
|
||||
// Special case: if the cursor lands directly on a call expression (i.e.
|
||||
// its enclosing SelectionTree node is the CallExpr itself, not the callee
|
||||
// identifier or an argument), and the call invokes a forwarding wrapper
|
||||
// such as `std::make_unique<T>(...)`, navigate to the constructor of `T`
|
||||
// that the wrapper ultimately calls. This mirrors the existing
|
||||
// constructor-call behaviour: `Abc^()` jumps to the constructor while
|
||||
// `A^bc()` jumps to the type. The hook does not fire when the cursor is
|
||||
// on the wrapper's identifier; that path continues to navigate to the
|
||||
// wrapper itself via the candidate loop below.
|
||||
if (const auto *CE = findEnclosingCallAt(AST, CurLoc)) {
|
||||
if (const auto *Callee = CE->getDirectCallee()) {
|
||||
llvm::SmallPtrSet<const CXXConstructorDecl *, 1> Seen;
|
||||
for (const auto *Ctor :
|
||||
getForwardedConstructors(Callee, AST.ForwardingToConstructorCache))
|
||||
if (Seen.insert(Ctor).second) {
|
||||
LocateASTReferentMetric.record(1, "forwarded-constructor");
|
||||
AddResultDecl(Ctor);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Result.empty()) {
|
||||
enhanceLocatedSymbolsFromIndex(Result, Index, MainFilePath);
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Emit all symbol locations (declaration or definition) from AST.
|
||||
DeclRelationSet Relations =
|
||||
DeclRelation::TemplatePattern | DeclRelation::Alias;
|
||||
|
|
@ -966,27 +1007,12 @@ public:
|
|||
bool forwardsToConstructor(const Decl *D) {
|
||||
if (TargetConstructors.empty())
|
||||
return false;
|
||||
auto *FD = llvm::dyn_cast<clang::FunctionDecl>(D);
|
||||
if (FD == nullptr || !FD->isTemplateInstantiation())
|
||||
const auto *FD = llvm::dyn_cast<clang::FunctionDecl>(D);
|
||||
if (!FD)
|
||||
return false;
|
||||
|
||||
SmallVector<const CXXConstructorDecl *, 1> *Constructors = nullptr;
|
||||
if (auto Entry = AST.ForwardingToConstructorCache.find(FD);
|
||||
Entry != AST.ForwardingToConstructorCache.end())
|
||||
Constructors = &Entry->getSecond();
|
||||
if (Constructors == nullptr) {
|
||||
if (auto *PT = FD->getPrimaryTemplate();
|
||||
PT == nullptr || !isLikelyForwardingFunction(PT))
|
||||
return false;
|
||||
|
||||
SmallVector<const CXXConstructorDecl *, 1> FoundConstructors =
|
||||
searchConstructorsInForwardingFunction(FD);
|
||||
auto Iter = AST.ForwardingToConstructorCache.try_emplace(
|
||||
FD, std::move(FoundConstructors));
|
||||
Constructors = &Iter.first->getSecond();
|
||||
}
|
||||
for (auto *Constructor : *Constructors)
|
||||
if (TargetConstructors.contains(Constructor))
|
||||
for (const auto *Ctor :
|
||||
getForwardedConstructors(FD, AST.ForwardingToConstructorCache))
|
||||
if (TargetConstructors.contains(Ctor))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -573,23 +573,12 @@ SymbolCollector::getRefContainer(const Decl *Enclosing,
|
|||
return Enclosing;
|
||||
}
|
||||
|
||||
SmallVector<const CXXConstructorDecl *, 1>
|
||||
ArrayRef<const CXXConstructorDecl *>
|
||||
SymbolCollector::findIndirectConstructors(const Decl *D) {
|
||||
auto *FD = llvm::dyn_cast<clang::FunctionDecl>(D);
|
||||
if (FD == nullptr || !FD->isTemplateInstantiation())
|
||||
const auto *FD = llvm::dyn_cast<clang::FunctionDecl>(D);
|
||||
if (!FD)
|
||||
return {};
|
||||
if (auto Entry = ForwardingToConstructorCache.find(FD);
|
||||
Entry != ForwardingToConstructorCache.end())
|
||||
return Entry->getSecond();
|
||||
if (auto *PT = FD->getPrimaryTemplate();
|
||||
PT == nullptr || !isLikelyForwardingFunction(PT))
|
||||
return {};
|
||||
|
||||
SmallVector<const CXXConstructorDecl *, 1> FoundConstructors =
|
||||
searchConstructorsInForwardingFunction(FD);
|
||||
auto Iter = ForwardingToConstructorCache.try_emplace(
|
||||
FD, std::move(FoundConstructors));
|
||||
return Iter.first->getSecond();
|
||||
return getForwardedConstructors(FD, ForwardingToConstructorCache);
|
||||
}
|
||||
|
||||
// Always return true to continue indexing.
|
||||
|
|
|
|||
|
|
@ -161,9 +161,9 @@ public:
|
|||
private:
|
||||
// If D is an instantiation of a likely forwarding function, return the
|
||||
// constructors it invokes so that we can record indirect references
|
||||
// to those as well.
|
||||
SmallVector<const CXXConstructorDecl *, 1>
|
||||
findIndirectConstructors(const Decl *D);
|
||||
// to those as well. The returned ArrayRef is owned by
|
||||
// `ForwardingToConstructorCache`.
|
||||
ArrayRef<const CXXConstructorDecl *> findIndirectConstructors(const Decl *D);
|
||||
|
||||
const Symbol *addDeclaration(const NamedDecl &, SymbolID,
|
||||
bool IsMainFileSymbol);
|
||||
|
|
|
|||
|
|
@ -2939,6 +2939,79 @@ TEST(FindReferences, TemplatedConstructorForwarding) {
|
|||
rangeIs(Main.range("ForwardedCaller2"))));
|
||||
}
|
||||
|
||||
TEST(LocateSymbol, ConstructorForwarding) {
|
||||
// Caret-on-paren of a forwarding wrapper navigates to the constructor it
|
||||
// ultimately invokes; caret-on-identifier still navigates to the wrapper.
|
||||
// This mirrors the existing direct-ctor behaviour (`Abc^()` -> ctor,
|
||||
// `A^bc()` -> type).
|
||||
Annotations Code(R"cpp(
|
||||
namespace std {
|
||||
template <class T> T &&forward(T &t);
|
||||
template <class T, class... Args>
|
||||
T *$MakeUnique[[make_unique]](Args &&...args) {
|
||||
return new T(std::forward<Args>(args)...);
|
||||
}
|
||||
template <class T, class... Args> T *make_unique2(Args &&...args) {
|
||||
return make_unique<T>(forward<Args>(args)...);
|
||||
}
|
||||
template <class T, class... Args> T *make_unique3(Args &&...args) {
|
||||
return make_unique2<T>(forward<Args>(args)...);
|
||||
}
|
||||
template <class T> struct shared_ptr {
|
||||
shared_ptr(T *) {}
|
||||
};
|
||||
template <class T, class... Args>
|
||||
shared_ptr<T> make_shared(Args &&...args) {
|
||||
return shared_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
}
|
||||
|
||||
// Non-forwarding template: a call to it should fall through to itself.
|
||||
template <class T> T *$Make[[make]]() { return nullptr; }
|
||||
|
||||
struct Test {
|
||||
$DefaultCtor[[Test]]() {}
|
||||
$IntCtor[[Test]](int) {}
|
||||
Test(const char *) {}
|
||||
};
|
||||
|
||||
int main() {
|
||||
// Caret on parens -> Test default ctor.
|
||||
auto a = std::make_unique<Test>$ParenMU^();
|
||||
// Caret on the wrapper identifier -> make_unique itself.
|
||||
auto b = std::ma$IdentMU^ke_unique<Test>();
|
||||
// make_shared works the same way; overload resolution picks Test(int).
|
||||
auto c = std::make_shared<Test>$MakeShared^(1);
|
||||
// Chained forwarding (three wrappers deep).
|
||||
auto d = std::make_unique3<Test>$Chained^();
|
||||
// Overload resolution inside the instantiated body picks Test(int).
|
||||
auto e = std::make_unique<Test>$Overload^(42);
|
||||
// Non-forwarding template call: no constructor target.
|
||||
auto f = make<Test>$NonFwd^();
|
||||
}
|
||||
)cpp");
|
||||
TestTU TU = TestTU::withCode(Code.code());
|
||||
auto AST = TU.build();
|
||||
|
||||
EXPECT_THAT(locateSymbolAt(AST, Code.point("ParenMU")),
|
||||
ElementsAre(sym("Test", Code.range("DefaultCtor"),
|
||||
Code.range("DefaultCtor"))));
|
||||
EXPECT_THAT(locateSymbolAt(AST, Code.point("IdentMU")),
|
||||
ElementsAre(sym("make_unique", Code.range("MakeUnique"),
|
||||
Code.range("MakeUnique"))));
|
||||
EXPECT_THAT(
|
||||
locateSymbolAt(AST, Code.point("MakeShared")),
|
||||
ElementsAre(sym("Test", Code.range("IntCtor"), Code.range("IntCtor"))));
|
||||
EXPECT_THAT(locateSymbolAt(AST, Code.point("Chained")),
|
||||
ElementsAre(sym("Test", Code.range("DefaultCtor"),
|
||||
Code.range("DefaultCtor"))));
|
||||
EXPECT_THAT(
|
||||
locateSymbolAt(AST, Code.point("Overload")),
|
||||
ElementsAre(sym("Test", Code.range("IntCtor"), Code.range("IntCtor"))));
|
||||
EXPECT_THAT(locateSymbolAt(AST, Code.point("NonFwd")),
|
||||
ElementsAre(sym("make", Code.range("Make"), Code.range("Make"))));
|
||||
}
|
||||
|
||||
TEST(GetNonLocalDeclRefs, All) {
|
||||
struct Case {
|
||||
llvm::StringRef AnnotatedCode;
|
||||
|
|
|
|||
Loading…
Reference in a new issue