[ty] Support comparisons between variable-length tuples (#21824)
## Summary Closes https://github.com/astral-sh/ty/issues/1741.
This commit is contained in:
@@ -338,7 +338,111 @@ reveal_type(a is not c) # revealed: Literal[True]
|
||||
|
||||
For tuples like `tuple[int, ...]`, `tuple[Any, ...]`
|
||||
|
||||
// TODO
|
||||
### Unsupported Comparisons
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
Comparisons between homogeneous tuples with incompatible element types should emit diagnostics for
|
||||
ordering operators (`<`, `<=`, `>`, `>=`), but not for equality operators (`==`, `!=`).
|
||||
|
||||
```py
|
||||
def f(
|
||||
a: tuple[int, ...],
|
||||
b: tuple[str, ...],
|
||||
c: tuple[str],
|
||||
):
|
||||
# Equality comparisons are always valid
|
||||
reveal_type(a == b) # revealed: bool
|
||||
reveal_type(a != b) # revealed: bool
|
||||
|
||||
# Ordering comparisons between incompatible types should emit errors
|
||||
# error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str, ...]`"
|
||||
a < b
|
||||
# error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str, ...]` and `tuple[int, ...]`"
|
||||
b < a
|
||||
# error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str]`"
|
||||
a < c
|
||||
# error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str]` and `tuple[int, ...]`"
|
||||
c < a
|
||||
```
|
||||
|
||||
When comparing fixed-length tuples with variable-length tuples, all element types that could
|
||||
potentially be compared must be compatible.
|
||||
|
||||
```py
|
||||
def _(
|
||||
var_int: tuple[int, ...],
|
||||
var_str: tuple[str, ...],
|
||||
fixed_int_str: tuple[int, str],
|
||||
):
|
||||
# Fixed `tuple[int, str]` vs. variable `tuple[int, ...]`:
|
||||
# Position 0: `int` vs. `int` are comparable.
|
||||
# Position 1 (if `var_int` has 2+ elements): `str` vs. `int` are not comparable.
|
||||
# error: [unsupported-operator]
|
||||
fixed_int_str < var_int
|
||||
|
||||
# Variable `tuple[int, ...]` vs. fixed `tuple[int, str]`:
|
||||
# Position 0: `int` vs. `int` are comparable.
|
||||
# Position 1 (if `var_int` has 2+ elements): `int` vs. `str` are not comparable.
|
||||
# error: [unsupported-operator]
|
||||
var_int < fixed_int_str
|
||||
|
||||
# Variable `tuple[str, ...]` vs. fixed `tuple[int, str]`:
|
||||
# Position 0: `str` vs. `int` are not comparable.
|
||||
# error: [unsupported-operator]
|
||||
var_str < fixed_int_str
|
||||
```
|
||||
|
||||
### Supported Comparisons
|
||||
|
||||
Comparisons between homogeneous tuples with compatible element types should work.
|
||||
|
||||
```py
|
||||
def _(a: tuple[int, ...], b: tuple[int, ...], c: tuple[bool, ...]):
|
||||
# Same element types - always valid
|
||||
reveal_type(a == b) # revealed: bool
|
||||
reveal_type(a != b) # revealed: bool
|
||||
reveal_type(a < b) # revealed: bool
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a > b) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
|
||||
# int and bool are compatible for comparison
|
||||
reveal_type(a < c) # revealed: bool
|
||||
reveal_type(c < a) # revealed: bool
|
||||
```
|
||||
|
||||
### Tuples with Prefixes and Suffixes
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
Variable-length tuples with prefixes and suffixes are also checked.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
def _(
|
||||
prefix_int_var_str: tuple[int, *tuple[str, ...]],
|
||||
prefix_str_var_int: tuple[str, *tuple[int, ...]],
|
||||
):
|
||||
# Prefix `int` vs. prefix `str` are not comparable.
|
||||
# error: [unsupported-operator]
|
||||
prefix_int_var_str < prefix_str_var_int
|
||||
```
|
||||
|
||||
Tuples with compatible prefixes/suffixes are allowed.
|
||||
|
||||
```py
|
||||
def _(
|
||||
prefix_int_var_int: tuple[int, *tuple[int, ...]],
|
||||
prefix_int_var_bool: tuple[int, *tuple[bool, ...]],
|
||||
):
|
||||
# Prefix `int` vs. prefix `int`, variable `int` vs. variable `bool` are all comparable.
|
||||
reveal_type(prefix_int_var_int < prefix_int_var_bool) # revealed: bool
|
||||
```
|
||||
|
||||
## Chained comparisons with elements that incorrectly implement `__bool__`
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: tuples.md - Comparison: Tuples - Homogeneous - Tuples with Prefixes and Suffixes
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(
|
||||
2 | prefix_int_var_str: tuple[int, *tuple[str, ...]],
|
||||
3 | prefix_str_var_int: tuple[str, *tuple[int, ...]],
|
||||
4 | ):
|
||||
5 | # Prefix `int` vs. prefix `str` are not comparable.
|
||||
6 | # error: [unsupported-operator]
|
||||
7 | prefix_int_var_str < prefix_str_var_int
|
||||
8 | def _(
|
||||
9 | prefix_int_var_int: tuple[int, *tuple[int, ...]],
|
||||
10 | prefix_int_var_bool: tuple[int, *tuple[bool, ...]],
|
||||
11 | ):
|
||||
12 | # Prefix `int` vs. prefix `int`, variable `int` vs. variable `bool` are all comparable.
|
||||
13 | reveal_type(prefix_int_var_int < prefix_int_var_bool) # revealed: bool
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unsupported-operator]: Unsupported `<` operation
|
||||
--> src/mdtest_snippet.py:7:5
|
||||
|
|
||||
5 | # Prefix `int` vs. prefix `str` are not comparable.
|
||||
6 | # error: [unsupported-operator]
|
||||
7 | prefix_int_var_str < prefix_str_var_int
|
||||
| ------------------^^^------------------
|
||||
| | |
|
||||
| | Has type `tuple[str, *tuple[int, ...]]`
|
||||
| Has type `tuple[int, *tuple[str, ...]]`
|
||||
8 | def _(
|
||||
9 | prefix_int_var_int: tuple[int, *tuple[int, ...]],
|
||||
|
|
||||
info: Operation fails because operator `<` is not supported between objects of type `int` and `str`
|
||||
info: rule `unsupported-operator` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,187 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: tuples.md - Comparison: Tuples - Homogeneous - Unsupported Comparisons
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def f(
|
||||
2 | a: tuple[int, ...],
|
||||
3 | b: tuple[str, ...],
|
||||
4 | c: tuple[str],
|
||||
5 | ):
|
||||
6 | # Equality comparisons are always valid
|
||||
7 | reveal_type(a == b) # revealed: bool
|
||||
8 | reveal_type(a != b) # revealed: bool
|
||||
9 |
|
||||
10 | # Ordering comparisons between incompatible types should emit errors
|
||||
11 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str, ...]`"
|
||||
12 | a < b
|
||||
13 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str, ...]` and `tuple[int, ...]`"
|
||||
14 | b < a
|
||||
15 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str]`"
|
||||
16 | a < c
|
||||
17 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str]` and `tuple[int, ...]`"
|
||||
18 | c < a
|
||||
19 | def _(
|
||||
20 | var_int: tuple[int, ...],
|
||||
21 | var_str: tuple[str, ...],
|
||||
22 | fixed_int_str: tuple[int, str],
|
||||
23 | ):
|
||||
24 | # Fixed `tuple[int, str]` vs. variable `tuple[int, ...]`:
|
||||
25 | # Position 0: `int` vs. `int` are comparable.
|
||||
26 | # Position 1 (if `var_int` has 2+ elements): `str` vs. `int` are not comparable.
|
||||
27 | # error: [unsupported-operator]
|
||||
28 | fixed_int_str < var_int
|
||||
29 |
|
||||
30 | # Variable `tuple[int, ...]` vs. fixed `tuple[int, str]`:
|
||||
31 | # Position 0: `int` vs. `int` are comparable.
|
||||
32 | # Position 1 (if `var_int` has 2+ elements): `int` vs. `str` are not comparable.
|
||||
33 | # error: [unsupported-operator]
|
||||
34 | var_int < fixed_int_str
|
||||
35 |
|
||||
36 | # Variable `tuple[str, ...]` vs. fixed `tuple[int, str]`:
|
||||
37 | # Position 0: `str` vs. `int` are not comparable.
|
||||
38 | # error: [unsupported-operator]
|
||||
39 | var_str < fixed_int_str
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unsupported-operator]: Unsupported `<` operation
|
||||
--> src/mdtest_snippet.py:12:5
|
||||
|
|
||||
10 | # Ordering comparisons between incompatible types should emit errors
|
||||
11 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str, ...]`"
|
||||
12 | a < b
|
||||
| -^^^-
|
||||
| | |
|
||||
| | Has type `tuple[str, ...]`
|
||||
| Has type `tuple[int, ...]`
|
||||
13 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str, ...]` and `tuple[int, ...]`"
|
||||
14 | b < a
|
||||
|
|
||||
info: Operation fails because operator `<` is not supported between objects of type `int` and `str`
|
||||
info: rule `unsupported-operator` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unsupported-operator]: Unsupported `<` operation
|
||||
--> src/mdtest_snippet.py:14:5
|
||||
|
|
||||
12 | a < b
|
||||
13 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str, ...]` and `tuple[int, ...]`"
|
||||
14 | b < a
|
||||
| -^^^-
|
||||
| | |
|
||||
| | Has type `tuple[int, ...]`
|
||||
| Has type `tuple[str, ...]`
|
||||
15 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str]`"
|
||||
16 | a < c
|
||||
|
|
||||
info: Operation fails because operator `<` is not supported between objects of type `str` and `int`
|
||||
info: rule `unsupported-operator` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unsupported-operator]: Unsupported `<` operation
|
||||
--> src/mdtest_snippet.py:16:5
|
||||
|
|
||||
14 | b < a
|
||||
15 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str]`"
|
||||
16 | a < c
|
||||
| -^^^-
|
||||
| | |
|
||||
| | Has type `tuple[str]`
|
||||
| Has type `tuple[int, ...]`
|
||||
17 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str]` and `tuple[int, ...]`"
|
||||
18 | c < a
|
||||
|
|
||||
info: Operation fails because operator `<` is not supported between objects of type `int` and `str`
|
||||
info: rule `unsupported-operator` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unsupported-operator]: Unsupported `<` operation
|
||||
--> src/mdtest_snippet.py:18:5
|
||||
|
|
||||
16 | a < c
|
||||
17 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str]` and `tuple[int, ...]`"
|
||||
18 | c < a
|
||||
| -^^^-
|
||||
| | |
|
||||
| | Has type `tuple[int, ...]`
|
||||
| Has type `tuple[str]`
|
||||
19 | def _(
|
||||
20 | var_int: tuple[int, ...],
|
||||
|
|
||||
info: Operation fails because operator `<` is not supported between objects of type `str` and `int`
|
||||
info: rule `unsupported-operator` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unsupported-operator]: Unsupported `<` operation
|
||||
--> src/mdtest_snippet.py:28:5
|
||||
|
|
||||
26 | # Position 1 (if `var_int` has 2+ elements): `str` vs. `int` are not comparable.
|
||||
27 | # error: [unsupported-operator]
|
||||
28 | fixed_int_str < var_int
|
||||
| -------------^^^-------
|
||||
| | |
|
||||
| | Has type `tuple[int, ...]`
|
||||
| Has type `tuple[int, str]`
|
||||
29 |
|
||||
30 | # Variable `tuple[int, ...]` vs. fixed `tuple[int, str]`:
|
||||
|
|
||||
info: Operation fails because operator `<` is not supported between objects of type `str` and `int`
|
||||
info: rule `unsupported-operator` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unsupported-operator]: Unsupported `<` operation
|
||||
--> src/mdtest_snippet.py:34:5
|
||||
|
|
||||
32 | # Position 1 (if `var_int` has 2+ elements): `int` vs. `str` are not comparable.
|
||||
33 | # error: [unsupported-operator]
|
||||
34 | var_int < fixed_int_str
|
||||
| -------^^^-------------
|
||||
| | |
|
||||
| | Has type `tuple[int, str]`
|
||||
| Has type `tuple[int, ...]`
|
||||
35 |
|
||||
36 | # Variable `tuple[str, ...]` vs. fixed `tuple[int, str]`:
|
||||
|
|
||||
info: Operation fails because operator `<` is not supported between objects of type `int` and `str`
|
||||
info: rule `unsupported-operator` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unsupported-operator]: Unsupported `<` operation
|
||||
--> src/mdtest_snippet.py:39:5
|
||||
|
|
||||
37 | # Position 0: `str` vs. `int` are not comparable.
|
||||
38 | # error: [unsupported-operator]
|
||||
39 | var_str < fixed_int_str
|
||||
| -------^^^-------------
|
||||
| | |
|
||||
| | Has type `tuple[int, str]`
|
||||
| Has type `tuple[str, ...]`
|
||||
|
|
||||
info: Operation fails because operator `<` is not supported between objects of type `str` and `int`
|
||||
info: rule `unsupported-operator` is enabled by default
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user