[ASTMatchers] Allow hasName() to look through inline namespaces

Summary:
Allow hasName() to look through inline namespaces.
This will fix the interaction between some clang-tidy checks and libc++.

libc++ defines names in an inline namespace named std::<version_#>.
When we try to match a name using hasName("std::xxx") it fails to match and the clang-tidy check does not work.

Reviewers: klimek

Subscribers: klimek, cfe-commits

Differential Revision: http://reviews.llvm.org/D15506

llvm-svn: 259898
diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
index 463cf0b..47054d8 100644
--- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
+++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp
@@ -298,45 +298,149 @@
   assert(!Name.empty());
 }
 
-bool HasNameMatcher::matchesNodeUnqualified(const NamedDecl &Node) const {
-  assert(UseUnqualifiedMatch);
-  if (Node.getIdentifier()) {
-    // Simple name.
-    return Name == Node.getName();
+namespace {
+
+bool ConsumeNameSuffix(StringRef &FullName, StringRef Suffix) {
+  StringRef Name = FullName;
+  if (!Name.endswith(Suffix))
+    return false;
+  Name = Name.drop_back(Suffix.size());
+  if (!Name.empty()) {
+    if (!Name.endswith("::"))
+      return false;
+    Name = Name.drop_back(2);
   }
+  FullName = Name;
+  return true;
+}
+
+bool ConsumeNodeName(StringRef &Name, const NamedDecl &Node) {
+  // Simple name.
+  if (Node.getIdentifier())
+    return ConsumeNameSuffix(Name, Node.getName());
+
   if (Node.getDeclName()) {
     // Name needs to be constructed.
     llvm::SmallString<128> NodeName;
     llvm::raw_svector_ostream OS(NodeName);
     Node.printName(OS);
-    return Name == OS.str();
+    return ConsumeNameSuffix(Name, OS.str());
   }
+
+  return ConsumeNameSuffix(Name, "(anonymous)");
+}
+
+}  // namespace
+
+bool HasNameMatcher::matchesNodeUnqualified(const NamedDecl &Node) const {
+  assert(UseUnqualifiedMatch);
+  StringRef NodeName = Name;
+  return ConsumeNodeName(NodeName, Node) && NodeName.empty();
+}
+
+bool HasNameMatcher::matchesNodeFullFast(const NamedDecl &Node) const {
+  // This function is copied and adapted from NamedDecl::printQualifiedName()
+  // By matching each part individually we optimize in a couple of ways:
+  //  - We can exit early on the first failure.
+  //  - We can skip inline/anonymous namespaces without another pass.
+  //  - We print one name at a time, reducing the chance of overflowing the
+  //    inlined space of the SmallString.
+  StringRef Pattern = Name;
+  const bool IsFullyQualified = Pattern.startswith("::");
+
+  // First, match the name.
+  if (!ConsumeNodeName(Pattern, Node))
+    return false;
+
+  // Try to match each declaration context.
+  // We are allowed to skip anonymous and inline namespaces if they don't match.
+  const DeclContext *Ctx = Node.getDeclContext();
+
+  if (Ctx->isFunctionOrMethod())
+    return Pattern.empty() && !IsFullyQualified;
+
+  for (; !Pattern.empty() && Ctx && isa<NamedDecl>(Ctx);
+       Ctx = Ctx->getParent()) {
+    if (const auto *ND = dyn_cast<NamespaceDecl>(Ctx)) {
+      StringRef NSName =
+          ND->isAnonymousNamespace() ? "(anonymous namespace)" : ND->getName();
+
+      // If it matches, continue.
+      if (ConsumeNameSuffix(Pattern, NSName))
+        continue;
+      // If it didn't match but we can skip it, continue.
+      if (ND->isAnonymousNamespace() || ND->isInline())
+        continue;
+
+      return false;
+    }
+    if (const auto *RD = dyn_cast<RecordDecl>(Ctx)) {
+      if (!isa<ClassTemplateSpecializationDecl>(Ctx)) {
+        if (RD->getIdentifier()) {
+          if (ConsumeNameSuffix(Pattern, RD->getName()))
+            continue;
+        } else {
+          llvm::SmallString<128> NodeName;
+          NodeName += StringRef("(anonymous ");
+          NodeName += RD->getKindName();
+          NodeName += ')';
+          if (ConsumeNameSuffix(Pattern, NodeName))
+            continue;
+        }
+
+        return false;
+      }
+    }
+
+    // We don't know how to deal with this DeclContext.
+    // Fallback to the slow version of the code.
+    return matchesNodeFullSlow(Node);
+  }
+
+  // If we are fully qualified, we must not have any leftover context.
+  if (IsFullyQualified && Ctx && isa<NamedDecl>(Ctx))
+    return false;
+
+  return Pattern.empty();
+}
+
+bool HasNameMatcher::matchesNodeFullSlow(const NamedDecl &Node) const {
+  const StringRef Pattern = Name;
+
+  const bool SkipUnwrittenCases[] = {false, true};
+  for (bool SkipUnwritten : SkipUnwrittenCases) {
+    llvm::SmallString<128> NodeName = StringRef("::");
+    llvm::raw_svector_ostream OS(NodeName);
+
+    if (SkipUnwritten) {
+      PrintingPolicy Policy = Node.getASTContext().getPrintingPolicy();
+      Policy.SuppressUnwrittenScope = true;
+      Node.printQualifiedName(OS, Policy);
+    } else {
+      Node.printQualifiedName(OS);
+    }
+
+    const StringRef FullName = OS.str();
+
+    if (Pattern.startswith("::")) {
+      if (FullName == Pattern)
+        return true;
+    } else if (FullName.endswith(Pattern) &&
+               FullName.drop_back(Pattern.size()).endswith("::")) {
+      return true;
+    }
+  }
+
   return false;
 }
 
-bool HasNameMatcher::matchesNodeFull(const NamedDecl &Node) const {
-  llvm::SmallString<128> NodeName = StringRef("::");
-  llvm::raw_svector_ostream OS(NodeName);
-  Node.printQualifiedName(OS);
-  const StringRef FullName = OS.str();
-  const StringRef Pattern = Name;
-
-  if (Pattern.startswith("::"))
-    return FullName == Pattern;
-
-  return FullName.endswith(Pattern) &&
-         FullName.drop_back(Pattern.size()).endswith("::");
-}
-
 bool HasNameMatcher::matchesNode(const NamedDecl &Node) const {
-  // FIXME: There is still room for improvement, but it would require copying a
-  // lot of the logic from NamedDecl::printQualifiedName(). The benchmarks do
-  // not show like that extra complexity is needed right now.
+  assert(matchesNodeFullFast(Node) == matchesNodeFullSlow(Node));
   if (UseUnqualifiedMatch) {
-    assert(matchesNodeUnqualified(Node) == matchesNodeFull(Node));
+    assert(matchesNodeUnqualified(Node) == matchesNodeFullFast(Node));
     return matchesNodeUnqualified(Node);
   }
-  return matchesNodeFull(Node);
+  return matchesNodeFullFast(Node);
 }
 
 } // end namespace internal