Index: clangd/Selection.h =================================================================== --- clangd/Selection.h +++ clangd/Selection.h @@ -93,6 +93,8 @@ ast_type_traits::DynTypedNode ASTNode; // The extent to which this node is covered by the selection. Selection Selected; + // The DeclContext that is the lexical parent of this node. + const DeclContext *lexicalContext() const; }; // The most specific common ancestor of all the selected nodes. Index: clangd/Selection.cpp =================================================================== --- clangd/Selection.cpp +++ clangd/Selection.cpp @@ -58,7 +58,7 @@ bool TraverseTypeLoc(TypeLoc X) { return traverseNode(&X, [&] { return Base::TraverseTypeLoc(X); }); } - bool TraverseTypeNestedNameSpecifierLoc(NestedNameSpecifierLoc X) { + bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc X) { return traverseNode( &X, [&] { return Base::TraverseNestedNameSpecifierLoc(X); }); } @@ -297,5 +297,14 @@ } } +const DeclContext* SelectionTree::Node::lexicalContext() const { + if (!Parent) // Must be TUDecl. + return cast(ASTNode.get()); + for (const Node *N = Parent; N; N = N->Parent) + if (auto *DC = dyn_cast_or_null(N->ASTNode.get())) + return DC; + llvm_unreachable("No DeclContext in parent chain!"); +} + } // namespace clangd } // namespace clang Index: clangd/refactor/tweaks/CMakeLists.txt =================================================================== --- clangd/refactor/tweaks/CMakeLists.txt +++ clangd/refactor/tweaks/CMakeLists.txt @@ -13,6 +13,7 @@ # clangd/tool/CMakeLists.txt for an example. add_clang_library(clangDaemonTweaks OBJECT SwapIfBranches.cpp + QualifyName.cpp LINK_LIBS clangAST Index: clangd/refactor/tweaks/QualifyName.cpp =================================================================== --- /dev/null +++ clangd/refactor/tweaks/QualifyName.cpp @@ -0,0 +1,180 @@ +//===--- QualifyName.cpp -----------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "AST.h" +#include "ClangdUnit.h" +#include "Logger.h" +#include "SourceCode.h" +#include "refactor/Tweak.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/TypeLoc.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { +namespace { +// TODO: this tweak extracts references to names from the selection in an +// efficient way. This should be extracted somewhere common. + +// Describes the qualifiers implied by a reference to a name in the source code. +// This check may attempt to make these explicit. +struct ImpliedQualifier { + // The position immediately before the name and any written qualifiers. + SourceLocation Begin; + // The scope that was implied, that contains the first qualifier or the name. + const DeclContext *Implied; +}; + +// Returns the qualifier implied before an already-qualified name. e.g. +// namespace a::b::c { int D; } +// using namespace a; +// int X = b::c::D; +// ^^^^^^ +// Here a:: could be inserted. It's the enclosing context of namespace b, +// which is named by the first section of the nested name specifier. +llvm::Optional +impliedQualifier(NestedNameSpecifierLoc L) { + const auto* NNS = L.getNestedNameSpecifier(); + // Navigate to the unqualified part. (e.g. b:: in b::c::). + while (auto Prefix = NNS->getPrefix()) + NNS = Prefix; + // Get the enclosing context of whatever NNS refers to. + const DeclContext *DC = nullptr; + switch (NNS->getKind()) { + case clang::NestedNameSpecifier::Namespace: + DC = NNS->getAsNamespace()->getDeclContext(); + break; + case clang::NestedNameSpecifier::NamespaceAlias: + DC = NNS->getAsNamespaceAlias()->getDeclContext(); + break; + case clang::NestedNameSpecifier::TypeSpec: + case clang::NestedNameSpecifier::TypeSpecWithTemplate: { + auto *T = NNS->getAsType(); + if (auto *TT = dyn_cast(T)) + DC = TT->getDecl()->getDeclContext(); + break; + } + case clang::NestedNameSpecifier::Identifier: + case clang::NestedNameSpecifier::Super: + case clang::NestedNameSpecifier::Global: + break; + } + // The enclosing context is the qualifier to be inserted. + if (!DC) + return llvm::None; + return ImpliedQualifier{L.getBeginLoc(), DC}; +} + +// Returns the qualifier to be inserted before a name represented as N. +// There are two cases: +// N is qualified: defer to requiredQualifier(NNSLoc above), it may need more. +// N is unqualified: return the DeclContext of the decl it refers to. +llvm::Optional +impliedQualifier(ast_type_traits::DynTypedNode N, + ast_type_traits::DynTypedNode *Parent) { + if (auto *S = N.get()) { + if (auto *DRE = dyn_cast(S)) { + if (auto Loc = DRE->getQualifierLoc()) + return impliedQualifier(Loc); + return ImpliedQualifier{DRE->getLocation(), + DRE->getDecl()->getDeclContext()}; + } + } + if (auto *D = N.get()) { + if (auto *U = dyn_cast(D)) + return impliedQualifier(U->getQualifierLoc()); + if (auto *U = dyn_cast(D)) // FIXME: selection bug? + return impliedQualifier(U->getUsingDecl()->getQualifierLoc()); + } + if (auto *TL = N.get()) { + // Qualifiers are stored in the ElaboratedType in the immediate parent. + if (Parent) + if (auto *PTL = Parent->get()) + if (auto ET = PTL->getAs()) + if (auto Qualifier = ET.getQualifierLoc()) + return impliedQualifier(Qualifier); + // We hit this case if the cursor is on the qualifiers themselves. + if (auto ET = TL->getAs()) { + if (auto Qualifier = ET.getQualifierLoc()) + return impliedQualifier(Qualifier); + return llvm::None; + } + // Remaining cases handle unqualified names. + if (auto TT = TL->getAs()) + return ImpliedQualifier{TT.getNameLoc(), TT.getDecl()->getDeclContext()}; + if (auto TT = TL->getAs()) { + if (auto *TD = TT.getTypePtr()->getTemplateName().getAsTemplateDecl()) + return ImpliedQualifier{TT.getTemplateNameLoc(), TD->getDeclContext()}; + return llvm::None; + } + if (auto TT = TL->getAs()) + return ImpliedQualifier{TT.getNameLoc(), + TT.getTypedefNameDecl()->getDeclContext()}; + } + return llvm::None; +} + +/// Fully qualifies a name under a cursor. +/// Before: +/// using namespace std; +/// ^vector foo; +/// After: +/// std::vector foo; +class QualifyName : public Tweak { +public: + const char *id() const override final; + + bool prepare(const Selection &Inputs) override { + llvm::Optional Qualifier; + const DeclContext *Enclosing = nullptr; + for (const auto *Node = Inputs.ASTSelection.commonAncestor(); + Node != nullptr; Node = Node->Parent) { + if (auto Q = impliedQualifier( + Node->ASTNode, Node->Parent ? &Node->Parent->ASTNode : nullptr)) { + Qualifier = std::move(Q); + Enclosing = Node->lexicalContext(); + break; + } + } + if (!Qualifier) + return false; + // Traverse upward, printing the context until reaching an enclosing one. + for (const DeclContext *DC = Qualifier->Implied; DC; DC = DC->getParent()) { + if (!DC->isTransparentContext() && !DC->isNamespace() && + !DC->isTranslationUnit()) // Skip hard cases. + return false; + } + // Don't add prefixes of the current namespace. + if (Qualifier->Implied->Encloses(Enclosing)) + return false; + this->InsertLoc = Qualifier->Begin; + this->Qualifier = printNamespaceScope(*Qualifier->Implied); + return !this->Qualifier.empty(); + } + + std::string title() const override { + return llvm::formatv("Add '{0}' qualifier", Qualifier); + } + + Expected apply(const Selection &Inputs) override { + return tooling::Replacements(tooling::Replacement( + Inputs.AST.getASTContext().getSourceManager(), InsertLoc, 0, + Qualifier)); + } + +private: + SourceLocation InsertLoc; + std::string Qualifier; +}; + +REGISTER_TWEAK(QualifyName); + +} // namespace +} // namespace clangd +} // namespace clang Index: unittests/clangd/SelectionTests.cpp =================================================================== --- unittests/clangd/SelectionTests.cpp +++ unittests/clangd/SelectionTests.cpp @@ -239,6 +239,17 @@ } } +TEST(SelectionTest, Context) { + const char *C = "namespace a { int ^x; }"; + Annotations Test(C); + auto AST = TestTU::withCode(Test.code()).build(); + auto T = makeSelectionTree(C, AST); + + ASSERT_TRUE(T.commonAncestor()); + EXPECT_EQ(T.commonAncestor()->lexicalContext(), + findDecl(AST, "a::x").getLexicalDeclContext()); +} + } // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/TweakTests.cpp =================================================================== --- unittests/clangd/TweakTests.cpp +++ unittests/clangd/TweakTests.cpp @@ -185,6 +185,62 @@ )cpp"); } +TEST(TweakTest, QualifyName) { + llvm::StringLiteral ID = "QualifyName"; + + const char *Input = R"cpp( + namespace a { namespace b { namespace c { int D; } } } + using namespace a; + int X = ^b::c::D; + )cpp"; + const char *Output = R"cpp( + namespace a { namespace b { namespace c { int D; } } } + using namespace a; + int X = a::b::c::D; + )cpp"; + checkTransform(ID, Input, Output); + + Input = R"cpp( + namespace std { template class vector; } + using namespace std; + vector<^vector>* v; + )cpp"; + // FIXME: Outer vector is qualified rather than inner, because Selection + // doesn't include located template parameters. + Output = R"cpp( + namespace std { template class vector; } + using namespace std; + std::vector>* v; + )cpp"; + checkTransform(ID, Input, Output); + + checkAvailable(ID, R"cpp( + namespace a { namespace b { namespace c { int D; class E{}; } } } + using namespace a; + ^using ^b::^c::^D; + int X = ^b::^c::^D; + ^b::^c::^E instance; + )cpp"); + checkNotAvailable(ID, R"cpp( + namespace a { namespace b { namespace c { int D; class E{}; } } } + using namespace a; + using b::c::D^; + int ^X = b::c::D; + b::c::E ^instance; + )cpp"); + checkNotAvailable(ID, R"cpp( + namespace a { namespace b { namespace c { int D; class E{}; } } } + namespace { int x; } + using namespace a; + // Don't qualify fully-qualified things. + using ^a::^b::c::^D; + int y = ^x; + + // Don't add a:: while in namespace a. + namespace a { namespace z { int X = ^b::c::^D; } } + )cpp"); +} + } // namespace } // namespace clangd } // namespace clang