ruff/crates/red_knot_python_semantic/resources/mdtest/narrow/boolean.md
TomerBin 9a0dade925
[red-knot] Type narrowing inside boolean expressions (#13970)
## 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 😍
2024-10-28 18:17:48 -07:00

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]