brintos

brintos / llvm-project-archived public Read only

0
0
Text · 22.9 KiB · c9108fc Raw
613 lines · cpp
1//===--- USRLocFinder.cpp - Clang refactoring library ---------------------===//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/// \file10/// Methods for finding all instances of a USR. Our strategy is very11/// simple; we just compare the USR at every relevant AST node with the one12/// provided.13///14//===----------------------------------------------------------------------===//15 16#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"17#include "clang/AST/ASTContext.h"18#include "clang/AST/ParentMapContext.h"19#include "clang/AST/RecursiveASTVisitor.h"20#include "clang/Basic/LLVM.h"21#include "clang/Basic/SourceLocation.h"22#include "clang/Basic/SourceManager.h"23#include "clang/Lex/Lexer.h"24#include "clang/Tooling/Refactoring/Lookup.h"25#include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h"26#include "clang/Tooling/Refactoring/Rename/SymbolName.h"27#include "clang/Tooling/Refactoring/Rename/USRFinder.h"28#include "llvm/ADT/StringRef.h"29#include "llvm/Support/Casting.h"30#include <cstddef>31#include <set>32#include <string>33#include <vector>34 35using namespace llvm;36 37namespace clang {38namespace tooling {39 40namespace {41 42// Returns true if the given Loc is valid for edit. We don't edit the43// SourceLocations that are valid or in temporary buffer.44bool IsValidEditLoc(const clang::SourceManager& SM, clang::SourceLocation Loc) {45  if (Loc.isInvalid())46    return false;47  const clang::FullSourceLoc FullLoc(Loc, SM);48  auto FileIdAndOffset = FullLoc.getSpellingLoc().getDecomposedLoc();49  return SM.getFileEntryForID(FileIdAndOffset.first) != nullptr;50}51 52// This visitor recursively searches for all instances of a USR in a53// translation unit and stores them for later usage.54class USRLocFindingASTVisitor55    : public RecursiveSymbolVisitor<USRLocFindingASTVisitor> {56public:57  explicit USRLocFindingASTVisitor(const std::vector<std::string> &USRs,58                                   StringRef PrevName,59                                   const ASTContext &Context)60      : RecursiveSymbolVisitor(Context.getSourceManager(),61                               Context.getLangOpts()),62        USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) {63  }64 65  bool visitSymbolOccurrence(const NamedDecl *ND,66                             ArrayRef<SourceRange> NameRanges) {67    if (USRSet.find(getUSRForDecl(ND)) != USRSet.end()) {68      assert(NameRanges.size() == 1 &&69             "Multiple name pieces are not supported yet!");70      SourceLocation Loc = NameRanges[0].getBegin();71      const SourceManager &SM = Context.getSourceManager();72      // TODO: Deal with macro occurrences correctly.73      if (Loc.isMacroID())74        Loc = SM.getSpellingLoc(Loc);75      checkAndAddLocation(Loc);76    }77    return true;78  }79 80  // Non-visitors:81 82  /// Returns a set of unique symbol occurrences. Duplicate or83  /// overlapping occurrences are erroneous and should be reported!84  SymbolOccurrences takeOccurrences() { return std::move(Occurrences); }85 86private:87  void checkAndAddLocation(SourceLocation Loc) {88    const SourceLocation BeginLoc = Loc;89    const SourceLocation EndLoc = Lexer::getLocForEndOfToken(90        BeginLoc, 0, Context.getSourceManager(), Context.getLangOpts());91    StringRef TokenName =92        Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc),93                             Context.getSourceManager(), Context.getLangOpts());94    size_t Offset = TokenName.find(PrevName.getNamePieces()[0]);95 96    // The token of the source location we find actually has the old97    // name.98    if (Offset != StringRef::npos)99      Occurrences.emplace_back(PrevName, SymbolOccurrence::MatchingSymbol,100                               BeginLoc.getLocWithOffset(Offset));101  }102 103  const std::set<std::string> USRSet;104  const SymbolName PrevName;105  SymbolOccurrences Occurrences;106  const ASTContext &Context;107};108 109SourceLocation StartLocationForType(TypeLoc TL) {110  if (auto QTL = TL.getAs<QualifiedTypeLoc>())111    TL = QTL.getUnqualifiedLoc();112 113  // For elaborated types (e.g. `struct a::A`) we want the portion after the114  // `struct`, including the namespace qualifier, `a::`.115  switch (TL.getTypeLocClass()) {116  case TypeLoc::Record:117  case TypeLoc::InjectedClassName:118  case TypeLoc::Enum: {119    auto TTL = TL.castAs<TagTypeLoc>();120    if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())121      return QualifierLoc.getBeginLoc();122    return TTL.getNameLoc();123  }124  case TypeLoc::Typedef: {125    auto TTL = TL.castAs<TypedefTypeLoc>();126    if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())127      return QualifierLoc.getBeginLoc();128    return TTL.getNameLoc();129  }130  case TypeLoc::UnresolvedUsing: {131    auto TTL = TL.castAs<UnresolvedUsingTypeLoc>();132    if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())133      return QualifierLoc.getBeginLoc();134    return TTL.getNameLoc();135  }136  case TypeLoc::Using: {137    auto TTL = TL.castAs<UsingTypeLoc>();138    if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())139      return QualifierLoc.getBeginLoc();140    return TTL.getNameLoc();141  }142  case TypeLoc::TemplateSpecialization: {143    auto TTL = TL.castAs<TemplateSpecializationTypeLoc>();144    if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())145      return QualifierLoc.getBeginLoc();146    return TTL.getTemplateNameLoc();147  }148  case TypeLoc::DeducedTemplateSpecialization: {149    auto DTL = TL.castAs<clang::DeducedTemplateSpecializationTypeLoc>();150    if (NestedNameSpecifierLoc QualifierLoc = DTL.getQualifierLoc())151      return QualifierLoc.getBeginLoc();152    return DTL.getTemplateNameLoc();153  }154  case TypeLoc::DependentName: {155    auto TTL = TL.castAs<DependentNameTypeLoc>();156    if (NestedNameSpecifierLoc QualifierLoc = TTL.getQualifierLoc())157      return QualifierLoc.getBeginLoc();158    return TTL.getNameLoc();159  }160  default:161    llvm_unreachable("unhandled TypeLoc class");162  }163}164 165SourceLocation EndLocationForType(TypeLoc TL) {166  if (auto QTL = TL.getAs<QualifiedTypeLoc>())167    TL = QTL.getUnqualifiedLoc();168 169  // The location for template specializations (e.g. Foo<int>) includes the170  // templated types in its location range.  We want to restrict this to just171  // before the `<` character.172  if (auto TTL = TL.getAs<TemplateSpecializationTypeLoc>())173    return TTL.getLAngleLoc().getLocWithOffset(-1);174  return TL.getEndLoc();175}176 177NestedNameSpecifier GetNestedNameForType(TypeLoc TL) {178  if (auto QTL = TL.getAs<QualifiedTypeLoc>())179    TL = QTL.getUnqualifiedLoc();180  return TL.getPrefix().getNestedNameSpecifier();181}182 183// Find all locations identified by the given USRs for rename.184//185// This class will traverse the AST and find every AST node whose USR is in the186// given USRs' set.187class RenameLocFinder : public RecursiveASTVisitor<RenameLocFinder> {188public:189  RenameLocFinder(llvm::ArrayRef<std::string> USRs, ASTContext &Context)190      : USRSet(USRs.begin(), USRs.end()), Context(Context) {}191 192  // A structure records all information of a symbol reference being renamed.193  // We try to add as few prefix qualifiers as possible.194  struct RenameInfo {195    // The begin location of a symbol being renamed.196    SourceLocation Begin;197    // The end location of a symbol being renamed.198    SourceLocation End;199    // The declaration of a symbol being renamed (can be nullptr).200    const NamedDecl *FromDecl;201    // The declaration in which the nested name is contained (can be nullptr).202    const Decl *Context;203    // The nested name being replaced.204    NestedNameSpecifier Specifier;205    // Determine whether the prefix qualifiers of the NewName should be ignored.206    // Normally, we set it to true for the symbol declaration and definition to207    // avoid adding prefix qualifiers.208    // For example, if it is true and NewName is "a::b::foo", then the symbol209    // occurrence which the RenameInfo points to will be renamed to "foo".210    bool IgnorePrefixQualifiers;211  };212 213  bool VisitNamedDecl(const NamedDecl *Decl) {214    // UsingDecl has been handled in other place.215    if (llvm::isa<UsingDecl>(Decl))216      return true;217 218    // DestructorDecl has been handled in Typeloc.219    if (llvm::isa<CXXDestructorDecl>(Decl))220      return true;221 222    if (Decl->isImplicit())223      return true;224 225    if (isInUSRSet(Decl)) {226      // For the case of renaming an alias template, we actually rename the227      // underlying alias declaration of the template.228      if (const auto* TAT = dyn_cast<TypeAliasTemplateDecl>(Decl))229        Decl = TAT->getTemplatedDecl();230 231      auto StartLoc = Decl->getLocation();232      auto EndLoc = StartLoc;233      if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) {234        RenameInfo Info = {StartLoc,235                           EndLoc,236                           /*FromDecl=*/nullptr,237                           /*Context=*/nullptr,238                           /*Specifier=*/std::nullopt,239                           /*IgnorePrefixQualifiers=*/true};240        RenameInfos.push_back(Info);241      }242    }243    return true;244  }245 246  bool VisitMemberExpr(const MemberExpr *Expr) {247    const NamedDecl *Decl = Expr->getFoundDecl();248    auto StartLoc = Expr->getMemberLoc();249    auto EndLoc = Expr->getMemberLoc();250    if (isInUSRSet(Decl)) {251      RenameInfos.push_back({StartLoc, EndLoc,252                             /*FromDecl=*/nullptr,253                             /*Context=*/nullptr,254                             /*Specifier=*/std::nullopt,255                             /*IgnorePrefixQualifiers=*/true});256    }257    return true;258  }259 260  bool VisitDesignatedInitExpr(const DesignatedInitExpr *E) {261    for (const DesignatedInitExpr::Designator &D : E->designators()) {262      if (D.isFieldDesignator()) {263        if (const FieldDecl *Decl = D.getFieldDecl()) {264          if (isInUSRSet(Decl)) {265            auto StartLoc = D.getFieldLoc();266            auto EndLoc = D.getFieldLoc();267            RenameInfos.push_back({StartLoc, EndLoc,268                                   /*FromDecl=*/nullptr,269                                   /*Context=*/nullptr,270                                   /*Specifier=*/std::nullopt,271                                   /*IgnorePrefixQualifiers=*/true});272          }273        }274      }275    }276    return true;277  }278 279  bool VisitCXXConstructorDecl(const CXXConstructorDecl *CD) {280    // Fix the constructor initializer when renaming class members.281    for (const auto *Initializer : CD->inits()) {282      // Ignore implicit initializers.283      if (!Initializer->isWritten())284        continue;285 286      if (const FieldDecl *FD = Initializer->getMember()) {287        if (isInUSRSet(FD)) {288          auto Loc = Initializer->getSourceLocation();289          RenameInfos.push_back({Loc, Loc,290                                 /*FromDecl=*/nullptr,291                                 /*Context=*/nullptr,292                                 /*Specifier=*/std::nullopt,293                                 /*IgnorePrefixQualifiers=*/true});294        }295      }296    }297    return true;298  }299 300  bool VisitDeclRefExpr(const DeclRefExpr *Expr) {301    const NamedDecl *Decl = Expr->getFoundDecl();302    // Get the underlying declaration of the shadow declaration introduced by a303    // using declaration.304    if (auto *UsingShadow = llvm::dyn_cast<UsingShadowDecl>(Decl)) {305      Decl = UsingShadow->getTargetDecl();306    }307 308    auto StartLoc = Expr->getBeginLoc();309    // For template function call expressions like `foo<int>()`, we want to310    // restrict the end of location to just before the `<` character.311    SourceLocation EndLoc = Expr->hasExplicitTemplateArgs()312                                ? Expr->getLAngleLoc().getLocWithOffset(-1)313                                : Expr->getEndLoc();314 315    if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(Decl)) {316      if (isInUSRSet(MD)) {317        // Handle renaming static template class methods, we only rename the318        // name without prefix qualifiers and restrict the source range to the319        // name.320        RenameInfos.push_back({EndLoc, EndLoc,321                               /*FromDecl=*/nullptr,322                               /*Context=*/nullptr,323                               /*Specifier=*/std::nullopt,324                               /*IgnorePrefixQualifiers=*/true});325        return true;326      }327    }328 329    // In case of renaming an enum declaration, we have to explicitly handle330    // unscoped enum constants referenced in expressions (e.g.331    // "auto r = ns1::ns2::Green" where Green is an enum constant of an unscoped332    // enum decl "ns1::ns2::Color") as these enum constants cannot be caught by333    // TypeLoc.334    if (const auto *T = llvm::dyn_cast<EnumConstantDecl>(Decl)) {335      // FIXME: Handle the enum constant without prefix qualifiers (`a = Green`)336      // when renaming an unscoped enum declaration with a new namespace.337      if (!Expr->hasQualifier())338        return true;339 340      if (const auto *ED =341              llvm::dyn_cast_or_null<EnumDecl>(getClosestAncestorDecl(*T))) {342        if (ED->isScoped())343          return true;344        Decl = ED;345      }346      // The current fix would qualify "ns1::ns2::Green" as347      // "ns1::ns2::Color::Green".348      //349      // Get the EndLoc of the replacement by moving 1 character backward (350      // to exclude the last '::').351      //352      //    ns1::ns2::Green;353      //    ^      ^^354      // BeginLoc  |EndLoc of the qualifier355      //           new EndLoc356      EndLoc = Expr->getQualifierLoc().getEndLoc().getLocWithOffset(-1);357      assert(EndLoc.isValid() &&358             "The enum constant should have prefix qualifers.");359    }360    if (isInUSRSet(Decl) &&361        IsValidEditLoc(Context.getSourceManager(), StartLoc)) {362      RenameInfo Info = {StartLoc,363                         EndLoc,364                         Decl,365                         getClosestAncestorDecl(*Expr),366                         Expr->getQualifier(),367                         /*IgnorePrefixQualifiers=*/false};368      RenameInfos.push_back(Info);369    }370 371    return true;372  }373 374  bool VisitUsingDecl(const UsingDecl *Using) {375    for (const auto *UsingShadow : Using->shadows()) {376      if (isInUSRSet(UsingShadow->getTargetDecl())) {377        UsingDecls.push_back(Using);378        break;379      }380    }381    return true;382  }383 384  bool VisitNestedNameSpecifierLocations(NestedNameSpecifierLoc NestedLoc) {385    TypeLoc TL = NestedLoc.getAsTypeLoc();386    if (!TL)387      return true;388 389    if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(TL)) {390      if (isInUSRSet(TargetDecl)) {391        RenameInfo Info = {NestedLoc.getBeginLoc(),392                           EndLocationForType(TL),393                           TargetDecl,394                           getClosestAncestorDecl(NestedLoc),395                           /*Specifier=*/std::nullopt,396                           /*IgnorePrefixQualifiers=*/false};397        RenameInfos.push_back(Info);398      }399    }400    return true;401  }402 403  bool VisitTypeLoc(TypeLoc Loc) {404    auto Parents = Context.getParents(Loc);405    TypeLoc ParentTypeLoc;406    if (!Parents.empty()) {407      // Handle cases of nested name specificier locations.408      //409      // The VisitNestedNameSpecifierLoc interface is not impelmented in410      // RecursiveASTVisitor, we have to handle it explicitly.411      if (const auto *NSL = Parents[0].get<NestedNameSpecifierLoc>()) {412        VisitNestedNameSpecifierLocations(*NSL);413        return true;414      }415 416      if (const auto *TL = Parents[0].get<TypeLoc>())417        ParentTypeLoc = *TL;418    }419 420    // Handle the outermost TypeLoc which is directly linked to the interesting421    // declaration and don't handle nested name specifier locations.422    if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(Loc)) {423      if (isInUSRSet(TargetDecl)) {424        // Only handle the outermost typeLoc.425        //426        // For a type like "a::Foo", there will be two typeLocs for it.427        // One ElaboratedType, the other is RecordType:428        //429        //   ElaboratedType 0x33b9390 'a::Foo' sugar430        //   `-RecordType 0x338fef0 'class a::Foo'431        //     `-CXXRecord 0x338fe58 'Foo'432        //433        // Skip if this is an inner typeLoc.434        if (!ParentTypeLoc.isNull() &&435            isInUSRSet(getSupportedDeclFromTypeLoc(ParentTypeLoc)))436          return true;437 438        auto StartLoc = StartLocationForType(Loc);439        auto EndLoc = EndLocationForType(Loc);440        if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) {441          RenameInfo Info = {StartLoc,442                             EndLoc,443                             TargetDecl,444                             getClosestAncestorDecl(Loc),445                             GetNestedNameForType(Loc),446                             /*IgnorePrefixQualifiers=*/false};447          RenameInfos.push_back(Info);448        }449        return true;450      }451    }452 453    // Handle specific template class specialiation cases.454    if (const auto *TemplateSpecType =455            dyn_cast<TemplateSpecializationType>(Loc.getType())) {456      if (isInUSRSet(TemplateSpecType->getTemplateName().getAsTemplateDecl())) {457        auto StartLoc = StartLocationForType(Loc);458        auto EndLoc = EndLocationForType(Loc);459        if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) {460          RenameInfo Info = {461              StartLoc,462              EndLoc,463              TemplateSpecType->getTemplateName().getAsTemplateDecl(),464              getClosestAncestorDecl(DynTypedNode::create(Loc)),465              GetNestedNameForType(Loc),466              /*IgnorePrefixQualifiers=*/false};467          RenameInfos.push_back(Info);468        }469      }470    }471    return true;472  }473 474  // Returns a list of RenameInfo.475  const std::vector<RenameInfo> &getRenameInfos() const { return RenameInfos; }476 477  // Returns a list of using declarations which are needed to update.478  const std::vector<const UsingDecl *> &getUsingDecls() const {479    return UsingDecls;480  }481 482private:483  // Get the supported declaration from a given typeLoc. If the declaration type484  // is not supported, returns nullptr.485  const NamedDecl *getSupportedDeclFromTypeLoc(TypeLoc Loc) {486    if (const auto* TT = Loc.getType()->getAs<clang::TypedefType>())487      return TT->getDecl();488    return Loc.getType()->getAsTagDecl();489  }490 491  // Get the closest ancester which is a declaration of a given AST node.492  template <typename ASTNodeType>493  const Decl *getClosestAncestorDecl(const ASTNodeType &Node) {494    auto Parents = Context.getParents(Node);495    // FIXME: figure out how to handle it when there are multiple parents.496    if (Parents.size() != 1)497      return nullptr;498    if (ASTNodeKind::getFromNodeKind<Decl>().isBaseOf(Parents[0].getNodeKind()))499      return Parents[0].template get<Decl>();500    return getClosestAncestorDecl(Parents[0]);501  }502 503  // Get the parent typeLoc of a given typeLoc. If there is no such parent,504  // return nullptr.505  const TypeLoc *getParentTypeLoc(TypeLoc Loc) const {506    auto Parents = Context.getParents(Loc);507    // FIXME: figure out how to handle it when there are multiple parents.508    if (Parents.size() != 1)509      return nullptr;510    return Parents[0].get<TypeLoc>();511  }512 513  // Check whether the USR of a given Decl is in the USRSet.514  bool isInUSRSet(const Decl *Decl) const {515    auto USR = getUSRForDecl(Decl);516    if (USR.empty())517      return false;518    return llvm::is_contained(USRSet, USR);519  }520 521  const std::set<std::string> USRSet;522  ASTContext &Context;523  std::vector<RenameInfo> RenameInfos;524  // Record all interested using declarations which contains the using-shadow525  // declarations of the symbol declarations being renamed.526  std::vector<const UsingDecl *> UsingDecls;527};528 529} // namespace530 531SymbolOccurrences getOccurrencesOfUSRs(ArrayRef<std::string> USRs,532                                       StringRef PrevName, Decl *Decl) {533  USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext());534  Visitor.TraverseDecl(Decl);535  return Visitor.takeOccurrences();536}537 538std::vector<tooling::AtomicChange>539createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs,540                          llvm::StringRef NewName, Decl *TranslationUnitDecl) {541  RenameLocFinder Finder(USRs, TranslationUnitDecl->getASTContext());542  Finder.TraverseDecl(TranslationUnitDecl);543 544  const SourceManager &SM =545      TranslationUnitDecl->getASTContext().getSourceManager();546 547  std::vector<tooling::AtomicChange> AtomicChanges;548  auto Replace = [&](SourceLocation Start, SourceLocation End,549                     llvm::StringRef Text) {550    tooling::AtomicChange ReplaceChange = tooling::AtomicChange(SM, Start);551    llvm::Error Err = ReplaceChange.replace(552        SM, CharSourceRange::getTokenRange(Start, End), Text);553    if (Err) {554      llvm::errs() << "Failed to add replacement to AtomicChange: "555                   << llvm::toString(std::move(Err)) << "\n";556      return;557    }558    AtomicChanges.push_back(std::move(ReplaceChange));559  };560 561  for (const auto &RenameInfo : Finder.getRenameInfos()) {562    std::string ReplacedName = NewName.str();563    if (RenameInfo.IgnorePrefixQualifiers) {564      // Get the name without prefix qualifiers from NewName.565      size_t LastColonPos = NewName.find_last_of(':');566      if (LastColonPos != std::string::npos)567        ReplacedName = std::string(NewName.substr(LastColonPos + 1));568    } else {569      if (RenameInfo.FromDecl && RenameInfo.Context) {570        if (!llvm::isa<clang::TranslationUnitDecl>(571                RenameInfo.Context->getDeclContext())) {572          ReplacedName = tooling::replaceNestedName(573              RenameInfo.Specifier, RenameInfo.Begin,574              RenameInfo.Context->getDeclContext(), RenameInfo.FromDecl,575              NewName.starts_with("::") ? NewName.str()576                                        : ("::" + NewName).str());577        } else {578          // This fixes the case where type `T` is a parameter inside a function579          // type (e.g. `std::function<void(T)>`) and the DeclContext of `T`580          // becomes the translation unit. As a workaround, we simply use581          // fully-qualified name here for all references whose `DeclContext` is582          // the translation unit and ignore the possible existence of583          // using-decls (in the global scope) that can shorten the replaced584          // name.585          llvm::StringRef ActualName = Lexer::getSourceText(586              CharSourceRange::getTokenRange(587                  SourceRange(RenameInfo.Begin, RenameInfo.End)),588              SM, TranslationUnitDecl->getASTContext().getLangOpts());589          // Add the leading "::" back if the name written in the code contains590          // it.591          if (ActualName.starts_with("::") && !NewName.starts_with("::")) {592            ReplacedName = "::" + NewName.str();593          }594        }595      }596      // If the NewName contains leading "::", add it back.597      if (NewName.starts_with("::") && NewName.substr(2) == ReplacedName)598        ReplacedName = NewName.str();599    }600    Replace(RenameInfo.Begin, RenameInfo.End, ReplacedName);601  }602 603  // Hanlde using declarations explicitly as "using a::Foo" don't trigger604  // typeLoc for "a::Foo".605  for (const auto *Using : Finder.getUsingDecls())606    Replace(Using->getBeginLoc(), Using->getEndLoc(), "using " + NewName.str());607 608  return AtomicChanges;609}610 611} // end namespace tooling612} // end namespace clang613