Search code examples
clangllvmabstract-syntax-treeclang-tidy

Matching std::optional<bool> with Clang AST


I'm trying to write a clang-tidy check for std::optional<bool> r = 5 to catch implicit conversions to bool.

|-DeclStmt <line:4:5, col:30>
| `-VarDecl <col:5, col:29> col:25 r 'std::optional<bool>':'std::optional<bool>' cinit
|   `-ExprWithCleanups <col:29> 'std::optional<bool>':'std::optional<bool>'
|     `-ImplicitCastExpr <col:29> 'std::optional<bool>':'std::optional<bool>' <ConstructorConversion>
|       `-CXXConstructExpr <col:29> 'std::optional<bool>':'std::optional<bool>' 'void (int &&) noexcept(is_nothrow_constructible_v<bool, int>)'
|         `-MaterializeTemporaryExpr <col:29> 'int' xvalue
|           `-IntegerLiteral <col:29> 'int' 5

So far, I have match implicitCastExpr(hasDescendant(cxxConstructExpr())) where I'm matching for an implicitCastExpr with a cxxConstructoExpr. The problem is I want to narrow the match on cxxConstructExpr to find only cases where bool is the template argument. Does anyone know how to do this?


Solution

  • Inside cxxConstructExpr(...), you also need to use hasType, classTemplateSpecializationDecl, hasTemplateArgument, refersToType, and booleanType.

    Here is a shell script invoking clang-query that finds implicit conversions to std::optional<bool> from a type other than bool:

    #!/bin/sh
    
    query='m
      implicitCastExpr(                              # Implicit conversion
        hasSourceExpression(                         # from a
          cxxConstructExpr(                          # constructor expr
            hasType(                                 # whose type is
              classTemplateSpecializationDecl(       # a template specialization
                hasName("::std::optional"),          # of std::optional
                hasTemplateArgument(                 # with template argument
                  0, refersToType(booleanType())     # bool,
                )
              )
            ),
            unless(                                  # unless
              hasArgument(                           # the constructor argument
                0, expr(                             # is an expr with
                  hasType(                           # type
                    booleanType()                    # bool.
                  )
                )
              )
            )
          )
        )
      )'
    
    clang-query -c="$query" "$@"
    

    (I use a shell script so I can format the query expression and add comments.)

    Test input test.cc:

    // test.cc
    // Test clang-query finding implicit conversions to optional<bool>.
    
    #include <optional>                    // std::optional
    
    void f()
    {
      std::optional<bool> r = 5;           // reported
    
      std::optional<int>  s = 6;           // not reported
    
      std::optional<bool> t = false;       // not reported
    }
    
    // EOF
    

    Invocation of the script (saved as cmd.sh):

    $ ./cmd.sh test.cc -- --std=c++17 
    
    Match #1:
    
    [...path...]/test.cc:8:27: note: "root" binds here
      std::optional<bool> r = 5;           // reported
                              ^
    1 match.
    

    I used Clang+LLVM-14.0.0, although I don't think I've used anything particularly recent here.

    Figuring out these match expressions can be quite difficult. The main reference is the AST Matcher Reference, but even with that, it often requires a lot of trial and error.