Index: include/clang/ASTMatchers/ASTMatchersInternal.h =================================================================== --- include/clang/ASTMatchers/ASTMatchersInternal.h +++ include/clang/ASTMatchers/ASTMatchersInternal.h @@ -631,10 +631,18 @@ /// \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; Index: lib/ASTMatchers/ASTMatchersInternal.cpp =================================================================== --- lib/ASTMatchers/ASTMatchersInternal.cpp +++ 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 MatchNameSuffix(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 MatchNodeName(StringRef &Name, const NamedDecl &Node) { + // Simple name. + if (Node.getIdentifier()) + return MatchNameSuffix(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 MatchNameSuffix(Name, OS.str()); } - return false; + + return MatchNameSuffix(Name, "(anonymous)"); } -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(); +} // namespace + +bool HasNameMatcher::matchesNodeUnqualified(const NamedDecl &Node) const { + assert(UseUnqualifiedMatch); + StringRef NodeName = Name; + return MatchNodeName(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 (!MatchNodeName(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(Ctx); + Ctx = Ctx->getParent()) { + if (const auto *ND = dyn_cast(Ctx)) { + StringRef NSName = + ND->isAnonymousNamespace() ? "(anonymous namespace)" : ND->getName(); + + // If it matches, continue. + if (MatchNameSuffix(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(Ctx)) { + if (!isa(Ctx)) { + if (RD->getIdentifier()) { + if (MatchNameSuffix(Pattern, RD->getName())) + continue; + } else { + llvm::SmallString<128> NodeName; + NodeName += StringRef("(anonymous "); + NodeName += RD->getKindName(); + NodeName += ')'; + if (MatchNameSuffix(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(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); + + if (SkipUnwritten) { + PrintingPolicy Policy = Node.getASTContext().getPrintingPolicy(); + Policy.SuppressUnwrittenScope = true; + Node.printQualifiedName(OS, Policy); + } else { + Node.printQualifiedName(OS); + } + + const StringRef FullName = OS.str(); - return FullName.endswith(Pattern) && - FullName.drop_back(Pattern.size()).endswith("::"); + 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 Index: unittests/ASTMatchers/ASTMatchersTest.cpp =================================================================== --- unittests/ASTMatchers/ASTMatchersTest.cpp +++ unittests/ASTMatchers/ASTMatchersTest.cpp @@ -2644,6 +2644,52 @@ 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());