brintos

brintos / llvm-project-archived public Read only

0
0
Text · 9.8 KiB · dedde86 Raw
215 lines · cpp
1//===--- Lookup.cpp - Framework for clang refactoring tools ---------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9//  This file defines helper methods for clang tools performing name lookup.10//11//===----------------------------------------------------------------------===//12 13#include "clang/Tooling/Refactoring/Lookup.h"14#include "clang/AST/ASTContext.h"15#include "clang/AST/Decl.h"16#include "clang/AST/DeclCXX.h"17#include "clang/AST/DeclarationName.h"18#include "clang/Basic/SourceLocation.h"19#include "clang/Basic/SourceManager.h"20#include "llvm/ADT/SmallVector.h"21using namespace clang;22using namespace clang::tooling;23 24// Gets all namespaces that \p Context is in as a vector (ignoring anonymous25// namespaces). The inner namespaces come before outer namespaces in the vector.26// For example, if the context is in the following namespace:27//    `namespace a { namespace b { namespace c ( ... ) } }`,28// the vector will be `{c, b, a}`.29static llvm::SmallVector<const NamespaceDecl *, 4>30getAllNamedNamespaces(const DeclContext *Context) {31  llvm::SmallVector<const NamespaceDecl *, 4> Namespaces;32  auto GetNextNamedNamespace = [](const DeclContext *Context) {33    // Look past non-namespaces and anonymous namespaces on FromContext.34    while (Context && (!isa<NamespaceDecl>(Context) ||35                       cast<NamespaceDecl>(Context)->isAnonymousNamespace()))36      Context = Context->getParent();37    return Context;38  };39  for (Context = GetNextNamedNamespace(Context); Context != nullptr;40       Context = GetNextNamedNamespace(Context->getParent()))41    Namespaces.push_back(cast<NamespaceDecl>(Context));42  return Namespaces;43}44 45// Returns true if the context in which the type is used and the context in46// which the type is declared are the same semantical namespace but different47// lexical namespaces.48static bool49usingFromDifferentCanonicalNamespace(const DeclContext *FromContext,50                                     const DeclContext *UseContext) {51  // We can skip anonymous namespace because:52  // 1. `FromContext` and `UseContext` must be in the same anonymous namespaces53  // since referencing across anonymous namespaces is not possible.54  // 2. If `FromContext` and `UseContext` are in the same anonymous namespace,55  // the function will still return `false` as expected.56  llvm::SmallVector<const NamespaceDecl *, 4> FromNamespaces =57      getAllNamedNamespaces(FromContext);58  llvm::SmallVector<const NamespaceDecl *, 4> UseNamespaces =59      getAllNamedNamespaces(UseContext);60  // If `UseContext` has fewer level of nested namespaces, it cannot be in the61  // same canonical namespace as the `FromContext`.62  if (UseNamespaces.size() < FromNamespaces.size())63    return false;64  unsigned Diff = UseNamespaces.size() - FromNamespaces.size();65  auto FromIter = FromNamespaces.begin();66  // Only compare `FromNamespaces` with namespaces in `UseNamespaces` that can67  // collide, i.e. the top N namespaces where N is the number of namespaces in68  // `FromNamespaces`.69  auto UseIter = UseNamespaces.begin() + Diff;70  for (; FromIter != FromNamespaces.end() && UseIter != UseNamespaces.end();71       ++FromIter, ++UseIter) {72    // Literally the same namespace, not a collision.73    if (*FromIter == *UseIter)74      return false;75    // Now check the names. If they match we have a different canonical76    // namespace with the same name.77    if (cast<NamespaceDecl>(*FromIter)->getDeclName() ==78        cast<NamespaceDecl>(*UseIter)->getDeclName())79      return true;80  }81  assert(FromIter == FromNamespaces.end() && UseIter == UseNamespaces.end());82  return false;83}84 85static StringRef getBestNamespaceSubstr(const DeclContext *DeclA,86                                        StringRef NewName,87                                        bool HadLeadingColonColon) {88  while (true) {89    while (DeclA && !isa<NamespaceDecl>(DeclA))90      DeclA = DeclA->getParent();91 92    // Fully qualified it is! Leave :: in place if it's there already.93    if (!DeclA)94      return HadLeadingColonColon ? NewName : NewName.substr(2);95 96    // Otherwise strip off redundant namespace qualifications from the new name.97    // We use the fully qualified name of the namespace and remove that part98    // from NewName if it has an identical prefix.99    std::string NS =100        "::" + cast<NamespaceDecl>(DeclA)->getQualifiedNameAsString() + "::";101    if (NewName.consume_front(NS))102      return NewName;103 104    // No match yet. Strip of a namespace from the end of the chain and try105    // again. This allows to get optimal qualifications even if the old and new106    // decl only share common namespaces at a higher level.107    DeclA = DeclA->getParent();108  }109}110 111// Adds more scope specifier to the spelled name until the spelling is not112// ambiguous. A spelling is ambiguous if the resolution of the symbol is113// ambiguous. For example, if QName is "::y::bar", the spelling is "y::bar", and114// context contains a nested namespace "a::y", then "y::bar" can be resolved to115// ::a::y::bar in the context, which can cause compile error.116// FIXME: consider using namespaces.117static std::string disambiguateSpellingInScope(StringRef Spelling,118                                               StringRef QName,119                                               const DeclContext &UseContext,120                                               SourceLocation UseLoc) {121  assert(QName.starts_with("::"));122  assert(QName.ends_with(Spelling));123  if (Spelling.starts_with("::"))124    return std::string(Spelling);125 126  auto UnspelledSpecifier = QName.drop_back(Spelling.size());127  llvm::SmallVector<llvm::StringRef, 2> UnspelledScopes;128  UnspelledSpecifier.split(UnspelledScopes, "::", /*MaxSplit=*/-1,129                           /*KeepEmpty=*/false);130 131  llvm::SmallVector<const NamespaceDecl *, 4> EnclosingNamespaces =132      getAllNamedNamespaces(&UseContext);133  auto &AST = UseContext.getParentASTContext();134  StringRef TrimmedQName = QName.substr(2);135  const auto &SM = UseContext.getParentASTContext().getSourceManager();136  UseLoc = SM.getSpellingLoc(UseLoc);137 138  auto IsAmbiguousSpelling = [&](const llvm::StringRef CurSpelling) {139    if (CurSpelling.starts_with("::"))140      return false;141    // Lookup the first component of Spelling in all enclosing namespaces142    // and check if there is any existing symbols with the same name but in143    // different scope.144    StringRef Head = CurSpelling.split("::").first;145    for (const auto *NS : EnclosingNamespaces) {146      auto LookupRes = NS->lookup(DeclarationName(&AST.Idents.get(Head)));147      if (!LookupRes.empty()) {148        for (const NamedDecl *Res : LookupRes)149          // If `Res` is not visible in `UseLoc`, we don't consider it150          // ambiguous. For example, a reference in a header file should not be151          // affected by a potentially ambiguous name in some file that includes152          // the header.153          if (!TrimmedQName.starts_with(Res->getQualifiedNameAsString()) &&154              SM.isBeforeInTranslationUnit(155                  SM.getSpellingLoc(Res->getLocation()), UseLoc))156            return true;157      }158    }159    return false;160  };161 162  // Add more qualifiers until the spelling is not ambiguous.163  std::string Disambiguated = std::string(Spelling);164  while (IsAmbiguousSpelling(Disambiguated)) {165    if (UnspelledScopes.empty()) {166      Disambiguated = "::" + Disambiguated;167    } else {168      Disambiguated = (UnspelledScopes.back() + "::" + Disambiguated).str();169      UnspelledScopes.pop_back();170    }171  }172  return Disambiguated;173}174 175std::string tooling::replaceNestedName(NestedNameSpecifier Use,176                                       SourceLocation UseLoc,177                                       const DeclContext *UseContext,178                                       const NamedDecl *FromDecl,179                                       StringRef ReplacementString) {180  assert(ReplacementString.starts_with("::") &&181         "Expected fully-qualified name!");182 183  // We can do a raw name replacement when we are not inside the namespace for184  // the original class/function and it is not in the global namespace.  The185  // assumption is that outside the original namespace we must have a using186  // statement that makes this work out and that other parts of this refactor187  // will automatically fix using statements to point to the new class/function.188  // However, if the `FromDecl` is a class forward declaration, the reference is189  // still considered as referring to the original definition, so we can't do a190  // raw name replacement in this case.191  const bool class_name_only = !Use;192  const bool in_global_namespace =193      isa<TranslationUnitDecl>(FromDecl->getDeclContext());194  const bool is_class_forward_decl =195      isa<CXXRecordDecl>(FromDecl) &&196      !cast<CXXRecordDecl>(FromDecl)->isCompleteDefinition();197  if (class_name_only && !in_global_namespace && !is_class_forward_decl &&198      !usingFromDifferentCanonicalNamespace(FromDecl->getDeclContext(),199                                            UseContext)) {200    auto Pos = ReplacementString.rfind("::");201    return std::string(Pos != StringRef::npos202                           ? ReplacementString.substr(Pos + 2)203                           : ReplacementString);204  }205  // We did not match this because of a using statement, so we will need to206  // figure out how good a namespace match we have with our destination type.207  // We work backwards (from most specific possible namespace to least208  // specific).209  StringRef Suggested = getBestNamespaceSubstr(UseContext, ReplacementString,210                                               Use.isFullyQualified());211 212  return disambiguateSpellingInScope(Suggested, ReplacementString, *UseContext,213                                     UseLoc);214}215