crates/ty_python_semantic/resources/mdtest/narrow/conditionals/is.md
is conditionalsis Nonedef _(flag: bool):
x = None if flag else 1
if x is None:
reveal_type(x) # revealed: None
else:
reveal_type(x) # revealed: Literal[1]
reveal_type(x) # revealed: None | Literal[1]
is for other typesdef _(flag: bool):
class A: ...
x = A()
y = x if flag else None
if y is x:
reveal_type(y) # revealed: A
else:
reveal_type(y) # revealed: A | None
reveal_type(y) # revealed: A | None
is in chained comparisonsdef _(x_flag: bool, y_flag: bool):
x = True if x_flag else False
y = True if y_flag else False
reveal_type(x) # revealed: bool
reveal_type(y) # revealed: bool
if y is x is False: # Interpreted as `(y is x) and (x is False)`
reveal_type(x) # revealed: Literal[False]
reveal_type(y) # revealed: bool
else:
# The negation of the clause above is (y is not x) or (x is not False)
# So we can't narrow the type of x or y here, because each arm of the `or` could be true
reveal_type(x) # revealed: bool
reveal_type(y) # revealed: bool
is in elif clausedef _(flag1: bool, flag2: bool):
x = None if flag1 else (1 if flag2 else True)
reveal_type(x) # revealed: None | Literal[1, True]
if x is None:
reveal_type(x) # revealed: None
elif x is True:
reveal_type(x) # revealed: Literal[True]
else:
reveal_type(x) # revealed: Literal[1]
is for enumsfrom enum import Enum
class Answer(Enum):
NO = 0
YES = 1
def _(answer: Answer):
if answer is Answer.NO:
reveal_type(answer) # revealed: Literal[Answer.NO]
else:
reveal_type(answer) # revealed: Literal[Answer.YES]
class Single(Enum):
VALUE = 1
def _(x: Single | int):
if x is Single.VALUE:
reveal_type(x) # revealed: Single
else:
reveal_type(x) # revealed: int
is for EllipsisType (Python 3.10+)[environment]
python-version = "3.10"
from types import EllipsisType
def _(x: int | EllipsisType):
if x is ...:
reveal_type(x) # revealed: EllipsisType
else:
reveal_type(x) # revealed: int
is for EllipsisType (Python 3.9 and below)[environment]
python-version = "3.9"
def _(flag: bool):
x = ... if flag else 42
reveal_type(x) # revealed: ellipsis | Literal[42]
if x is ...:
reveal_type(x) # revealed: ellipsis
else:
reveal_type(x) # revealed: Literal[42]
from typing import Literal
def f() -> Literal[1, 2] | None: ...
if (x := f()) is None:
reveal_type(x) # revealed: None
else:
reveal_type(x) # revealed: Literal[1, 2]
is with two narrowable operandsBoth operands should be narrowed when both are narrowable expressions.
from typing import Literal
def _(t: Literal[True], tn: Literal[True] | None):
if tn is t:
reveal_type(tn) # revealed: Literal[True]
if t is tn:
reveal_type(tn) # revealed: Literal[True]
Both operands should also be narrowed in chained comparisons:
from typing import Literal
def _(a: Literal[1], b: Literal[1, 2], c: Literal[1, 2, 3]):
if a is b is c:
reveal_type(b) # revealed: Literal[1]
reveal_type(c) # revealed: Literal[1]
is where the other operand is a call expressionfrom typing import Literal, final
def foo() -> Literal[42]:
return 42
def f(x: object):
if x is foo():
reveal_type(x) # revealed: Literal[42]
else:
reveal_type(x) # revealed: object
if x is not foo():
reveal_type(x) # revealed: object
else:
reveal_type(x) # revealed: Literal[42]
if foo() is x:
reveal_type(x) # revealed: Literal[42]
else:
reveal_type(x) # revealed: object
if foo() is not x:
reveal_type(x) # revealed: object
else:
reveal_type(x) # revealed: Literal[42]
def bar() -> int:
return 42
def g(x: object):
if x is bar():
reveal_type(x) # revealed: int
else:
reveal_type(x) # revealed: object
if x is not bar():
reveal_type(x) # revealed: object
else:
reveal_type(x) # revealed: int
@final
class FinalClass: ...
def baz() -> FinalClass:
return FinalClass()
def h(x: object):
if x is baz():
reveal_type(x) # revealed: FinalClass
else:
reveal_type(x) # revealed: object
if x is not baz():
reveal_type(x) # revealed: object
else:
reveal_type(x) # revealed: FinalClass
def spam() -> None:
return None
def h(x: object):
if x is spam():
reveal_type(x) # revealed: None
else:
# `else` narrowing can occur because `spam()` returns a singleton type
reveal_type(x) # revealed: ~None
if x is not spam():
reveal_type(x) # revealed: ~None
else:
reveal_type(x) # revealed: None