Skip to content

Matcher Reference

June Rhodes edited this page Jun 28, 2024 · 1 revision

Clang for Unreal Engine supports all of the AST matchers that are built into the original Clang, plus additional matchers to support Unreal Engine code and a few other common scenarios.

Matchers from original Clang

For a list of matchers that are supported because they're in the original Clang, refer to the Upstream AST Matcher Reference document. Most of the matchers you are interested in will be found on that page.

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> hasUSpecifier std::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> hasUSpecifierValue std::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> hasUMetadata std::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> hasUMetadataValue std::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;
}

isMissingDllImportOrExport

Matcher<CXXRecordDecl>, Matcher<FunctionDecl>, Matcher<VarDecl> isMissingDllImportOrExport

Matches if the C++ class, struct, function or variable declaration could be marked __declspec(dllexport), but isn't. This can be used to find declarations in Public folders that should have the ..._API specifier on them, but don't.

This matcher only makes sense to use when targeting Windows, so make sure any rule that uses it is flagged with WindowsOnly: true in the .clang-rules file.

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

extern int A;
__declspec(dllexport) extern int B;

hasRedundantNamespacing

Matcher<ElaboratedTypeLoc> hasRedundantNamespacing

Matches if the type specifier has redundant namespace components.

For example, using this matcher would match the variable B and C but not A:

namespace Z::Y::X {
  class F {};
}

namespace Z::Y::W {
  X::F A;
  Y::X::F B;
  Z::Y::X::F C;
}

Traversal Matchers

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

forNone

Matcher<*> forNone Matcher<*>

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:

Match declared_field referenced_field
#1 A A
#2 B A
#3 C A
#4 D A
#5 A D
#6 B D
#7 C D
#8 D D

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:

Match declared_field referenced_field
#1 A A
#2 D D

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")))):

Match declared_field
#1 B
#2 C

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<*> forNoDescendant Matcher<*>

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> refersToPack Matcher<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> withUInterface Matcher<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> withIInterface Matcher<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"))))