mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:56 +00:00
[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
3 changed files with 54 additions and 16 deletions
|
@ -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
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
### Possibly unbound
|
||||
|
@ -160,22 +178,6 @@ class 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
|
||||
|
||||
```py
|
||||
|
|
|
@ -4633,6 +4633,20 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
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
|
||||
// fall back on looking for dunder methods on one of the operand types.
|
||||
(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue