Issue |
89821
|
Summary |
Odd (or perhaps inconsistent) behavior around `IgnoreUnlessSpelledInSource`
|
Labels |
clang:tooling
|
Assignees |
|
Reporter |
tJener
|
I'm noticing some odd behavior around the `IgnoreUnlessSpelledInSource` traversal mode, in particular when that mode changes.
---
Suppose we have the following source:
```c++
struct A {
~A();
};
struct B {
explicit B(A);
};
void f() { B(A{}); }
```
For the sake of discussion, assume that the AST of the function `f` is as follows:
```
α FunctionDecl <line:9:1, col:20> col:6 f 'void ()'
β `-CompoundStmt <col:10, col:20>
γ `-ExprWithCleanups <col:12, col:17> 'B'
δ `-CXXFunctionalCastExpr <col:12, col:17> 'B' functional cast to B <ConstructorConversion>
ε `-CXXConstructExpr <col:12, col:17> 'B' 'void (A)'
ζ `-CXXFunctionalCastExpr <col:14, col:16> 'A' functional cast to A <NoOp>
η `-CXXBindTemporaryExpr <col:15, col:16> 'A' (CXXTemporary 0xc230d88)
θ `-InitListExpr <col:15, col:16> 'A'
```
The AST nodes `ε`, `ζ`, and `θ` are "spelled in source" by the definition of `expr->IgnoreUnlessSpelledInSource() == expr`.
We look at some matchers and what nodes they match.
---
```
cxxConstructExpr()
```
We match the `CXXConstructExpr`. Nothing surprising.
---
```
traverse(TK_IgnoreUnlessSpelledInSource, cxxConstructExpr())
```
We match the `CXXConstructExpr`. Again, nothing surprising.
---
```
expr(traverse(TK_IgnoreUnlessSpelledInSource, cxxConstructExpr()))
```
We match the `CXXConstructExpr`. From my perspective, this is matching `expr` in the `AsIs` traversal mode, switching to `IgnoreUnlessSpelledInSource`, and then refining the match to `CXXConstructExpr`s that are spelled in source. Given that understanding, the result makes sense.
---
```
expr(anyOf(expr(traverse(clang::TK_IgnoreUnlessSpelledInSource,
cxxConstructExpr())),
unless(anything())))
```
Notes regarding the matcher:
* `anyOf` doesn't allow me to only provide a single argument, so I'm using `unless(anything())` to match nothing.
* The outermost `expr` is to resolve an ambiguous call to `MatchFinder::addMatcher`.
We match the `CXXConstructExpr`... *three* times. For each of `γ`, `δ`, and `ε`, the `traverse` matcher ignores the nodes not spelled in source and ends up passing the `CXXConstructExpr` to its inner matchers.
>From my perspective, this matcher is no different than the previous matcher, where without any traversal matchers (referencing the categorization in the [AST Matcher Reference](https://clang.llvm.org/docs/LibASTMatchersReference.html), not the `traverse` matcher), the node matchers are refining the current node.
---
Perhaps this not a good idea to do. However, when writing generic utilities that allow arbitrary matchers to be composed, it's a question that needs to be answered. In particular, the path that led me to this rabbit trail was `clang::tooling::Transformer` and its `RewriteRule`s.
https://github.com/llvm/llvm-project/blob/0c032fd5425d853dfc577e607b9c179d811cec19/clang/lib/Tooling/Transformer/RewriteRule.cpp#L425-L438
When a `RewriteRule` composes its matchers, it does so with a variadic `AnyOf` matcher. A user writing something like
```
makeRule(traverse(clang::TK_IgnoreUnlessSpelledInSource,
cxxConstructExpr().bind("ctor")),
insertBefore(node("ctor"), cat("/*hi*/")))
```
inadvertently trips over this, matching multiple times where it seems like it should only match once.
_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs