diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -462,6 +462,10 @@ bool isInAnonymousNamespace() const; + /// Helper to check if a Decl in a specific top level namespace, while inline + /// namespaces will be skipped. + bool isInTopNamespace(llvm::StringRef) const; + bool isInStdNamespace() const; ASTContext &getASTContext() const LLVM_READONLY; @@ -1943,6 +1947,8 @@ bool isNamespace() const { return getDeclKind() == Decl::Namespace; } + bool isTopNamespace(llvm::StringRef Namespace) const; + bool isStdNamespace() const; bool isInlineNamespace() const; diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h --- a/clang/include/clang/ASTMatchers/ASTMatchers.h +++ b/clang/include/clang/ASTMatchers/ASTMatchers.h @@ -7675,6 +7675,29 @@ return Node.isAnonymousNamespace(); } +/// Matches declarations in the top level namespace, but not in nested +/// namespaces. This is because nested namespaces are designed for +/// implementation details, we don't need to match them. +/// +/// Given +/// \code +/// class shared_ptr {}; +/// namespace boost { +/// class shared_ptr {}; #1 +/// namespace detail { +/// class shared_ptr {}; +/// } +/// inline namespace __1 { +/// class shared_ptr {}; #2 +/// } +/// } +/// \endcode +/// cxxRecordDecl(hasName("shared_ptr"), isInTopNamespace("boost")) will match +/// #1 and #2. +AST_MATCHER_P(Decl, isInTopNamespace, llvm::StringRef, Namespace) { + return Node.isInTopNamespace(Namespace); +} + /// Matches declarations in the namespace `std`, but not in nested namespaces. /// /// Given diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -391,9 +391,11 @@ return false; } -bool Decl::isInStdNamespace() const { +bool Decl::isInStdNamespace() const { return isInTopNamespace("std"); } + +bool Decl::isInTopNamespace(llvm::StringRef Namespace) const { const DeclContext *DC = getDeclContext(); - return DC && DC->isStdNamespace(); + return DC && DC->isTopNamespace(Namespace); } TranslationUnitDecl *Decl::getTranslationUnitDecl() { @@ -1123,20 +1125,21 @@ cast(this)->isInline(); } -bool DeclContext::isStdNamespace() const { +bool DeclContext::isStdNamespace() const { return isTopNamespace("std"); } +bool DeclContext::isTopNamespace(llvm::StringRef Namespace) const { if (!isNamespace()) return false; const auto *ND = cast(this); if (ND->isInline()) { - return ND->getParent()->isStdNamespace(); + return ND->getParent()->isTopNamespace(Namespace); } if (!getParent()->getRedeclContext()->isTranslationUnit()) return false; const IdentifierInfo *II = ND->getIdentifier(); - return II && II->isStr("std"); + return II && II->isStr(Namespace); } bool DeclContext::isDependentContext() const { diff --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp --- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp +++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp @@ -3478,6 +3478,33 @@ EXPECT_TRUE(matches("namespace {}", namespaceDecl(isAnonymous()))); } +TEST_P(ASTMatchersTest, InTopNamespace) { + if (!GetParam().isCXX()) { + return; + } + EXPECT_TRUE(notMatches( + "namespace boost {" + " namespace detail {" + " class shared_ptr {};" + " }" + "}", + cxxRecordDecl(hasName("shared_ptr"), isInTopNamespace("boost")))); + + EXPECT_TRUE( + matches("namespace boost {" + " class shared_ptr {};" + "}", + cxxRecordDecl(hasName("shared_ptr"), isInTopNamespace("boost")))); + + EXPECT_TRUE( + matches("namespace boost {" + " inline namespace __1 {" + " class shared_ptr {};" + " }" + "}", + cxxRecordDecl(hasName("shared_ptr"), isInTopNamespace("boost")))); +} + TEST_P(ASTMatchersTest, InStdNamespace) { if (!GetParam().isCXX()) { return;