|
| 1 | +## Recursive Rewrite Type <Badge type="tip" text="Has Fix" /> |
| 2 | + |
| 3 | + |
| 4 | +* [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJzbWFydCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoicmV3cml0ZXJzOlxyXG4tIGlkOiBvcHRpb25hbFxyXG4gIGxhbmd1YWdlOiBQeXRob25cclxuICBydWxlOlxyXG4gICAgYW55OlxyXG4gICAgLSBwYXR0ZXJuOlxyXG4gICAgICAgIGNvbnRleHQ6ICdhcmc6IE9wdGlvbmFsWyRUWVBFXSdcclxuICAgICAgICBzZWxlY3RvcjogZ2VuZXJpY190eXBlXHJcbiAgICAtIHBhdHRlcm46IE9wdGlvbmFsWyRUWVBFXVxyXG4gIHRyYW5zZm9ybTpcclxuICAgIE5UOlxyXG4gICAgICByZXdyaXRlOiBcclxuICAgICAgICByZXdyaXRlcnM6IFtvcHRpb25hbCwgdW5pb25zXVxyXG4gICAgICAgIHNvdXJjZTogJFRZUEVcclxuICBmaXg6ICROVCB8IE5vbmVcclxuLSBpZDogdW5pb25zXHJcbiAgbGFuZ3VhZ2U6IFB5dGhvblxyXG4gIHJ1bGU6XHJcbiAgICBwYXR0ZXJuOlxyXG4gICAgICBjb250ZXh0OiAnYTogVW5pb25bJCQkVFlQRVNdJ1xyXG4gICAgICBzZWxlY3RvcjogZ2VuZXJpY190eXBlXHJcbiAgdHJhbnNmb3JtOlxyXG4gICAgVU5JT05TOlxyXG4gICAgICByZXdyaXRlOlxyXG4gICAgICAgIHJld3JpdGVyczpcclxuICAgICAgICAgIC0gcmV3cml0ZS11bmlvbnNcclxuICAgICAgICBzb3VyY2U6ICQkJFRZUEVTXHJcbiAgICAgICAgam9pbkJ5OiBcIiB8IFwiXHJcbiAgZml4OiAkVU5JT05TXHJcbi0gaWQ6IHJld3JpdGUtdW5pb25zXHJcbiAgcnVsZTpcclxuICAgIHBhdHRlcm46ICRUWVBFXHJcbiAgICBraW5kOiB0eXBlXHJcbiAgdHJhbnNmb3JtOlxyXG4gICAgTlQ6XHJcbiAgICAgIHJld3JpdGU6IFxyXG4gICAgICAgIHJld3JpdGVyczogW29wdGlvbmFsLCB1bmlvbnNdXHJcbiAgICAgICAgc291cmNlOiAkVFlQRVxyXG4gIGZpeDogJE5UXHJcbnJ1bGU6XHJcbiAga2luZDogdHlwZVxyXG4gIHBhdHRlcm46ICRUUEVcclxudHJhbnNmb3JtOlxyXG4gIE5FV19UWVBFOlxyXG4gICAgcmV3cml0ZTogXHJcbiAgICAgIHJld3JpdGVyczogW29wdGlvbmFsLCB1bmlvbnNdXHJcbiAgICAgIHNvdXJjZTogJFRQRVxyXG5maXg6ICRORVdfVFlQRSIsInNvdXJjZSI6InJlc3VsdHM6ICBPcHRpb25hbFtVbmlvbltMaXN0W1VuaW9uW3N0ciwgZGljdF1dLCBzdHJdXVxuIn0=) |
| 5 | + |
| 6 | +### Description |
| 7 | + |
| 8 | +Suppose we want to transform Python's `Union[T1, T2]` to `T1 | T2` and `Optional[T]` to `T | None`. |
| 9 | + |
| 10 | +By default, ast-grep will only fix the outermost node that matches a pattern and will not rewrite the inner AST nodes inside a match. This avoids unexpected rewriting or infinite rewriting loop. |
| 11 | + |
| 12 | +So if you are using non-recurisve rewriter like [this](https://github.com/ast-grep/ast-grep/discussions/1566#discussion-7401382), `Optional[Union[int, str]]` will only be converted to `Union[int, str] | None`. Note the inner `Union[int, str]` is not enabled. This is because the rewriter `optional` matches `Optional[$TYPE]` and rewrite it to `$TYPE | None`. The inner `$TYPE` is not processed. |
| 13 | + |
| 14 | +However, we can apply `rewriters` to inner types recursively. Take the `optional` rewriter as an example, we need to apply rewriters, `optional` and `unioins`, **recursively** to `$TYPE` and get a new variable `$NT`. |
| 15 | + |
| 16 | +### YAML |
| 17 | +```yml |
| 18 | +id: recursive-rewrite-types |
| 19 | +language: python |
| 20 | +rewriters: |
| 21 | +# rewrite Optional[T] to T | None |
| 22 | +- id: optional |
| 23 | + rule: |
| 24 | + any: |
| 25 | + - pattern: |
| 26 | + context: 'arg: Optional[$TYPE]' |
| 27 | + selector: generic_type |
| 28 | + - pattern: Optional[$TYPE] |
| 29 | + # recursively apply rewriters to $TYPE |
| 30 | + transform: |
| 31 | + NT: |
| 32 | + rewrite: |
| 33 | + rewriters: [optional, unions] |
| 34 | + source: $TYPE |
| 35 | + # use the new variable $NT |
| 36 | + fix: $NT | None |
| 37 | + |
| 38 | +# similar to Optional, rewrite Union[T1, T2] to T1 | T2 |
| 39 | +- id: unions |
| 40 | + language: Python |
| 41 | + rule: |
| 42 | + pattern: |
| 43 | + context: 'a: Union[$$$TYPES]' |
| 44 | + selector: generic_type |
| 45 | + transform: |
| 46 | + UNIONS: |
| 47 | + # rewrite all types inside $$$TYPES |
| 48 | + rewrite: |
| 49 | + rewriters: [ rewrite-unions ] |
| 50 | + source: $$$TYPES |
| 51 | + joinBy: " | " |
| 52 | + fix: $UNIONS |
| 53 | +- id: rewrite-unions |
| 54 | + rule: |
| 55 | + pattern: $TYPE |
| 56 | + kind: type |
| 57 | + # recursive part |
| 58 | + transform: |
| 59 | + NT: |
| 60 | + rewrite: |
| 61 | + rewriters: [optional, unions] |
| 62 | + source: $TYPE |
| 63 | + fix: $NT |
| 64 | + |
| 65 | +# find all types |
| 66 | +rule: |
| 67 | + kind: type |
| 68 | + pattern: $TPE |
| 69 | +# apply the recursive rewriters |
| 70 | +transform: |
| 71 | + NEW_TYPE: |
| 72 | + rewrite: |
| 73 | + rewriters: [optional, unions] |
| 74 | + source: $TPE |
| 75 | +# output |
| 76 | +fix: $NEW_TYPE |
| 77 | +``` |
| 78 | +
|
| 79 | +
|
| 80 | +### Example |
| 81 | +
|
| 82 | +<!-- highlight matched code in curly-brace {lineNum} --> |
| 83 | +```python |
| 84 | +results: Optional[Union[List[Union[str, dict]], str]] |
| 85 | +``` |
| 86 | +
|
| 87 | +### Diff |
| 88 | +<!-- use # [!code --] and # [!code ++] to annotate diff --> |
| 89 | +```python |
| 90 | +results: Optional[Union[List[Union[str, dict]], str]] # [!code --] |
| 91 | +results: List[str | dict] | str | None #[!code ++] |
| 92 | +``` |
| 93 | +
|
| 94 | +### Contributed by |
| 95 | +Inspired by [steinuil](https://github.com/ast-grep/ast-grep/discussions/1566) |
0 commit comments