Index: cfe/trunk/include/clang/Tooling/Core/Lookup.h =================================================================== --- cfe/trunk/include/clang/Tooling/Core/Lookup.h +++ cfe/trunk/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. Must be fully +/// qualified including a leading "::". +/// \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: cfe/trunk/lib/Tooling/Core/CMakeLists.txt =================================================================== --- cfe/trunk/lib/Tooling/Core/CMakeLists.txt +++ cfe/trunk/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: cfe/trunk/lib/Tooling/Core/Lookup.cpp =================================================================== --- cfe/trunk/lib/Tooling/Core/Lookup.cpp +++ cfe/trunk/lib/Tooling/Core/Lookup.cpp @@ -0,0 +1,113 @@ +//===--- 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 StringRef 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 : NewName.substr(2); + + // Otherwise strip off redundant namespace qualifications from the new name. + // We use the fully qualified name of the namespace and remove that part + // from NewName if it has an identical prefix. + std::string NS = + "::" + cast(DeclA)->getQualifiedNameAsString() + "::"; + if (NewName.startswith(NS)) + return NewName.substr(NS.size()); + + // No match yet. Strip of a namespace from the end of the chain and try + // again. This allows to get optimal qualifications even if the old and new + // decl only share common namespaces at a higher level. + 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) { + assert(ReplacementString.startswith("::") && + "Expected fully-qualified name!"); + + // 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: cfe/trunk/unittests/Tooling/CMakeLists.txt =================================================================== --- cfe/trunk/unittests/Tooling/CMakeLists.txt +++ cfe/trunk/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: cfe/trunk/unittests/Tooling/LookupTest.cpp =================================================================== --- cfe/trunk/unittests/Tooling/LookupTest.cpp +++ cfe/trunk/unittests/Tooling/LookupTest.cpp @@ -0,0 +1,108 @@ +//===- 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; + SmallVector DeclStack; + + bool VisitCallExpr(CallExpr *Expr) { + OnCall(Expr); + return true; + } + + bool TraverseDecl(Decl *D) { + DeclStack.push_back(D); + bool Ret = TestVisitor::TraverseDecl(D); + DeclStack.pop_back(); + return Ret; + } +}; + +TEST(LookupTest, replaceNestedName) { + GetDeclsVisitor Visitor; + + auto replaceCallExpr = [&](const CallExpr *Expr, + StringRef ReplacementString) { + const auto *Callee = cast(Expr->getCallee()->IgnoreImplicit()); + const ValueDecl *FD = Callee->getDecl(); + return tooling::replaceNestedName( + Callee->getQualifier(), Visitor.DeclStack.back()->getDeclContext(), FD, + ReplacementString); + }; + + 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("c::bar", replaceCallExpr(Expr, "::a::c::bar")); + }; + Visitor.runOver("namespace a { namespace b { void foo(); }\n" + "void f() { b::foo(); } }\n"); + + Visitor.OnCall = [&](CallExpr *Expr) { + EXPECT_EQ("bar", replaceCallExpr(Expr, "::a::bar")); + }; + Visitor.runOver("namespace a { namespace b { void foo(); }\n" + "void f() { b::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