mirror of
https://github.com/astral-sh/ruff
synced 2026-01-21 05:20:49 -05:00
[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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1250,6 +1250,29 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
);
|
||||
}
|
||||
}
|
||||
// For symmetric operators (==, !=, is, is not), if left is not a narrowable target,
|
||||
// try to narrow the right operand instead by swapping the operands.
|
||||
// E.g., `None != x` should narrow `x` the same way as `x != None`.
|
||||
_ if matches!(
|
||||
op,
|
||||
ast::CmpOp::Eq | ast::CmpOp::NotEq | ast::CmpOp::Is | ast::CmpOp::IsNot
|
||||
) && matches!(
|
||||
right,
|
||||
ast::Expr::Name(_)
|
||||
| ast::Expr::Attribute(_)
|
||||
| ast::Expr::Subscript(_)
|
||||
| ast::Expr::Named(_)
|
||||
) =>
|
||||
{
|
||||
if let Some(right_place) = place_expr(right)
|
||||
// Swap lhs_ty and rhs_ty since we're narrowing the right operand
|
||||
&& let Some(ty) =
|
||||
self.evaluate_expr_compare_op(rhs_ty, lhs_ty, *op, is_positive)
|
||||
{
|
||||
let place = self.expect_place(&right_place);
|
||||
constraints.insert(place, NarrowingConstraint::regular(ty));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user