-
Notifications
You must be signed in to change notification settings - Fork 2k
Closed
Labels
bugSomething isn't workingSomething isn't workingfixesRelated to suggested fixes for violationsRelated to suggested fixes for violations
Description
Summary
The fix for none-not-at-end-of-union (RUF036) should in general be unsafe because it can change the program’s behavior. Example:
$ cat >ruf036_1.py <<'# EOF'
class C:
def __or__(self, other): return False
def __ror__(self, other): return True
print(None | C())
from typing import Literal
class M(type):
def __or__(self, other): return Literal[False]
def __ror__(self, other): return Literal[True]
class C(metaclass=M): pass
x: None | C
print(__annotate__(1)["x"])
# EOF
$ python3.14 ruf036_1.py
True
typing.Literal[True]
$ ruff --isolated check --select RUF036 --preview ruf036_1.py --fix
Found 2 errors (2 fixed, 0 remaining).
$ python3.14 ruf036_1.py
False
typing.Literal[False]It can still be safe for standard-library types because they behave as expected with |. I think it is worth a special case for such types because they are common in annotations and it would be nice for the fix to stay safe when possible.
The fix can introduce a syntax error by concatenating tokens. Spaces should be inserted where appropriate. This doesn’t happen in type expressions, but it happens in general, and it might happen in type expressions someday if anything like PEP 827 is accepted. Example:
$ cat >ruf036_2.py <<'# EOF'
print(None | (int)and 2)
print(2 or(None) | int)
# EOF
$ python ruf036_2.py
2
2
$ ruff --isolated check --select RUF036 --preview ruf036_2.py --fix
error: Fix introduced a syntax error. Reverting all changes.
This indicates a bug in Ruff. If you could open an issue at:
https://github.com/astral-sh/ruff/issues/new?title=%5BFix%20error%5D
...quoting the contents of `ruf036_2.py`, the rule codes RUF036, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
RUF036 `None` not at the end of the type union.
--> ruf036_2.py:1:7
|
1 | print(None | (int)and 2)
| ^^^^^^^^^^^^
2 | print(2 or(None) | int)
|
help: Move `None` to the end of the type union
- print(None | (int)and 2)
1 + print(int | Noneand 2)
2 | print(2 or(None) | int)
RUF036 `None` not at the end of the type union.
--> ruf036_2.py:2:11
|
1 | print(None | (int)and 2)
2 | print(2 or(None) | int)
| ^^^^^^^^^^^^
|
help: Move `None` to the end of the type union
1 | print(None | (int)and 2)
- print(2 or(None) | int)
2 + print(2 orint | None)
Found 2 errors.
[*] 2 fixable with the `--fix` option.Version
ruff 0.15.5 (5e4a3d9 2026-03-05)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingfixesRelated to suggested fixes for violationsRelated to suggested fixes for violations