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 here
Expected 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 = true plus misc. options for file in-/ex-clusion
- Python version used: 3.14.0
Bug Report
My repo1 has a line of code which uses a chained comparison to check if two
str | Noneoperands are equal and are notNone(see example below). Said code failed type-checking in yesterday's pipeline which pulled the latestmypyfrom PyPI. Apparently, since 1.20.02 theaargument is no longer narrowed to astr.To Reproduce
Gist URL: https://gist.github.com/mypy-play/dc825cee3a47c4b77265efb1e253ec02
Expected Behavior
ashould narrow tostr, and alsobby virtue of its equality witha.3Actual Behavior
Below are the
mypyPlayground outputs:"mypy master branch"
"mypy latest (1.20.0)"
Both versions failed to narrow
b, butmasterdidn't infer anything aboutaeither. 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 toNone 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.ini(and other config files): nilFor the aforementioned failed pipeline
mypy.ini(and other config files):ignore_missing_imports = trueplus misc. options for file in-/ex-clusionFootnotes
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. ↩