mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:43 +00:00

## Summary
This PR adds type narrowing in `and` and `or` expressions, for example:
```py
class A: ...
x: A | None = A() if bool_instance() else None
isinstance(x, A) or reveal_type(x) # revealed: None
```
## Test Plan
New mdtests 😍
2.1 KiB
2.1 KiB
Narrowing in boolean expressions
In or
expressions, the right-hand side is evaluated only if the left-hand side is falsy.
So when the right-hand side is evaluated, we know the left side has failed.
Similarly, in and
expressions, the right-hand side is evaluated only if the left-hand side is truthy.
So when the right-hand side is evaluated, we know the left side has succeeded.
Narrowing in or
def bool_instance() -> bool:
return True
class A: ...
x: A | None = A() if bool_instance() else None
isinstance(x, A) or reveal_type(x) # revealed: None
x is None or reveal_type(x) # revealed: A
reveal_type(x) # revealed: A | None
Narrowing in and
def bool_instance() -> bool:
return True
class A: ...
x: A | None = A() if bool_instance() else None
isinstance(x, A) and reveal_type(x) # revealed: A
x is None and reveal_type(x) # revealed: None
reveal_type(x) # revealed: A | None
Multiple and
arms
def bool_instance() -> bool:
return True
class A: ...
x: A | None = A() if bool_instance() else None
bool_instance() and isinstance(x, A) and reveal_type(x) # revealed: A
isinstance(x, A) and bool_instance() and reveal_type(x) # revealed: A
reveal_type(x) and isinstance(x, A) and bool_instance() # revealed: A | None
Multiple or
arms
def bool_instance() -> bool:
return True
class A: ...
x: A | None = A() if bool_instance() else None
bool_instance() or isinstance(x, A) or reveal_type(x) # revealed: None
isinstance(x, A) or bool_instance() or reveal_type(x) # revealed: None
reveal_type(x) or isinstance(x, A) or bool_instance() # revealed: A | None
Multiple predicates
def bool_instance() -> bool:
return True
class A: ...
x: A | None | Literal[1] = A() if bool_instance() else None if bool_instance() else 1
x is None or isinstance(x, A) or reveal_type(x) # revealed: Literal[1]
Mix of and
and or
def bool_instance() -> bool:
return True
class A: ...
x: A | None | Literal[1] = A() if bool_instance() else None if bool_instance() else 1
isinstance(x, A) or x is not None and reveal_type(x) # revealed: Literal[1]