mirror of
https://github.com/astral-sh/ruff
synced 2026-01-23 06:20:55 -05:00
## Summary
Properly handle binary operator inference for union types.
This fixes a bug I noticed while looking at ecosystem results. The MRE
version of it is this:
```py
def sub(x: float, y: float):
# Red Knot: Operator `-` is unsupported between objects of type `int | float` and `int | float`
return x - y
```
## Test Plan
- New Markdown tests.
- Expected diff in the ecosystem checks
1.9 KiB
1.9 KiB
Binary operations on union types
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 unsupported between objects of type `int` and `int | None`"
reveal_type(i + u) # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is unsupported 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 unsupported between objects of type `int | str` and `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: int | float
reveal_type(x - y) # revealed: int | float
reveal_type(x * y) # revealed: int | float
reveal_type(x / y) # revealed: int | float
reveal_type(x // y) # revealed: int | float
reveal_type(x % y) # revealed: int | float