-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
[1.20 regression] loss of type inference of chained-comparison operands #21149
Description
Bug Report
My repo1 has a line of code which uses a chained comparison to check if two str | None operands are equal and are not None (see example below). Said code failed type-checking in yesterday's pipeline which pulled the latest mypy from PyPI. Apparently, since 1.20.02 the a argument is no longer narrowed to a str.
To Reproduce
Gist URL: https://gist.github.com/mypy-play/dc825cee3a47c4b77265efb1e253ec02
from typing import reveal_type
def some_func(a: str | None = None, b: str | None = None) -> None:
if None is not a == b:
reveal_type(a)
reveal_type(b)
print(a + b) # Stand-in for some code using them as strings
else:
... # Some other processing happens hereExpected Behavior
a should narrow to str, and also b by virtue of its equality with a.3
Actual Behavior
Below are the mypy Playground outputs:
"mypy master branch"
main.py:6: note: Revealed type is "str | None"
main.py:7: note: Revealed type is "str | None"
main.py:8: error: Unsupported operand types for + ("str" and "None") [operator]
main.py:8: error: Unsupported left operand type for + ("None") [operator]
main.py:8: note: Both left and right operands are unions
Found 2 errors in 1 file (checked 1 source file)
"mypy latest (1.20.0)"
main.py:6: note: Revealed type is "builtins.str"
main.py:7: note: Revealed type is "builtins.str | None"
main.py:8: error: Unsupported operand types for + ("str" and "None") [operator]
main.py:8: note: Right operand is of type "str | None"
Found 1 error in 1 file (checked 1 source file)
Both versions failed to narrow b, but master didn't infer anything about a either. I'm probably out of my depth here, but I'm suspecting that the chained comparison is incorrectly parsed in the new version into something equivalent to None is not (a == b), instead of the correct (None is not a) and (a == b), leading to the complete loss of type narrowing.
Your Environment
For the minimal reproducible example
- Mypy version used: master (on Playground)
- Mypy command-line flags: nil
- Mypy configuration options from
mypy.ini(and other config files): nil - Python version used: 3.14
For the aforementioned failed pipeline
- Mypy version used: 1.20.0
- Mypy command-line flags: nil
- Mypy configuration options from
mypy.ini(and other config files):ignore_missing_imports = trueplus misc. options for file in-/ex-clusion - Python version used: 3.14.0
Footnotes
-
Provided for context only since the issue template encourages so:
If the project you encountered the issue in is open source, please provide a link to the project.↩ -
Note that I failed to replicate the differing behaviors between 1.19 and 1.20 on
mypyPlayground. Instead, "mypy latest (1.20.0)" retained the behavior from 1.19, while "mypy master branch" replicated the narrowing failure I saw with 1.20.0 in the above pipeline and on my local machine. ↩ -
As noted in Actual Behavior,
bis not narrowed in any recent version. Maybemypyis trying to be more conservative in the narrowing, accommodating for the off-chance of somestrsubclass overriding.__eq__()to somehow compare toTruewithNone... ? May have to do with (the discussion around) Plugin interface for type narrowing on==comparisons #10708. ↩