diff --git a/clang-tools-extra/clang-tidy/misc/NonPrivateMemberVariablesInClassesCheck.cpp b/clang-tools-extra/clang-tidy/misc/NonPrivateMemberVariablesInClassesCheck.cpp --- a/clang-tools-extra/clang-tidy/misc/NonPrivateMemberVariablesInClassesCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/NonPrivateMemberVariablesInClassesCheck.cpp @@ -59,8 +59,9 @@ // If we are ok with public fields, then we only want to complain about // protected fields, else we want to complain about all non-private fields. // We can ignore public member variables in structs/classes, in unions. - auto InterestingField = fieldDecl( - IgnorePublicMemberVariables ? isProtected() : unless(isPrivate())); + auto InterestingField = IgnorePublicMemberVariables + ? fieldDecl(isProtected()) + : fieldDecl(unless(isPrivate())); // We only want the records that not only contain the mutable data (non-static // member variables), but also have some logic (non-static, non-implicit diff --git a/clang/docs/LibASTMatchersReference.html b/clang/docs/LibASTMatchersReference.html --- a/clang/docs/LibASTMatchersReference.html +++ b/clang/docs/LibASTMatchersReference.html @@ -2175,6 +2175,75 @@ +
Matches private C++ declarations and C++ base specifers that specify private +inheritance. + +Examples: + class C { + public: int a; + protected: int b; + private: int c; // fieldDecl(isPrivate()) matches 'c' + }; + + struct Base {}; + struct Derived1 : private Base {}; // matches 'Base' + class Derived2 : Base {}; // matches 'Base' +
Matches protected C++ declarations and C++ base specifers that specify +protected inheritance. + +Examples: + class C { + public: int a; + protected: int b; // fieldDecl(isProtected()) matches 'b' + private: int c; + }; + + class Base {}; + class Derived : protected Base {}; // matches 'Base' +
Matches public C++ declarations and C++ base specifers that specify public +inheritance. + +Examples: + class C { + public: int a; // fieldDecl(isPublic()) matches 'a' + protected: int b; + private: int c; + }; + + class Base {}; + class Derived1 : public Base {}; // matches 'Base' + struct Derived2 : Base {}; // matches 'Base' +
Matches declarations of virtual methods and C++ base specifers that specify +virtual inheritance. + +Example: + class A { + public: + virtual void x(); // matches x + }; + +Example: + class Base {}; + class DirectlyDerived : virtual Base {}; // matches Base + class IndirectlyDerived : DirectlyDerived, Base {}; // matches Base + +Usable as: Matcher<CXXMethodDecl>, Matcher<CXXBaseSpecifier> +
Matches if the given method declaration is virtual. +@@ -3012,44 +3088,52 @@ Matches declarations of virtual methods and C++ base specifers that specify +virtual inheritance. -Given +Example: class A { public: - virtual void x(); + virtual void x(); // matches x }; - matches A::x + +Example: + class Base {}; + class DirectlyDerived : virtual Base {}; // matches Base + class IndirectlyDerived : DirectlyDerived, Base {}; // matches Base + +Usable as: Matcher<CXXMethodDecl>, Matcher<CXXBaseSpecifier>- Matcher<Decl> isPrivate diff --git a/clang/include/clang/AST/ASTTypeTraits.h b/clang/include/clang/AST/ASTTypeTraits.h --- a/clang/include/clang/AST/ASTTypeTraits.h +++ b/clang/include/clang/AST/ASTTypeTraits.h @@ -16,6 +16,7 @@ #define LLVM_CLANG_AST_ASTTYPETRAITS_H #include "clang/AST/ASTFwd.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/TemplateBase.h" #include "clang/AST/TypeLoc.h" @@ -136,6 +137,7 @@ NKI_QualType, NKI_TypeLoc, NKI_LastKindWithoutPointerIdentity = NKI_TypeLoc, + NKI_CXXBaseSpecifier, NKI_CXXCtorInitializer, NKI_NestedNameSpecifier, NKI_Decl, @@ -198,6 +200,7 @@ KIND_TO_KIND_ID(Stmt) KIND_TO_KIND_ID(Type) KIND_TO_KIND_ID(OMPClause) +KIND_TO_KIND_ID(CXXBaseSpecifier) #define DECL(DERIVED, BASE) KIND_TO_KIND_ID(DERIVED##Decl) #include "clang/AST/DeclNodes.inc" #define STMT(DERIVED, BASE) KIND_TO_KIND_ID(DERIVED) @@ -510,6 +513,10 @@ struct DynTypedNode::BaseConverter< TypeLoc, void> : public ValueConverter Matches private C++ declarations. + Matches private C++ declarations and C++ base specifers that specify private +inheritance. -Given +Examples: class C { public: int a; protected: int b; - private: int c; + private: int c; // fieldDecl(isPrivate()) matches 'c' }; -fieldDecl(isPrivate()) - matches 'int c;' + + struct Base {}; + struct Derived1 : private Base {}; // matches 'Base' + class Derived2 : Base {}; // matches 'Base'- Matcher<Decl> isProtected @@ -7677,7 +7815,13 @@ void y(X &x) { x; X z; } class Y { friend class X; }; -Usable as: Matcher<Expr>, Matcher<ValueDecl> +Example matches class Derived +(matcher = cxxRecordDecl(hasAnyBase(hasType(cxxRecordDecl(hasName("Base")))))) +class Base {}; +class Derived : Base {}; + +Usable as: Matcher<Expr>, Matcher<FriendDecl>, Matcher<ValueDecl>, +Matcher<CXXBaseSpecifier> Matches protected C++ declarations. + Matches protected C++ declarations and C++ base specifers that specify +protected inheritance. -Given +Examples: class C { public: int a; - protected: int b; + protected: int b; // fieldDecl(isProtected()) matches 'b' private: int c; }; -fieldDecl(isProtected()) - matches 'int b;' + + class Base {}; + class Derived : protected Base {}; // matches 'Base'- Matcher<Decl> isPublic + Matches public C++ declarations. +@@ -5135,6 +5219,33 @@ Matches public C++ declarations and C++ base specifers that specify public +inheritance. -Given +Examples: class C { - public: int a; + public: int a; // fieldDecl(isPublic()) matches 'a' protected: int b; private: int c; }; -fieldDecl(isPublic()) - matches 'int a;' + + class Base {}; + class Derived1 : public Base {}; // matches 'Base' + struct Derived2 : Base {}; // matches 'Base'+ Matcher<CXXBaseSpecifier> hasType Matcher<Decl> InnerMatcher + + Overloaded to match the declaration of the expression's or value +declaration's type. + +In case of a value declaration (for example a variable declaration), +this resolves one layer of indirection. For example, in the value +declaration "X x;", cxxRecordDecl(hasName("X")) matches the declaration of +X, while varDecl(hasType(cxxRecordDecl(hasName("X")))) matches the +declaration of x. + +Example matches x (matcher = expr(hasType(cxxRecordDecl(hasName("X"))))) + and z (matcher = varDecl(hasType(cxxRecordDecl(hasName("X"))))) + and friend class X (matcher = friendDecl(hasType("X")) + class X {}; + void y(X &x) { x; X z; } + class Y { friend class X; }; + +Example matches class Derived +(matcher = cxxRecordDecl(hasAnyBase(hasType(cxxRecordDecl(hasName("Base")))))) +class Base {}; +class Derived : Base {}; + +Usable as: Matcher<Expr>, Matcher<FriendDecl>, Matcher<ValueDecl>, +Matcher<CXXBaseSpecifier> +Matcher<CXXConstructExpr> forEachArgumentWithParam Matcher<Expr> ArgMatcher, Matcher<ParmVarDecl> ParamMatcher + Matches all arguments and their respective ParmVarDecl. @@ -5518,6 +5629,21 @@+ Matcher<CXXRecordDecl> hasAnyBase Matcher<CXXBaseSpecifier> BaseSpecMatcher + + Matches C++ classes that have a direct or indirect base matching BaseSpecMatcher. + +Example matches DirectlyDerived, IndirectlyDerived (BaseSpecMatcher == +hasType(cxxRecordDecl(hasName("SpecialBase")))) class Foo; + class Bar : Foo {}; + class Baz : Bar {}; + class SpecialBase; + class DirectlyDerived : SpecialBase {}; // directly derived + class IndirectlyDerived : DirectlyDerived {}; // indirectly derived + +FIXME: Refactor this and isDerivedFrom to reuse implementation. +Matcher<CXXRecordDecl> hasMethod Matcher<CXXMethodDecl> InnerMatcher @@ -6289,7 +6421,13 @@ void y(X &x) { x; X z; } class Y { friend class X; }; -Usable as: Matcher<Expr>, Matcher<ValueDecl> +Example matches class Derived +(matcher = cxxRecordDecl(hasAnyBase(hasType(cxxRecordDecl(hasName("Base")))))) +class Base {}; +class Derived : Base {}; + +Usable as: Matcher<Expr>, Matcher<FriendDecl>, Matcher<ValueDecl>, +Matcher<CXXBaseSpecifier> Matches the first method of a class or struct that satisfies InnerMatcher. @@ -6075,7 +6201,13 @@ void y(X &x) { x; X z; } class Y { friend class X; }; -Usable as: Matcher<Expr>, Matcher<ValueDecl> +Example matches class Derived +(matcher = cxxRecordDecl(hasAnyBase(hasType(cxxRecordDecl(hasName("Base")))))) +class Base {}; +class Derived : Base {}; + +Usable as: Matcher<Expr>, Matcher<FriendDecl>, Matcher<ValueDecl>, +Matcher<CXXBaseSpecifier>{}; +template <> +struct DynTypedNode::BaseConverter + : public PtrConverter {}; + // The only operation we allow on unsupported types is \c get. // This allows to conveniently use \c DynTypedNode when having an arbitrary // AST node that is not supported, but prevents misuse - a user cannot create 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 @@ -47,6 +47,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Attr.h" +#include "clang/AST/CXXInheritance.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclFriend.h" @@ -548,52 +549,72 @@ extern const internal::VariadicDynCastAllOfMatcher templateTypeParmDecl; -/// Matches public C++ declarations. +/// Matches public C++ declarations and C++ base specifers that specify public +/// inheritance. /// -/// Given +/// Examples: /// \code /// class C { -/// public: int a; +/// public: int a; // fieldDecl(isPublic()) matches 'a' /// protected: int b; /// private: int c; /// }; /// \endcode -/// fieldDecl(isPublic()) -/// matches 'int a;' -AST_MATCHER(Decl, isPublic) { - return Node.getAccess() == AS_public; +/// +/// \code +/// class Base {}; +/// class Derived1 : public Base {}; // matches 'Base' +/// struct Derived2 : Base {}; // matches 'Base' +/// \endcode +AST_POLYMORPHIC_MATCHER(isPublic, + AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, + CXXBaseSpecifier)) { + return getAccessSpecifier(Node) == AS_public; } -/// Matches protected C++ declarations. +/// Matches protected C++ declarations and C++ base specifers that specify +/// protected inheritance. /// -/// Given +/// Examples: /// \code /// class C { /// public: int a; -/// protected: int b; +/// protected: int b; // fieldDecl(isProtected()) matches 'b' /// private: int c; /// }; /// \endcode -/// fieldDecl(isProtected()) -/// matches 'int b;' -AST_MATCHER(Decl, isProtected) { - return Node.getAccess() == AS_protected; +/// +/// \code +/// class Base {}; +/// class Derived : protected Base {}; // matches 'Base' +/// \endcode +AST_POLYMORPHIC_MATCHER(isProtected, + AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, + CXXBaseSpecifier)) { + return getAccessSpecifier(Node) == AS_protected; } -/// Matches private C++ declarations. +/// Matches private C++ declarations and C++ base specifers that specify private +/// inheritance. /// -/// Given +/// Examples: /// \code /// class C { /// public: int a; /// protected: int b; -/// private: int c; +/// private: int c; // fieldDecl(isPrivate()) matches 'c' /// }; /// \endcode -/// fieldDecl(isPrivate()) -/// matches 'int c;' -AST_MATCHER(Decl, isPrivate) { - return Node.getAccess() == AS_private; +/// +/// \code +/// struct Base {}; +/// struct Derived1 : private Base {}; // matches 'Base' +/// class Derived2 : Base {}; // matches 'Base' +/// \endcode +AST_POLYMORPHIC_MATCHER(isPrivate, + AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, + CXXBaseSpecifier)) { + return getAccessSpecifier(Node) == AS_private; } /// Matches non-static data members that are bit-fields. @@ -2839,6 +2860,26 @@ return Matcher (M).matches(*InterfaceDecl, Finder, Builder); } +/// Matches C++ classes that have a direct or indirect base matching \p +/// BaseSpecMatcher. +/// +/// Example: +/// matcher hasAnyBase(hasType(cxxRecordDecl(hasName("SpecialBase"))))) +/// \code +/// class Foo; +/// class Bar : Foo {}; +/// class Baz : Bar {}; +/// class SpecialBase; +/// class Proxy : SpecialBase {}; // matches Proxy +/// class IndirectlyDerived : Proxy {}; //matches IndirectlyDerived +/// \endcode +/// +// FIXME: Refactor this and isDerivedFrom to reuse implementation. +AST_MATCHER_P(CXXRecordDecl, hasAnyBase, internal::Matcher , + BaseSpecMatcher) { + return internal::matchesAnyBase(Node, BaseSpecMatcher, Finder, Builder); +} + /// Similar to \c isDerivedFrom(), but also matches classes that directly /// match \c Base. AST_POLYMORPHIC_MATCHER_P_OVERLOAD( @@ -3469,9 +3510,19 @@ /// class Y { friend class X; }; /// \endcode /// -/// Usable as: Matcher , Matcher +/// Example matches class Derived +/// (matcher = cxxRecordDecl(hasAnyBase(hasType(cxxRecordDecl(hasName("Base")))))) +/// \code +/// class Base {}; +/// class Derived : Base {}; +/// \endcode +/// +/// Usable as: Matcher , Matcher , Matcher , +/// Matcher AST_POLYMORPHIC_MATCHER_P_OVERLOAD( - hasType, AST_POLYMORPHIC_SUPPORTED_TYPES(Expr, FriendDecl, ValueDecl), + hasType, + AST_POLYMORPHIC_SUPPORTED_TYPES(Expr, FriendDecl, ValueDecl, + CXXBaseSpecifier), internal::Matcher , InnerMatcher, 1) { QualType QT = internal::getUnderlyingType(Node); if (!QT.isNull()) @@ -5177,17 +5228,28 @@ return Matched; } -/// Matches if the given method declaration is virtual. +/// Matches declarations of virtual methods and C++ base specifers that specify +/// virtual inheritance. /// -/// Given +/// Example: /// \code /// class A { /// public: -/// virtual void x(); +/// virtual void x(); // matches x /// }; /// \endcode -/// matches A::x -AST_MATCHER(CXXMethodDecl, isVirtual) { +/// +/// Example: +/// \code +/// class Base {}; +/// class DirectlyDerived : virtual Base {}; // matches Base +/// class IndirectlyDerived : DirectlyDerived, Base {}; // matches Base +/// \endcode +/// +/// Usable as: Matcher , Matcher +AST_POLYMORPHIC_MATCHER(isVirtual, + AST_POLYMORPHIC_SUPPORTED_TYPES(CXXMethodDecl, + CXXBaseSpecifier)) { return Node.isVirtual(); } diff --git a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h --- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -130,6 +130,9 @@ return TSI->getType(); return QualType(); } +inline QualType getUnderlyingType(const CXXBaseSpecifier &Node) { + return Node.getType(); +} /// Unifies obtaining the FunctionProtoType pointer from both /// FunctionProtoType and FunctionDecl nodes.. @@ -142,6 +145,15 @@ return Node.getType()->getAs (); } +/// Unifies obtaining the access specifier from Decl and CXXBaseSpecifier nodes. +inline clang::AccessSpecifier getAccessSpecifier(const Decl &Node) { + return Node.getAccess(); +} + +inline clang::AccessSpecifier getAccessSpecifier(const CXXBaseSpecifier &Node) { + return Node.getAccessSpecifier(); +} + /// Internal version of BoundNodes. Holds all the bound nodes. class BoundNodesMap { public: @@ -1929,6 +1941,13 @@ HasOverloadOpNameMatcher hasAnyOverloadedOperatorNameFunc(ArrayRef NameRefs); +/// Returns true if \p Node has a base specifier matching \p BaseSpec. +/// +/// A class is not considered to be derived from itself. +bool matchesAnyBase(const CXXRecordDecl &Node, + const Matcher &BaseSpecMatcher, + ASTMatchFinder *Finder, BoundNodesTreeBuilder *Builder); + } // namespace internal } // namespace ast_matchers diff --git a/clang/lib/AST/ASTTypeTraits.cpp b/clang/lib/AST/ASTTypeTraits.cpp --- a/clang/lib/AST/ASTTypeTraits.cpp +++ b/clang/lib/AST/ASTTypeTraits.cpp @@ -27,6 +27,7 @@ { NKI_None, "NestedNameSpecifierLoc" }, { NKI_None, "QualType" }, { NKI_None, "TypeLoc" }, + { NKI_None, "CXXBaseSpecifier" }, { NKI_None, "CXXCtorInitializer" }, { NKI_None, "NestedNameSpecifier" }, { NKI_None, "Decl" }, diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp --- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp +++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp @@ -68,6 +68,30 @@ BoundNodesTreeBuilder *Builder, ArrayRef InnerMatchers); +bool matchesAnyBase(const CXXRecordDecl &Node, + const Matcher &BaseSpecMatcher, + ASTMatchFinder *Finder, BoundNodesTreeBuilder *Builder) { + if (!Node.hasDefinition()) + return false; + + CXXBasePaths Paths; + Paths.setOrigin(&Node); + + const auto basePredicate = + [Finder, Builder, &BaseSpecMatcher](const CXXBaseSpecifier *BaseSpec, + CXXBasePath &IgnoredParam) { + BoundNodesTreeBuilder Result(*Builder); + if (BaseSpecMatcher.matches(*BaseSpec, Finder, Builder)) { + *Builder = std::move(Result); + return true; + } + return false; + }; + + return Node.lookupInBases(basePredicate, Paths, + /*LookupInDependent =*/true); +} + void BoundNodesTreeBuilder::visitMatches(Visitor *ResultVisitor) { if (Bindings.empty()) Bindings.push_back(BoundNodesMap()); diff --git a/clang/lib/ASTMatchers/Dynamic/Registry.cpp b/clang/lib/ASTMatchers/Dynamic/Registry.cpp --- a/clang/lib/ASTMatchers/Dynamic/Registry.cpp +++ b/clang/lib/ASTMatchers/Dynamic/Registry.cpp @@ -237,6 +237,7 @@ REGISTER_MATCHER(has); REGISTER_MATCHER(hasAncestor); REGISTER_MATCHER(hasAnyArgument); + REGISTER_MATCHER(hasAnyBase); REGISTER_MATCHER(hasAnyClause); REGISTER_MATCHER(hasAnyConstructorInitializer); REGISTER_MATCHER(hasAnyDeclaration); 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 @@ -3006,5 +3006,126 @@ EXPECT_TRUE(matchesWithOpenMP(Source6, Matcher)); } +TEST(HasAnyBase, DirectBase) { + EXPECT_TRUE(matches( + "struct Base {};" + "struct ExpectedMatch : Base {};", + cxxRecordDecl(hasName("ExpectedMatch"), + hasAnyBase(hasType(cxxRecordDecl(hasName("Base"))))))); +} + +TEST(HasAnyBase, IndirectBase) { + EXPECT_TRUE(matches( + "struct Base {};" + "struct Intermediate : Base {};" + "struct ExpectedMatch : Intermediate {};", + cxxRecordDecl(hasName("ExpectedMatch"), + hasAnyBase(hasType(cxxRecordDecl(hasName("Base"))))))); +} + +TEST(HasAnyBase, NoBase) { + EXPECT_TRUE(notMatches("struct Foo {};" + "struct Bar {};", + cxxRecordDecl(hasAnyBase(hasType(cxxRecordDecl()))))); +} + +TEST(IsPublicBase, Public) { + EXPECT_TRUE(matches("class Base {};" + "class Derived : public Base {};", + cxxRecordDecl(hasAnyBase(isPublic())))); +} + +TEST(IsPublicBase, DefaultAccessSpecifierPublic) { + EXPECT_TRUE(matches("class Base {};" + "struct Derived : Base {};", + cxxRecordDecl(hasAnyBase(isPublic())))); +} + +TEST(IsPublicBase, Private) { + EXPECT_TRUE(notMatches("class Base {};" + "class Derived : private Base {};", + cxxRecordDecl(hasAnyBase(isPublic())))); +} + +TEST(IsPublicBase, DefaultAccessSpecifierPrivate) { + EXPECT_TRUE(notMatches("class Base {};" + "class Derived : Base {};", + cxxRecordDecl(hasAnyBase(isPublic())))); +} + +TEST(IsPublicBase, Protected) { + EXPECT_TRUE(notMatches("class Base {};" + "class Derived : protected Base {};", + cxxRecordDecl(hasAnyBase(isPublic())))); +} + +TEST(IsPrivateBase, Private) { + EXPECT_TRUE(matches("class Base {};" + "class Derived : private Base {};", + cxxRecordDecl(hasAnyBase(isPrivate())))); +} + +TEST(IsPrivateBase, DefaultAccessSpecifierPrivate) { + EXPECT_TRUE(matches("struct Base {};" + "class Derived : Base {};", + cxxRecordDecl(hasAnyBase(isPrivate())))); +} + +TEST(IsPrivateBase, Public) { + EXPECT_TRUE(notMatches("class Base {};" + "class Derived : public Base {};", + cxxRecordDecl(hasAnyBase(isPrivate())))); +} + +TEST(IsPrivateBase, DefaultAccessSpecifierPublic) { + EXPECT_TRUE(notMatches("class Base {};" + "struct Derived : Base {};", + cxxRecordDecl(hasAnyBase(isPrivate())))); +} + +TEST(IsPrivateBase, Protected) { + EXPECT_TRUE(notMatches("class Base {};" + "class Derived : protected Base {};", + cxxRecordDecl(hasAnyBase(isPrivate())))); +} + +TEST(IsProtectedBase, Protected) { + EXPECT_TRUE(matches("class Base {};" + "class Derived : protected Base {};", + cxxRecordDecl(hasAnyBase(isProtected())))); +} + +TEST(IsProtectedBase, Public) { + EXPECT_TRUE(notMatches("class Base {};" + "class Derived : public Base {};", + cxxRecordDecl(hasAnyBase(isProtected())))); +} + +TEST(IsProtectedBase, Private) { + EXPECT_TRUE(notMatches("class Base {};" + "class Derived : private Base {};", + cxxRecordDecl(hasAnyBase(isProtected())))); +} + +TEST(IsVirtual, Directly) { + EXPECT_TRUE(matches("class Base {};" + "class Derived : virtual Base {};", + cxxRecordDecl(hasAnyBase(isVirtual())))); +} + +TEST(IsVirtual, Indirectly) { + EXPECT_TRUE( + matches("class Base {};" + "class Intermediate : virtual Base {};" + "class Derived : Intermediate {};", + cxxRecordDecl(hasName("Derived"), hasAnyBase(isVirtual())))); +} + +TEST(IsVirtual, NoVirtualBase) { + EXPECT_TRUE(notMatches("class Base {};" + "class Derived : Base {};", + cxxRecordDecl(hasAnyBase(isVirtual())))); +} + } // namespace ast_matchers } // namespace clang