sammccall updated this revision to Diff 185690.
sammccall added a comment.
Herald added a project: clang.
Update matching to use Inputs.ASTSelection.
Cover some more cases of names (I think?)
Handle under-qualified names as well as unqualified ones.
The main benefit of this is it's a step closer to extracting all references to
qualified/unqualified names from the selection.
Repository:
rCTE Clang Tools Extra
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D56610/new/
https://reviews.llvm.org/D56610
Files:
clangd/Selection.cpp
clangd/Selection.h
clangd/refactor/tweaks/CMakeLists.txt
clangd/refactor/tweaks/QualifyName.cpp
unittests/clangd/SelectionTests.cpp
unittests/clangd/TweakTests.cpp
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 <typename> class vector; }
+ using namespace std;
+ vector<^vector<int>>* v;
+ )cpp";
+ // FIXME: Outer vector is qualified rather than inner, because Selection
+ // doesn't include located template parameters.
+ Output = R"cpp(
+ namespace std { template <typename> class vector; }
+ using namespace std;
+ std::vector<vector<int>>* 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
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: 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>
+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<TagType>(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>
+impliedQualifier(ast_type_traits::DynTypedNode N,
+ ast_type_traits::DynTypedNode *Parent) {
+ if (auto *S = N.get<Stmt>()) {
+ if (auto *DRE = dyn_cast<DeclRefExpr>(S)) {
+ if (auto Loc = DRE->getQualifierLoc())
+ return impliedQualifier(Loc);
+ return ImpliedQualifier{DRE->getLocation(),
+ DRE->getDecl()->getDeclContext()};
+ }
+ }
+ if (auto *D = N.get<Decl>()) {
+ if (auto *U = dyn_cast<UsingDecl>(D))
+ return impliedQualifier(U->getQualifierLoc());
+ if (auto *U = dyn_cast<UsingShadowDecl>(D)) // FIXME: selection bug?
+ return impliedQualifier(U->getUsingDecl()->getQualifierLoc());
+ }
+ if (auto *TL = N.get<TypeLoc>()) {
+ // Qualifiers are stored in the ElaboratedType in the immediate parent.
+ if (Parent)
+ if (auto *PTL = Parent->get<TypeLoc>())
+ if (auto ET = PTL->getAs<ElaboratedTypeLoc>())
+ 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<ElaboratedTypeLoc>()) {
+ if (auto Qualifier = ET.getQualifierLoc())
+ return impliedQualifier(Qualifier);
+ return llvm::None;
+ }
+ // Remaining cases handle unqualified names.
+ if (auto TT = TL->getAs<TagTypeLoc>())
+ return ImpliedQualifier{TT.getNameLoc(), TT.getDecl()->getDeclContext()};
+ if (auto TT = TL->getAs<TemplateSpecializationTypeLoc>()) {
+ if (auto *TD = TT.getTypePtr()->getTemplateName().getAsTemplateDecl())
+ return ImpliedQualifier{TT.getTemplateNameLoc(), TD->getDeclContext()};
+ return llvm::None;
+ }
+ if (auto TT = TL->getAs<TypedefTypeLoc>())
+ return ImpliedQualifier{TT.getNameLoc(),
+ TT.getTypedefNameDecl()->getDeclContext()};
+ }
+ return llvm::None;
+}
+
+/// Fully qualifies a name under a cursor.
+/// Before:
+/// using namespace std;
+/// ^vector<int> foo;
+/// After:
+/// std::vector<int> foo;
+class QualifyName : public Tweak {
+public:
+ const char *id() const override final;
+
+ bool prepare(const Selection &Inputs) override {
+ llvm::Optional<ImpliedQualifier> 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<tooling::Replacements> 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: 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/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<DeclContext>(ASTNode.get<Decl>());
+ for (const Node *N = Parent; N; N = N->Parent)
+ if (auto *DC = dyn_cast_or_null<DeclContext>(N->ASTNode.get<Decl>()))
+ return DC;
+ llvm_unreachable("No DeclContext in parent chain!");
+}
+
} // namespace clangd
} // namespace clang
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits