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

Reply via email to