Fixes for PYI061 and RUF020 can generate `None | None`
Closed this issue · 5 comments
The fixes for redundant-none-literal
(PYI061) and never-union
(RUF020) in Ruff 0.8.0 can generate None | None
, which raises an error at runtime.
PYI061 example:
$ cat pyi061.py
from typing import Literal
x: Literal[None] | None
$ ruff check --isolated --preview --select PYI061 pyi061.py --fix
Found 1 error (1 fixed, 0 remaining).
$ cat pyi061.py
from typing import Literal
x: None | None
$ python pyi061.py
Traceback (most recent call last):
File "pyi061.py", line 2, in <module>
x: None | None
~~~~~^~~~~~
TypeError: unsupported operand type(s) for |: 'NoneType' and 'NoneType'
RUF020 example:
$ cat ruf020.py
from typing import Never
x: None | Never | None
$ ruff check --isolated --select RUF020 ruf020.py --fix
Found 1 error (1 fixed, 0 remaining).
$ cat ruf020.py
from typing import Never
x: None | None
$ python ruf020.py
Traceback (most recent call last):
File "ruf020.py", line 2, in <module>
x: None | None
~~~~~^~~~~~
TypeError: unsupported operand type(s) for |: 'NoneType' and 'NoneType'
Hah, great catch (as ever). For reference, the CPython issue where it's debated whether we should change this behaviour at runtime in Python is python/cpython#107271.
Regardless of whether we change it in a future version of CPython, however, this is clearly a bug in ruff, since the code does not raise an exception prior to the autofix but does afterwards.
CC: @sbrugman (there's no expectation that you fix the issue, I just thought that you might be interested knowing about it)
Nice catch. I'll look into it.
The frustrating thing here is that if you have PYI016 also enabled, then this isn't an issue. But of course we can't mandate that users select rules in tandem like that.
I'm exploring to fix this in a single go: if the parent union already contains the None
literal, then we can change the edit to range_deletion
instead of range_replacement
.