Index: include/clang/Tooling/Core/Lookup.h =================================================================== --- /dev/null +++ include/clang/Tooling/Core/Lookup.h @@ -0,0 +1,48 @@ +//===--- Lookup.h - Framework for clang refactoring tools --*- C++ -*------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines helper methods for clang tools performing name lookup. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_CORE_LOOKUP_H +#define LLVM_CLANG_TOOLING_CORE_LOOKUP_H + +#include "clang/Basic/LLVM.h" +#include + +namespace clang { + +class DeclContext; +class NamedDecl; +class NestedNameSpecifier; + +namespace tooling { + +/// Emulate a lookup to replace one nested name specifier with another using as +/// few additional namespace qualifications as possible. +/// +/// This does not perform a full C++ lookup so ADL will not work. +/// +/// \param Use The nested name to be replaced. +/// \param UseContext The context in which the nested name is contained. This +/// will be used to minimize namespace qualifications. +/// \param FromDecl The declaration to which the nested name points. +/// \param ReplacementString The replacement nested name. Should be qualified, +/// leading "::" is optional. +/// \returns The new name to be inserted in place of the current nested name. +std::string replaceNestedName(const NestedNameSpecifier *Use, + const DeclContext *UseContext, + const NamedDecl *FromDecl, + StringRef ReplacementString); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_CORE_LOOKUP_H Index: lib/Tooling/Core/CMakeLists.txt =================================================================== --- lib/Tooling/Core/CMakeLists.txt +++ lib/Tooling/Core/CMakeLists.txt @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS support) add_clang_library(clangToolingCore + Lookup.cpp Replacement.cpp LINK_LIBS Index: lib/Tooling/Core/Lookup.cpp =================================================================== --- /dev/null +++ lib/Tooling/Core/Lookup.cpp @@ -0,0 +1,106 @@ +//===--- Lookup.cpp - Framework for clang refactoring tools ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines helper methods for clang tools performing name lookup. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Core/Lookup.h" +#include "clang/AST/Decl.h" +using namespace clang; +using namespace clang::tooling; + +static bool isInsideDifferentNamespaceWithSameName(const DeclContext *DeclA, + const DeclContext *DeclB) { + while (true) { + // Look past non-namespaces on DeclA. + while (DeclA && !isa(DeclA)) + DeclA = DeclA->getParent(); + + // Look past non-namespaces on DeclB. + while (DeclB && !isa(DeclB)) + DeclB = DeclB->getParent(); + + // We hit the root, no namespace collision. + if (!DeclA || !DeclB) + return false; + + // Literally the same namespace, not a collision. + if (DeclA == DeclB) + return false; + + // Now check the names. If they match we have a different namespace with the + // same name. + if (cast(DeclA)->getDeclName() == + cast(DeclB)->getDeclName()) + return true; + + DeclA = DeclA->getParent(); + DeclB = DeclB->getParent(); + } +} + +static std::string getBestNamespaceSubstr(const DeclContext *DeclA, + StringRef NewName, + bool HadLeadingColonColon) { + while (true) { + while (DeclA && !isa(DeclA)) + DeclA = DeclA->getParent(); + + // Fully qualified it is! Leave :: in place if it's there already. + if (!DeclA) + return HadLeadingColonColon ? "::" + NewName.str() : NewName.str(); + + // Otherwise strip off redundant namespace qualifications from the new name. + std::string NS = + cast(DeclA)->getQualifiedNameAsString() + "::"; + if (NewName.startswith("::")) + NS = "::" + NS; + if (NewName.startswith(NS)) + return NewName.substr(NS.size()); + DeclA = DeclA->getParent(); + } +} + +/// Check if the name specifier begins with a written "::". +static bool isFullyQualified(const NestedNameSpecifier *NNS) { + while (NNS) { + if (NNS->getKind() == NestedNameSpecifier::Global) + return true; + NNS = NNS->getPrefix(); + } + return false; +} + +std::string tooling::replaceNestedName(const NestedNameSpecifier *Use, + const DeclContext *UseContext, + const NamedDecl *FromDecl, + StringRef ReplacementString) { + // We can do a raw name replacement when we are not inside the namespace for + // the original function and it is not in the global namespace. The + // assumption is that outside the original namespace we must have a using + // statement that makes this work out and that other parts of this refactor + // will automatically fix using statements to point to the new function + const bool class_name_only = !Use; + const bool in_global_namespace = + isa(FromDecl->getDeclContext()); + if (class_name_only && !in_global_namespace && + !isInsideDifferentNamespaceWithSameName(FromDecl->getDeclContext(), + UseContext)) { + auto Pos = ReplacementString.rfind("::"); + return Pos != StringRef::npos ? ReplacementString.substr(Pos + 2) + : ReplacementString; + } + // We did not match this because of a using statement, so we will need to + // figure out how good a namespace match we have with our destination type. + // We work backwards (from most specific possible namespace to least + // specific). + return getBestNamespaceSubstr(UseContext, ReplacementString, + isFullyQualified(Use)); +} Index: unittests/Tooling/CMakeLists.txt =================================================================== --- unittests/Tooling/CMakeLists.txt +++ unittests/Tooling/CMakeLists.txt @@ -6,6 +6,7 @@ add_clang_unittest(ToolingTests CommentHandlerTest.cpp CompilationDatabaseTest.cpp + LookupTest.cpp ToolingTest.cpp RecursiveASTVisitorTest.cpp RecursiveASTVisitorTestCallVisitor.cpp Index: unittests/Tooling/LookupTest.cpp =================================================================== --- /dev/null +++ unittests/Tooling/LookupTest.cpp @@ -0,0 +1,91 @@ +//===- unittest/Tooling/LookupTest.cpp ------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TestVisitor.h" +#include "clang/Tooling/Core/Lookup.h" +using namespace clang; + +namespace { +struct GetDeclsVisitor : TestVisitor { + std::function OnCall; + + bool VisitCallExpr(CallExpr *Expr) { + OnCall(Expr); + return true; + } +}; + +static std::string replaceCallExpr(const CallExpr *Expr, + StringRef ReplacementString) { + const auto *Callee = cast(Expr->getCallee()->IgnoreImplicit()); + const ValueDecl *FD = Callee->getDecl(); + ASTContext &Context = FD->getASTContext(); + const DeclContext *DC = + Context.getParents(ast_type_traits::DynTypedNode::create(*Expr))[0] + .get(); + return tooling::replaceNestedName(Callee->getQualifier(), DC, FD, + ReplacementString); +} + +TEST(LookupTest, replaceNestedName) { + GetDeclsVisitor Visitor; + + Visitor.OnCall = [](CallExpr *Expr) { + EXPECT_EQ("bar", replaceCallExpr(Expr, "bar")); + }; + Visitor.runOver("namespace a { void foo(); }\n" + "namespace a { void f() { foo(); } }\n"); + + Visitor.OnCall = [](CallExpr *Expr) { + EXPECT_EQ("bar", replaceCallExpr(Expr, "a::bar")); + }; + Visitor.runOver("namespace a { void foo(); }\n" + "namespace a { void f() { foo(); } }\n"); + + Visitor.OnCall = [](CallExpr *Expr) { + EXPECT_EQ("a::bar", replaceCallExpr(Expr, "a::bar")); + }; + Visitor.runOver("namespace a { void foo(); }\n" + "namespace b { void f() { a::foo(); } }\n"); + + Visitor.OnCall = [](CallExpr *Expr) { + EXPECT_EQ("a::bar", replaceCallExpr(Expr, "a::bar")); + }; + Visitor.runOver("namespace a { void foo(); }\n" + "namespace b { namespace a { void foo(); }\n" + "void f() { a::foo(); } }\n"); + + Visitor.OnCall = [](CallExpr *Expr) { + EXPECT_EQ("bar", replaceCallExpr(Expr, "bar")); + }; + Visitor.runOver("void foo(); void f() { foo(); }\n"); + + Visitor.OnCall = [](CallExpr *Expr) { + EXPECT_EQ("::bar", replaceCallExpr(Expr, "bar")); + }; + Visitor.runOver("void foo(); void f() { ::foo(); }\n"); + + Visitor.OnCall = [](CallExpr *Expr) { + EXPECT_EQ("a::bar", replaceCallExpr(Expr, "a::bar")); + }; + Visitor.runOver("namespace a { void foo(); }\nvoid f() { a::foo(); }\n"); + + Visitor.OnCall = [](CallExpr *Expr) { + EXPECT_EQ("a::bar", replaceCallExpr(Expr, "a::bar")); + }; + Visitor.runOver("namespace a { int foo(); }\nauto f = a::foo()\n"); + + Visitor.OnCall = [](CallExpr *Expr) { + EXPECT_EQ("bar", replaceCallExpr(Expr, "a::bar")); + }; + Visitor.runOver( + "namespace a { int foo(); }\nusing a::foo;\nauto f = foo()\n"); +} + +} // end anonymous namespace