225 lines
10 KiB
C++
225 lines
10 KiB
C++
|
//===--- Lookup.cpp - Framework for clang refactoring tools ---------------===//
|
||
|
//
|
||
|
// 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
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
//
|
||
|
// This file defines helper methods for clang tools performing name lookup.
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "clang/Tooling/Refactoring/Lookup.h"
|
||
|
#include "clang/AST/ASTContext.h"
|
||
|
#include "clang/AST/Decl.h"
|
||
|
#include "clang/AST/DeclCXX.h"
|
||
|
#include "clang/AST/DeclarationName.h"
|
||
|
#include "clang/Basic/SourceLocation.h"
|
||
|
#include "clang/Basic/SourceManager.h"
|
||
|
#include "llvm/ADT/SmallVector.h"
|
||
|
using namespace clang;
|
||
|
using namespace clang::tooling;
|
||
|
|
||
|
// Gets all namespaces that \p Context is in as a vector (ignoring anonymous
|
||
|
// namespaces). The inner namespaces come before outer namespaces in the vector.
|
||
|
// For example, if the context is in the following namespace:
|
||
|
// `namespace a { namespace b { namespace c ( ... ) } }`,
|
||
|
// the vector will be `{c, b, a}`.
|
||
|
static llvm::SmallVector<const NamespaceDecl *, 4>
|
||
|
getAllNamedNamespaces(const DeclContext *Context) {
|
||
|
llvm::SmallVector<const NamespaceDecl *, 4> Namespaces;
|
||
|
auto GetNextNamedNamespace = [](const DeclContext *Context) {
|
||
|
// Look past non-namespaces and anonymous namespaces on FromContext.
|
||
|
while (Context && (!isa<NamespaceDecl>(Context) ||
|
||
|
cast<NamespaceDecl>(Context)->isAnonymousNamespace()))
|
||
|
Context = Context->getParent();
|
||
|
return Context;
|
||
|
};
|
||
|
for (Context = GetNextNamedNamespace(Context); Context != nullptr;
|
||
|
Context = GetNextNamedNamespace(Context->getParent()))
|
||
|
Namespaces.push_back(cast<NamespaceDecl>(Context));
|
||
|
return Namespaces;
|
||
|
}
|
||
|
|
||
|
// Returns true if the context in which the type is used and the context in
|
||
|
// which the type is declared are the same semantical namespace but different
|
||
|
// lexical namespaces.
|
||
|
static bool
|
||
|
usingFromDifferentCanonicalNamespace(const DeclContext *FromContext,
|
||
|
const DeclContext *UseContext) {
|
||
|
// We can skip anonymous namespace because:
|
||
|
// 1. `FromContext` and `UseContext` must be in the same anonymous namespaces
|
||
|
// since referencing across anonymous namespaces is not possible.
|
||
|
// 2. If `FromContext` and `UseContext` are in the same anonymous namespace,
|
||
|
// the function will still return `false` as expected.
|
||
|
llvm::SmallVector<const NamespaceDecl *, 4> FromNamespaces =
|
||
|
getAllNamedNamespaces(FromContext);
|
||
|
llvm::SmallVector<const NamespaceDecl *, 4> UseNamespaces =
|
||
|
getAllNamedNamespaces(UseContext);
|
||
|
// If `UseContext` has fewer level of nested namespaces, it cannot be in the
|
||
|
// same canonical namespace as the `FromContext`.
|
||
|
if (UseNamespaces.size() < FromNamespaces.size())
|
||
|
return false;
|
||
|
unsigned Diff = UseNamespaces.size() - FromNamespaces.size();
|
||
|
auto FromIter = FromNamespaces.begin();
|
||
|
// Only compare `FromNamespaces` with namespaces in `UseNamespaces` that can
|
||
|
// collide, i.e. the top N namespaces where N is the number of namespaces in
|
||
|
// `FromNamespaces`.
|
||
|
auto UseIter = UseNamespaces.begin() + Diff;
|
||
|
for (; FromIter != FromNamespaces.end() && UseIter != UseNamespaces.end();
|
||
|
++FromIter, ++UseIter) {
|
||
|
// Literally the same namespace, not a collision.
|
||
|
if (*FromIter == *UseIter)
|
||
|
return false;
|
||
|
// Now check the names. If they match we have a different canonical
|
||
|
// namespace with the same name.
|
||
|
if (cast<NamespaceDecl>(*FromIter)->getDeclName() ==
|
||
|
cast<NamespaceDecl>(*UseIter)->getDeclName())
|
||
|
return true;
|
||
|
}
|
||
|
assert(FromIter == FromNamespaces.end() && UseIter == UseNamespaces.end());
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static StringRef getBestNamespaceSubstr(const DeclContext *DeclA,
|
||
|
StringRef NewName,
|
||
|
bool HadLeadingColonColon) {
|
||
|
while (true) {
|
||
|
while (DeclA && !isa<NamespaceDecl>(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<NamespaceDecl>(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;
|
||
|
}
|
||
|
|
||
|
// Adds more scope specifier to the spelled name until the spelling is not
|
||
|
// ambiguous. A spelling is ambiguous if the resolution of the symbol is
|
||
|
// ambiguous. For example, if QName is "::y::bar", the spelling is "y::bar", and
|
||
|
// context contains a nested namespace "a::y", then "y::bar" can be resolved to
|
||
|
// ::a::y::bar in the context, which can cause compile error.
|
||
|
// FIXME: consider using namespaces.
|
||
|
static std::string disambiguateSpellingInScope(StringRef Spelling,
|
||
|
StringRef QName,
|
||
|
const DeclContext &UseContext,
|
||
|
SourceLocation UseLoc) {
|
||
|
assert(QName.startswith("::"));
|
||
|
assert(QName.endswith(Spelling));
|
||
|
if (Spelling.startswith("::"))
|
||
|
return std::string(Spelling);
|
||
|
|
||
|
auto UnspelledSpecifier = QName.drop_back(Spelling.size());
|
||
|
llvm::SmallVector<llvm::StringRef, 2> UnspelledScopes;
|
||
|
UnspelledSpecifier.split(UnspelledScopes, "::", /*MaxSplit=*/-1,
|
||
|
/*KeepEmpty=*/false);
|
||
|
|
||
|
llvm::SmallVector<const NamespaceDecl *, 4> EnclosingNamespaces =
|
||
|
getAllNamedNamespaces(&UseContext);
|
||
|
auto &AST = UseContext.getParentASTContext();
|
||
|
StringRef TrimmedQName = QName.substr(2);
|
||
|
const auto &SM = UseContext.getParentASTContext().getSourceManager();
|
||
|
UseLoc = SM.getSpellingLoc(UseLoc);
|
||
|
|
||
|
auto IsAmbiguousSpelling = [&](const llvm::StringRef CurSpelling) {
|
||
|
if (CurSpelling.startswith("::"))
|
||
|
return false;
|
||
|
// Lookup the first component of Spelling in all enclosing namespaces
|
||
|
// and check if there is any existing symbols with the same name but in
|
||
|
// different scope.
|
||
|
StringRef Head = CurSpelling.split("::").first;
|
||
|
for (const auto *NS : EnclosingNamespaces) {
|
||
|
auto LookupRes = NS->lookup(DeclarationName(&AST.Idents.get(Head)));
|
||
|
if (!LookupRes.empty()) {
|
||
|
for (const NamedDecl *Res : LookupRes)
|
||
|
// If `Res` is not visible in `UseLoc`, we don't consider it
|
||
|
// ambiguous. For example, a reference in a header file should not be
|
||
|
// affected by a potentially ambiguous name in some file that includes
|
||
|
// the header.
|
||
|
if (!TrimmedQName.startswith(Res->getQualifiedNameAsString()) &&
|
||
|
SM.isBeforeInTranslationUnit(
|
||
|
SM.getSpellingLoc(Res->getLocation()), UseLoc))
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
// Add more qualifiers until the spelling is not ambiguous.
|
||
|
std::string Disambiguated = std::string(Spelling);
|
||
|
while (IsAmbiguousSpelling(Disambiguated)) {
|
||
|
if (UnspelledScopes.empty()) {
|
||
|
Disambiguated = "::" + Disambiguated;
|
||
|
} else {
|
||
|
Disambiguated = (UnspelledScopes.back() + "::" + Disambiguated).str();
|
||
|
UnspelledScopes.pop_back();
|
||
|
}
|
||
|
}
|
||
|
return Disambiguated;
|
||
|
}
|
||
|
|
||
|
std::string tooling::replaceNestedName(const NestedNameSpecifier *Use,
|
||
|
SourceLocation UseLoc,
|
||
|
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 class/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 class/function.
|
||
|
// However, if the `FromDecl` is a class forward declaration, the reference is
|
||
|
// still considered as referring to the original definition, so we can't do a
|
||
|
// raw name replacement in this case.
|
||
|
const bool class_name_only = !Use;
|
||
|
const bool in_global_namespace =
|
||
|
isa<TranslationUnitDecl>(FromDecl->getDeclContext());
|
||
|
const bool is_class_forward_decl =
|
||
|
isa<CXXRecordDecl>(FromDecl) &&
|
||
|
!cast<CXXRecordDecl>(FromDecl)->isCompleteDefinition();
|
||
|
if (class_name_only && !in_global_namespace && !is_class_forward_decl &&
|
||
|
!usingFromDifferentCanonicalNamespace(FromDecl->getDeclContext(),
|
||
|
UseContext)) {
|
||
|
auto Pos = ReplacementString.rfind("::");
|
||
|
return std::string(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).
|
||
|
StringRef Suggested = getBestNamespaceSubstr(UseContext, ReplacementString,
|
||
|
isFullyQualified(Use));
|
||
|
|
||
|
return disambiguateSpellingInScope(Suggested, ReplacementString, *UseContext,
|
||
|
UseLoc);
|
||
|
}
|