[inductor] Replace `Any` with `object` in pattern_matcher structural equality (#194540)
`typing.Any` disables type checking at every use site, so a parameter typed `Any`
silently accepts a caller mistake and silently permits any attribute access in
the body. `object` keeps the "accepts anything" contract for the caller while
forcing the body to narrow before it dereferences, which is exactly the contract
these functions already implement at runtime.
No runtime behavior changes.
### Review order
**The nine `pattern_eq` overloads** are the core of the change. The base is
`return isinstance(other, self.__class__)`, and eight of the nine overrides
already established that `other` is a `Self` before touching an attribute — but
in the wrong order. Each opened with
```python
other = typing.cast(Self, other) # super makes sure this is true
return super().pattern_eq(other) and self.name == other.name
```
The comment was false at the point the cast ran, and the code was safe only
because `and` short-circuits before the first attribute read. A reader who split
that chain into statements would get an `AttributeError` on arbitrary input with
no type-checker warning, since the cast had already asserted `Self`. Each
override now checks first and casts second:
```python
if not super().pattern_eq(other):
return False
other = typing.cast(Self, other)
return self.name == other.name
```
The base's docstring records the contract once rather than repeating a comment
eight times. I deliberately kept the `super().pattern_eq(other)` calls rather
than replacing them with a local `isinstance(other, type(self))`: for
`_TargetArgsExpr` the super call is `_TargetExpr.pattern_eq`, which compares
`op`/`fns`/`users` and is *not* redundant with an isinstance check.
**`_constant_values_equal`, `_python_constant_repr`, and
`PatternPrettyPrinter.pretty_print`** are fully isinstance-guarded dispatch over
untrusted constants, each ending in a fallback (`a == b`, `repr(value)`) valid
for any object. **`GetAttr.__init__`'s `value`** is only stored and then fed back
through those same three functions. **`_not_implemented`** raises
unconditionally, so widening its varargs is free.
`pretty_print` was the one site expected to need a cast, since its last branch is
`if hasattr(obj, "pretty_print"): return obj.pretty_print(self)`. Pyrefly narrows
on `hasattr`, so it type checks under `object` as-is and is included rather than
left behind as the file's last predicate `Any`.
Finally, the two `# pyrefly: ignore [bad-override]` suppressions on
`Converter.call_method`/`call_module` are removed. They were **already dead
before this change** (verified below); since this PR touches the
`_not_implemented` they point at, leaving them would mask the next real override
break at those lines.
### Deliberately out of scope: `Constant = Any`
`Constant = Any` at line 92 is left alone. `Constant = object` costs only 2
net-new pyrefly errors, but they are not cosmetic — they surface that
`MatchContext.pattern_to_node` is declared `dict[PatternExpr, Node | None]` while
`MatchContext.match` assigns a possibly-constant `node` into it:
```
ERROR Argument `Node | object` is not assignable to parameter `node` with type
`Node` in function `PatternExpr._match` --> pattern_matcher.py:512
ERROR Cannot set item in `dict[PatternExpr, Node | None]`
--> pattern_matcher.py:515
```
Fixing that means re-typing the matcher's central data structure and every
reader of it (`filter_multi_user_patterns`, `find_anchor_nodes`, the
`MatchContext.__init__` parameter). That is a different PR from this one.
### Backward compatibility
`GetAttr.value`'s inferred attribute type goes from `Any` to `object`. Type-only,
no runtime effect, and `torch._inductor` is private, but an out-of-tree or
internal caller that reads `pattern.value.dtype` or forwards `pattern.value` to a
`Tensor`-typed parameter will now need a narrowing check. In-tree there is a
single construction site (line 2809) plus the tests.
### Test plan
Confirmed zero net-new type errors across the whole repo, not just the edited
file, by diffing a full pyrefly run against the same run on the pristine tree:
```bash
pyrefly check --config pyrefly.toml | grep -E "^ERROR|^ -->" > after.txt
git show HEAD~1:torch/_inductor/pattern_matcher.py \
> torch/_inductor/pattern_matcher.py
pyrefly check --config pyrefly.toml | grep -E "^ERROR|^ -->" > before.txt
diff before.txt after.txt # empty
```
Both runs report `65 errors (10,965 suppressed)`, all pre-existing and in
unrelated files. `pattern_matcher.py` alone reports `0 errors (23 suppressed)`
before and after, so no suppression had to be added or moved.
That the two removed suppressions were already dead was established by deleting
them from the **pristine** file and re-checking: still `0 errors`. `lintrunner`
runs pyrefly through `uv` with the CI-pinned dependency set, and it is clean with
them gone.
Because the `pattern_eq` restructure is a real code change and not just an
annotation swap, its behavior was pinned down directly. 27 patterns covering all
10 concrete `PatternExpr` subclasses were compared against each other and against
8 non-pattern operands (`None`, `0`, `""`, `[]`, `{}`, `object()`, a tensor, an
`OpOverload`), and all 945 ordered pairs recorded:
```bash
python agent_space/check_pattern_eq.py > after.txt
git show HEAD~1:torch/_inductor/pattern_matcher.py \
> torch/_inductor/pattern_matcher.py
python agent_space/check_pattern_eq.py > before.txt
diff before.txt after.txt # empty
```
Identical results, `raised=0` on both sides, and symmetry holds across every
pattern pair. (The check also shows `GetAttr(float("nan"))` is not `pattern_eq`
to itself, because `_constant_values_equal` is only NaN-aware for tensors, not
scalars. Pre-existing and unchanged here.)
Ran the pattern matcher test suite and diffed per-test outcomes:
```bash
cd test/inductor
python -m unittest -v test_pattern_matcher 2>&1 \
| grep -E "\.\.\. (ok|ERROR|FAIL|skipped)" | sort > after.txt
```
Outcomes identical for all 122 tests. My dev box has no triton, so 47 errors and
1 failure are environment-induced and reproduce unchanged on the pristine tree.
The file's `__main__` block is gated on `IS_LINUX and HAS_GPU`, hence invoking
unittest directly. The tests that exercise the changed code paths pass:
```bash
cd test/inductor
python -m unittest -v \
test_pattern_matcher.TestPatternMatcher.test_pretty_print_get_attr_nonfinite_tensor_constant_executes \
test_pattern_matcher.TestPatternMatcher.test_pretty_print_get_attr_tensor_constant_requires_contiguous
```
Lint:
```bash
lintrunner --take PYFMT,RUFF,PYREFLY,CODESPELL,FLAKE8 -a
```
---
This PR was authored with the assistance of an AI coding assistant.
Pull Request resolved: https://github.com/pytorch/pytorch/pull/194540
Approved by: https://github.com/jansel B
Bob Ren committed
bd6ca92ea774c9b7193298b80ca7d84b210e58f8
Parent: 3585e4a
Committed by PyTorch MergeBot <pytorchmergebot@users.noreply.github.com>
on 8/25/2026, 11:37:05 PM