mirror of https://github.com/astral-sh/ruff
[red-knot] fix: improve type inference for binary ops on tuples (#16725)
## Summary This PR includes minor improvements to binary operation inference, specifically for tuple concatenation. ### Before ```py reveal_type((1, 2) + (3, 4)) # revealed: @Todo(return type of decorated function) # If TODO is ignored, the revealed type would be `tuple[1|2|3|4, ...]` ``` The `builtins.tuple` type stub defines `__add__`, but it appears to only work for homogeneous tuples. However, I think this limitation is not ideal for many use cases. ### After ```py reveal_type((1, 2) + (3, 4)) # revealed: tuple[Literal[1], Literal[2], Literal[3], Literal[4]] ``` ## Test Plan ### Added - `mdtest/binary/tuples.md` ### Affected - `mdtest/slots.md` (a test have been moved out of the `False-Negative` block.)
This commit is contained in:
parent
d03b12e711
commit
270318c2e0
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Binary operations on tuples
|
||||||
|
|
||||||
|
## Concatenation for heterogeneous tuples
|
||||||
|
|
||||||
|
```py
|
||||||
|
reveal_type((1, 2) + (3, 4)) # revealed: tuple[Literal[1], Literal[2], Literal[3], Literal[4]]
|
||||||
|
reveal_type(() + (1, 2)) # revealed: tuple[Literal[1], Literal[2]]
|
||||||
|
reveal_type((1, 2) + ()) # revealed: tuple[Literal[1], Literal[2]]
|
||||||
|
reveal_type(() + ()) # revealed: tuple[()]
|
||||||
|
|
||||||
|
def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
|
||||||
|
reveal_type(x + y) # revealed: tuple[int, str, None, tuple[int]]
|
||||||
|
reveal_type(y + x) # revealed: tuple[None, tuple[int], int, str]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Concatenation for homogeneous tuples
|
||||||
|
|
||||||
|
```py
|
||||||
|
def _(x: tuple[int, ...], y: tuple[str, ...]):
|
||||||
|
reveal_type(x + y) # revealed: @Todo(full tuple[...] support)
|
||||||
|
reveal_type(x + (1, 2)) # revealed: @Todo(full tuple[...] support)
|
||||||
|
```
|
||||||
|
|
@ -113,6 +113,24 @@ class D(B, A): ... # fine
|
||||||
class E(B, C, A): ... # fine
|
class E(B, C, A): ... # fine
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Post-hoc modifications
|
||||||
|
|
||||||
|
```py
|
||||||
|
class A:
|
||||||
|
__slots__ = ()
|
||||||
|
__slots__ += ("a", "b")
|
||||||
|
|
||||||
|
reveal_type(A.__slots__) # revealed: tuple[Literal["a"], Literal["b"]]
|
||||||
|
|
||||||
|
class B:
|
||||||
|
__slots__ = ("c", "d")
|
||||||
|
|
||||||
|
class C(
|
||||||
|
A, # error: [incompatible-slots]
|
||||||
|
B, # error: [incompatible-slots]
|
||||||
|
): ...
|
||||||
|
```
|
||||||
|
|
||||||
## False negatives
|
## False negatives
|
||||||
|
|
||||||
### Possibly unbound
|
### Possibly unbound
|
||||||
|
|
@ -160,22 +178,6 @@ class B:
|
||||||
class C(A, B): ...
|
class C(A, B): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Post-hoc modifications
|
|
||||||
|
|
||||||
```py
|
|
||||||
class A:
|
|
||||||
__slots__ = ()
|
|
||||||
__slots__ += ("a", "b")
|
|
||||||
|
|
||||||
reveal_type(A.__slots__) # revealed: @Todo(return type of decorated function)
|
|
||||||
|
|
||||||
class B:
|
|
||||||
__slots__ = ("c", "d")
|
|
||||||
|
|
||||||
# False negative: [incompatible-slots]
|
|
||||||
class C(A, B): ...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Built-ins with implicit layouts
|
### Built-ins with implicit layouts
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
|
||||||
|
|
@ -4633,6 +4633,20 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
self.infer_binary_expression_type(left, Type::IntLiteral(i64::from(bool_value)), op)
|
self.infer_binary_expression_type(left, Type::IntLiteral(i64::from(bool_value)), op)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(Type::Tuple(lhs), Type::Tuple(rhs), ast::Operator::Add) => {
|
||||||
|
// Note: this only works on heterogeneous tuples.
|
||||||
|
let lhs_elements = lhs.elements(self.db());
|
||||||
|
let rhs_elements = rhs.elements(self.db());
|
||||||
|
|
||||||
|
Some(TupleType::from_elements(
|
||||||
|
self.db(),
|
||||||
|
lhs_elements
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.chain(rhs_elements.iter().copied()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// We've handled all of the special cases that we support for literals, so we need to
|
// We've handled all of the special cases that we support for literals, so we need to
|
||||||
// fall back on looking for dunder methods on one of the operand types.
|
// fall back on looking for dunder methods on one of the operand types.
|
||||||
(
|
(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue