[ty] narrow the right-hand side of ==, !=, is and is not conditions when the left-hand side is not narrowable (#22511)

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
drbh
2026-01-13 11:01:54 -05:00
committed by GitHub
parent c7b41060f4
commit d13b5db066
4 changed files with 98 additions and 0 deletions

View File

@@ -12,6 +12,30 @@ def _(flag: bool):
reveal_type(x) # revealed: None
```
## `None != x` (reversed operands)
```py
def _(flag: bool):
x = None if flag else 1
if None != x:
reveal_type(x) # revealed: Literal[1]
else:
reveal_type(x) # revealed: None
```
This also works for `==` with reversed operands:
```py
def _(flag: bool):
x = None if flag else 1
if None == x:
reveal_type(x) # revealed: None
else:
reveal_type(x) # revealed: Literal[1]
```
## `!=` for other singleton types
### Bool

View File

@@ -121,6 +121,31 @@ def test(x: Literal["a", "b", "c"] | None | int = None):
reveal_type(x) # revealed: Literal["a", "c"] | int
```
## No narrowing for the right-hand side (currently)
No narrowing is done for the right-hand side currently, even if the right-hand side is a valid
"target" (name/attribute/subscript) that could potentially be narrowed. We may change this in the
future:
```py
from typing import Literal
def f(x: Literal["abc", "def"]):
if "a" in x:
# `x` could also be validly narrowed to `Literal["abc"]` here:
reveal_type(x) # revealed: Literal["abc", "def"]
else:
# `x` could also be validly narrowed to `Literal["def"]` here:
reveal_type(x) # revealed: Literal["abc", "def"]
if "a" not in x:
# `x` could also be validly narrowed to `Literal["def"]` here:
reveal_type(x) # revealed: Literal["abc", "def"]
else:
# `x` could also be validly narrowed to `Literal["abc"]` here:
reveal_type(x) # revealed: Literal["abc", "def"]
```
## bool
```py

View File

@@ -16,6 +16,32 @@ def _(flag: bool):
reveal_type(x) # revealed: None | Literal[1]
```
## `None is not x` (reversed operands)
```py
def _(flag: bool):
x = None if flag else 1
if None is not x:
reveal_type(x) # revealed: Literal[1]
else:
reveal_type(x) # revealed: None
reveal_type(x) # revealed: None | Literal[1]
```
This also works for other singleton types with reversed operands:
```py
def _(flag: bool):
x = True if flag else False
if False is not x:
reveal_type(x) # revealed: Literal[True]
else:
reveal_type(x) # revealed: Literal[False]
```
## `is not` for other singleton types
Boolean literals: