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

Samuel Benzaquen via cfe-commits cfe-commits at lists.llvm.org
Fri Feb 5 10:29:24 PST 2016


Author: sbenza
Date: Fri Feb  5 12:29:24 2016
New Revision: 259898

URL: http://llvm.org/viewvc/llvm-project?rev=259898&view=rev
Log:
[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

Modified:
    cfe/trunk/include/clang/ASTMatchers/ASTMatchersInternal.h
    cfe/trunk/lib/ASTMatchers/ASTMatchersInternal.cpp
    cfe/trunk/unittests/ASTMatchers/ASTMatchersTest.cpp

Modified: cfe/trunk/include/clang/ASTMatchers/ASTMatchersInternal.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/ASTMatchers/ASTMatchersInternal.h?rev=259898&r1=259897&r2=259898&view=diff
==============================================================================
--- cfe/trunk/include/clang/ASTMatchers/ASTMatchersInternal.h (original)
+++ cfe/trunk/include/clang/ASTMatchers/ASTMatchersInternal.h Fri Feb  5 12:29:24 2016
@@ -653,10 +653,18 @@ class HasNameMatcher : public SingleNode
 
   /// \brief Full match routine
   ///
+  /// Fast implementation for the simple case of a named declaration at
+  /// namespace or RecordDecl scope.
+  /// It is slower than matchesNodeUnqualified, but faster than
+  /// matchesNodeFullSlow.
+  bool matchesNodeFullFast(const NamedDecl &Node) const;
+
+  /// \brief Full match routine
+  ///
   /// It generates the fully qualified name of the declaration (which is
   /// expensive) before trying to match.
   /// It is slower but simple and works on all cases.
-  bool matchesNodeFull(const NamedDecl &Node) const;
+  bool matchesNodeFullSlow(const NamedDecl &Node) const;
 
   const bool UseUnqualifiedMatch;
   const std::string Name;

Modified: cfe/trunk/lib/ASTMatchers/ASTMatchersInternal.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/ASTMatchers/ASTMatchersInternal.cpp?rev=259898&r1=259897&r2=259898&view=diff
==============================================================================
--- cfe/trunk/lib/ASTMatchers/ASTMatchersInternal.cpp (original)
+++ cfe/trunk/lib/ASTMatchers/ASTMatchersInternal.cpp Fri Feb  5 12:29:24 2016
@@ -298,45 +298,149 @@ HasNameMatcher::HasNameMatcher(StringRef
   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 false;
+
+  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::matchesNodeFull(const NamedDecl &Node) const {
-  llvm::SmallString<128> NodeName = StringRef("::");
-  llvm::raw_svector_ostream OS(NodeName);
-  Node.printQualifiedName(OS);
-  const StringRef FullName = OS.str();
+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;
 
-  if (Pattern.startswith("::"))
-    return FullName == Pattern;
+  const bool SkipUnwrittenCases[] = {false, true};
+  for (bool SkipUnwritten : SkipUnwrittenCases) {
+    llvm::SmallString<128> NodeName = StringRef("::");
+    llvm::raw_svector_ostream OS(NodeName);
 
-  return FullName.endswith(Pattern) &&
-         FullName.drop_back(Pattern.size()).endswith("::");
+    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::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

Modified: cfe/trunk/unittests/ASTMatchers/ASTMatchersTest.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/ASTMatchers/ASTMatchersTest.cpp?rev=259898&r1=259897&r2=259898&view=diff
==============================================================================
--- cfe/trunk/unittests/ASTMatchers/ASTMatchersTest.cpp (original)
+++ cfe/trunk/unittests/ASTMatchers/ASTMatchersTest.cpp Fri Feb  5 12:29:24 2016
@@ -2827,6 +2827,52 @@ TEST(Matcher, HasNameSupportsOuterClasse
                  recordDecl(hasName("A+B::C"))));
 }
 
+TEST(Matcher, HasNameSupportsInlinedNamespaces) {
+  std::string code = "namespace a { inline namespace b { class C; } }";
+  EXPECT_TRUE(matches(code, recordDecl(hasName("a::b::C"))));
+  EXPECT_TRUE(matches(code, recordDecl(hasName("a::C"))));
+  EXPECT_TRUE(matches(code, recordDecl(hasName("::a::b::C"))));
+  EXPECT_TRUE(matches(code, recordDecl(hasName("::a::C"))));
+}
+
+TEST(Matcher, HasNameSupportsAnonymousNamespaces) {
+  std::string code = "namespace a { namespace { class C; } }";
+  EXPECT_TRUE(
+      matches(code, recordDecl(hasName("a::(anonymous namespace)::C"))));
+  EXPECT_TRUE(matches(code, recordDecl(hasName("a::C"))));
+  EXPECT_TRUE(
+      matches(code, recordDecl(hasName("::a::(anonymous namespace)::C"))));
+  EXPECT_TRUE(matches(code, recordDecl(hasName("::a::C"))));
+}
+
+TEST(Matcher, HasNameSupportsAnonymousOuterClasses) {
+  EXPECT_TRUE(matches("class A { class { class C; } x; };",
+                      recordDecl(hasName("A::(anonymous class)::C"))));
+  EXPECT_TRUE(matches("class A { class { class C; } x; };",
+                      recordDecl(hasName("::A::(anonymous class)::C"))));
+  EXPECT_FALSE(matches("class A { class { class C; } x; };",
+                       recordDecl(hasName("::A::C"))));
+  EXPECT_TRUE(matches("class A { struct { class C; } x; };",
+                      recordDecl(hasName("A::(anonymous struct)::C"))));
+  EXPECT_TRUE(matches("class A { struct { class C; } x; };",
+                      recordDecl(hasName("::A::(anonymous struct)::C"))));
+  EXPECT_FALSE(matches("class A { struct { class C; } x; };",
+                       recordDecl(hasName("::A::C"))));
+}
+
+TEST(Matcher, HasNameSupportsFunctionScope) {
+  std::string code =
+      "namespace a { void F(int a) { struct S { int m; }; int i; } }";
+  EXPECT_TRUE(matches(code, varDecl(hasName("i"))));
+  EXPECT_FALSE(matches(code, varDecl(hasName("F()::i"))));
+
+  EXPECT_TRUE(matches(code, fieldDecl(hasName("m"))));
+  EXPECT_TRUE(matches(code, fieldDecl(hasName("S::m"))));
+  EXPECT_TRUE(matches(code, fieldDecl(hasName("F(int)::S::m"))));
+  EXPECT_TRUE(matches(code, fieldDecl(hasName("a::F(int)::S::m"))));
+  EXPECT_TRUE(matches(code, fieldDecl(hasName("::a::F(int)::S::m"))));
+}
+
 TEST(Matcher, IsDefinition) {
   DeclarationMatcher DefinitionOfClassA =
       recordDecl(hasName("A"), isDefinition());




More information about the cfe-commits mailing list