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?
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.