Skip to main content

Extended AST matchers

In addition to the AST matchers that Clang provides by default, clang-tidy for Unreal Engine adds additional matchers that are helpful when trying to match against Unreal Engine code. All of these extended AST matchers are detailed below.

Narrowing Matchers

Narrowing matchers match certain attributes on the current node, thus narrowing down the set of nodes of the current type to match on.

isUClass

Matcher<NamedDecl>isUClass

Matches if the declaration is a UCLASS().

For example, using this matcher would match UA but not B:

UCLASS()
class UA : public UObject
{
GENERATED_BODY();
};

class B
{
};

isUStruct

Matcher<NamedDecl>isUStruct

Matches if the declaration is a USTRUCT().

For example, using this matcher would match FA but not B:

USTRUCT()
struct FA
{
GENERATED_BODY();
};

struct B
{
};

isUInterface

Matcher<NamedDecl>isUInterface

Matches if the declaration is a UINTERFACE().

For example, using this matcher would match UI but not B (it also doesn't match II):

UINTERFACE()
class UI : public UInterface
{
GENERATED_BODY();
};

class II
{
GENERATED_BODY();
}

class B
{
};

isIInterface

Matcher<NamedDecl>isIInterface

Matches if the declaration is the associated interface of a UINTERFACE().

For example, using this matcher would match II but not UI:

UINTERFACE()
class UI : public UInterface
{
GENERATED_BODY();
};

class II
{
GENERATED_BODY();
}

class B
{
};

isUFunction

Matcher<NamedDecl>isUFunction

Matches if the declaration is a UFUNCTION().

For example, using this matcher would match A but not B:

UCLASS()
class UObj : public UObject
{
GENERATED_BODY();

public:
UFUNCTION()
void A();

void B();
};

isUProperty

Matcher<NamedDecl>isUProperty

Matches if the declaration is a UPROPERTY().

For example, using this matcher would match A but not B:

UCLASS()
class UObj : public UObject
{
GENERATED_BODY();

public:
UPROPERTY()
int A;

int B;
};

hasUSpecifier

Matcher<NamedDecl>hasUSpecifierstd::string Name

Matches if the declaration has Name as a specifier. Name is a case insensitive match. For the following code:

UPROPERTY(Replicated)
int A;

the A field declaration would be matched by hasUSpecifier("Replicated").

hasUSpecifierValue

Matcher<NamedDecl>hasUSpecifierValuestd::string Name, std::string Value

Matches if the declaration has Name as a specifier with the value Value. Name is a case insensitive match. Value is matched exactly. For the following code:

UPROPERTY(BlueprintGetter=Hello)
int A;

the A field declaration would be matched by hasUSpecifierValue("blueprintgetter", "Hello").

hasUMetadata

Matcher<NamedDecl>hasUMetadatastd::string Name

Matches if the declaration has Name as a specifier. Name is a case insensitive match. For the following code:

UPROPERTY(meta = (Category = "Hello"))
int A;

the A field declaration would be matched by hasUMetadata("Category").

hasUMetadataValue

Matcher<NamedDecl>hasUMetadataValuestd::string Name, std::string Value

Matches if the declaration has Name as a specifier with the value Value. Name is a case insensitive match. Value is matched exactly. For the following code:

UPROPERTY(meta = (Category = "Hello"))
int A;

the A field declaration would be matched by hasUMetadataValue("category", "Hello").

isPODType

Matcher<QualType>isPODType

Matches if the type is a Plain Old Data (POD) type.

For example, using this matcher would match the type of A but not the type of B:

class FStruct {
int A;

FString B;
}

Traversal Matchers

Traversal matchers specify the relationship to other nodes that are reachable from the current node.

forNone

Matcher<*>forNoneMatcher<*>

The exclusion version of forEach, this is intended to be used when you have at least one other forEach matcher in the expression, and you want exclude a set of nodes that meet that other condition.

It's best explained with an example. Let us say you have a class whose method refers to the field declarations through member access, and you want to find the fields that are not accessed in Method:

class Test {
int A;
int B;
int C;
int D;

void Method() {
this->A;
this->D;
}
}

If you were to use the matcher expression:

cxxMethodDecl(
hasName("Method"),
ofClass(
cxxRecordDecl(
forEach(
fieldDecl().bind("declared_field")
)
)
),
hasBody(
compoundStmt(
forEach(
memberExpr(
member(
fieldDecl().bind("referenced_field")
)
)
)
)
)
)

It would give you the following set of matches:

Matchdeclared_fieldreferenced_field
#1AA
#2BA
#3CA
#4DA
#5AD
#6BD
#7CD
#8DD

That is, it gives you every combination as a unique match result.

If you were to constrain the referenced_field so that it had to match the declared_field using equalsBoundNode within forEach(memberExpr(member(fieldDecl(equalsBoundNode("declared_field")).bind("referenced_field")))), it would give you the subset where both are equal:

Matchdeclared_fieldreferenced_field
#1AA
#2DD

forNone would give you the subset where there is no matching referenced_field for declared_field with forNone(memberExpr(member(fieldDecl(equalsBoundNode("declared_field")).bind("referenced_field")))):

Matchdeclared_field
#1B
#2C

Note that .bind() within a forNone has no effect, because any match that would generate bindings within a forNone would preclude the result from being included anyway.

forNoDescendant

Matcher<*>forNoDescendantMatcher<*>

forNoDescendant is the forEachDescendant equivalent of forNone. Rather than just checking if there are no immediate children that match the inner matcher, it checks all descendants.

refersToPack

Matcher<TemplateArgument>refersToPackMatcher<TemplateArgument> InnerMatcher

Where a given template argument matches a pack parameter (...), this iterates over all of template arguments contained within a pack, and matches if any of them match InnerMatcher.

withUInterface

Matcher<CXXRecordDecl>withUInterfaceMatcher<CXXRecordDecl> InnerMatcher

Where a given matcher is an interface class of a UINTERFACE() (that is, it is the ITheInterface to an interface declared as UTheInterface), this allows you to match on the the associated UInterface declaration.

For example, in the following code:

UINTERFACE()
class UTheInterface : public UInterface
{
GENERATED_BODY()
};

class ITheInterface
{
GENERATED_BODY()
};

Then the following matcher expression would match:

cxxRecordDecl(hasName("ITheInterface"), withUInterface(cxxRecordDecl(hasName("UTheInterface"))))

withIInterface

Matcher<CXXRecordDecl>withIInterfaceMatcher<CXXRecordDecl> InnerMatcher

Where a given matcher is a UINTERFACE() (that is, it is the UTheInterface which has an interface class ITheInterface declared), this allows you to match on the the associated interface class declaration.

For example, in the following code:

UINTERFACE()
class UTheInterface : public UInterface
{
GENERATED_BODY()
};

class ITheInterface
{
GENERATED_BODY()
};

Then the following matcher expression would match:

cxxRecordDecl(hasName("UTheInterface"), withIInterface(cxxRecordDecl(hasName("ITheInterface"))))