c++clangabstract-syntax-treeclang-ast-matchers

Matching template argument type name


I'm trying to match a varDecl() that is a template specialization with types of a certain name. For example:

template <typename T>
class C { };

C<char> var;

I would like to match var when the template argument is char. The matcher I'm using so far:

m varDecl(hasType(classTemplateSpecializationDecl(
    hasAnyTemplateArgument(templateArgument(
        # TODO: match type with the name 'char'
    ).bind("template_param"))
).bind("var")))

Output from clang-query:

Binding for "template_param":
TemplateArgument type 'char'
`-BuiltinType 0x13203c6b0 'char'

Binding for "var":
ClassTemplateSpecializationDecl 0x1538906f0 <var_test.cpp:3:1, line:4:11> col:7 class C definition implicit_instantiation
|-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
| |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr
| |-CopyConstructor simple trivial has_const_param implicit_has_const_param
| |-MoveConstructor exists simple trivial
| |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
| |-MoveAssignment exists simple trivial needs_implicit
| `-Destructor simple irrelevant trivial needs_implicit
|-TemplateArgument type 'A'
| `-RecordType 0x15386ca80 'A'
|   `-CXXRecord 0x15386c9f0 'A'
|-CXXRecordDecl 0x1538909c8 <col:1, col:7> col:7 implicit class C
|-CXXConstructorDecl 0x153890a88 <col:7> col:7 implicit used constexpr C 'void () noexcept' inline default trivial
| `-CompoundStmt 0x153891030 <col:7>
|-CXXConstructorDecl 0x153890c40 <col:7> col:7 implicit constexpr C 'void (const C<A> &)' inline default trivial noexcept-unevaluated 0x153890c40
| `-ParmVarDecl 0x153890d70 <col:7> col:7 'const C<A> &'
`-CXXConstructorDecl 0x153890e50 <col:7> col:7 implicit constexpr C 'void (C<A> &&)' inline default trivial noexcept-unevaluated 0x153890e50
  `-ParmVarDecl 0x153890f80 <col:7> col:7 'C<A> &&'

Things I've tried:

        refersToType(builtinType(hasName("char")))

Doesn't work:

3:22: Error building matcher builtinType.
3:34: Incorrect type for arg 1. (Expected = Matcher<BuiltinType>) != (Actual = Matcher<NamedDecl>)

refersToDeclaration(decl()) also doesn't seem to match anything.


Solution

  • Fixed matcher

    Use this matcher:

    m varDecl(hasType(classTemplateSpecializationDecl(
        hasAnyTemplateArgument(templateArgument(
            refersToType(asString("char"))
        ).bind("template_param"))
    ).bind("var")))
    

    That is, refersToType(asString("char")) goes where the TODO in the question was.

    With that change, C<char> var; is matched, while (say) C<int> var2; is not. (Tested with Clang-18.1.8 on Windows.)

    Explanation

    refersToType matches a TemplateArgument whose ArgKind is Type, meaning it carries a QualType.

    asString("char") then matches an argument QualType that, when stringified, is "char".

    Why do some other things not work?

    The question mentions trying:

    refersToType(builtinType(hasName("char")))
    

    This does not work because builtinType wants an argument that constrains a type, while hasName only constrains declarations. The latter point can be observed in the documentation by noting that hasName has a "return type" (i.e., it is) a Matcher<NamedDecl>, and NamedDecl is a kind of declaration (a syntactic notion) rather than a kind of type (a semantic notion).

    The question also mentions trying:

    refersToDeclaration(decl())
    

    This does not work because char is a type template argument, whereas refersToDeclaration only matches declaration template arguments. The latter would be something like C<&bar> (address of some variable), where the argument is effectively naming a specific declaration.

    Something I tried while experimenting is:

    refersToType(builtinType(asString("char")))
    

    This does not work because asString constrains QualType, whereas builtinType accepts a constraint on Type. (Note that QualType is not a kind of Type, rather it is a tuple containing a Type and optional qualifiers like const.) A constraint on BuiltinType specifically would work, but there aren't any; the set of matchers is unfortunately incomplete compared to what can be examined directly using the C++ API. Consequently, the builtinType refinement ends up being counterproductive here.