crates/ty_python_semantic/resources/mdtest/binary/unions.md
Binary operations on union types are only available if they are supported for all possible combinations of types:
def f1(i: int, u: int | None):
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `int` and `int | None`"
reveal_type(i + u) # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `int | None` and `int`"
reveal_type(u + i) # revealed: Unknown
int can be added to int, and str can be added to str, but expressions of type int | str
cannot be added, because that would require addition of int and str or vice versa:
def f2(i: int, s: str, int_or_str: int | str):
i + i
s + s
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `int | str`"
reveal_type(int_or_str + int_or_str) # revealed: Unknown
However, if an operation is supported for all possible combinations, the result will be a union of the possible outcomes:
from typing import Literal
def f3(two_or_three: Literal[2, 3], a_or_b: Literal["a", "b"]):
reveal_type(two_or_three + two_or_three) # revealed: Literal[4, 5, 6]
reveal_type(two_or_three**two_or_three) # revealed: Literal[4, 8, 9, 27]
reveal_type(a_or_b + a_or_b) # revealed: Literal["aa", "ab", "ba", "bb"]
reveal_type(two_or_three * a_or_b) # revealed: Literal["aa", "bb", "aaa", "bbb"]
We treat a type annotation of float as a union of int and float, so union handling is relevant
here:
def f4(x: float, y: float):
reveal_type(x + y) # revealed: float
reveal_type(x - y) # revealed: float
reveal_type(x * y) # revealed: float
reveal_type(x / y) # revealed: float
reveal_type(x // y) # revealed: float
reveal_type(x % y) # revealed: float
If any of the union elements leads to a division by zero, we will report an error:
def f5(m: int, n: Literal[-1, 0, 1]):
# error: [division-by-zero] "Cannot divide object of type `int` by zero"
return m / n
Binary-operator diagnostics share state across union alternatives, so several combinations that divide by zero produce only one warning per expression. Each expression has its own state, so repeating the operation still produces a warning:
def f6(m: Literal[1, 2], n: Literal[0, 1]):
m / n # error: [division-by-zero]
m / n # error: [division-by-zero]