# Comparison: Tuples

## Heterogeneous

For tuples like `tuple[int, str, Literal[1]]`

### Value Comparisons

"Value Comparisons" refers to the operators: `==`, `!=`, `<`, `<=`, `>`, `>=`

#### Results without Ambiguity

Cases where the result can be definitively inferred as a `BooleanLiteral`.

```py
a = (1, "test", (3, 13), True)
b = (1, "test", (3, 14), False)

reveal_type(a == a)  # revealed: Literal[True]
reveal_type(a != a)  # revealed: Literal[False]
reveal_type(a < a)  # revealed: Literal[False]
reveal_type(a <= a)  # revealed: Literal[True]
reveal_type(a > a)  # revealed: Literal[False]
reveal_type(a >= a)  # revealed: Literal[True]

reveal_type(a == b)  # revealed: Literal[False]
reveal_type(a != b)  # revealed: Literal[True]
reveal_type(a < b)  # revealed: Literal[True]
reveal_type(a <= b)  # revealed: Literal[True]
reveal_type(a > b)  # revealed: Literal[False]
reveal_type(a >= b)  # revealed: Literal[False]
```

Even when tuples have different lengths, comparisons should be handled appropriately.

```py path=different_length.py
a = (1, 2, 3)
b = (1, 2, 3, 4)

reveal_type(a == b)  # revealed: Literal[False]
reveal_type(a != b)  # revealed: Literal[True]
reveal_type(a < b)  # revealed: Literal[True]
reveal_type(a <= b)  # revealed: Literal[True]
reveal_type(a > b)  # revealed: Literal[False]
reveal_type(a >= b)  # revealed: Literal[False]

c = ("a", "b", "c", "d")
d = ("a", "b", "c")

reveal_type(c == d)  # revealed: Literal[False]
reveal_type(c != d)  # revealed: Literal[True]
reveal_type(c < d)  # revealed: Literal[False]
reveal_type(c <= d)  # revealed: Literal[False]
reveal_type(c > d)  # revealed: Literal[True]
reveal_type(c >= d)  # revealed: Literal[True]
```

#### Results with Ambiguity

```py
def bool_instance() -> bool: ...
def int_instance() -> int:
    return 42

a = (bool_instance(),)
b = (int_instance(),)

reveal_type(a == a)  # revealed: bool
reveal_type(a != a)  # revealed: bool
reveal_type(a < a)  # revealed: bool
reveal_type(a <= a)  # revealed: bool
reveal_type(a > a)  # revealed: bool
reveal_type(a >= a)  # 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
reveal_type(a >= b)  # revealed: bool
```

#### Comparison Unsupported

If two tuples contain types that do not support comparison, the result may be `Unknown`. However,
`==` and `!=` are exceptions and can still provide definite results.

```py
a = (1, 2)
b = (1, "hello")

# TODO: should be Literal[False], once we implement (in)equality for mismatched literals
reveal_type(a == b)  # revealed: bool

# TODO: should be Literal[True], once we implement (in)equality for mismatched literals
reveal_type(a != b)  # revealed: bool

# TODO: should be Unknown and add more informative diagnostics
reveal_type(a < b)  # revealed: bool
reveal_type(a <= b)  # revealed: bool
reveal_type(a > b)  # revealed: bool
reveal_type(a >= b)  # revealed: bool
```

However, if the lexicographic comparison completes without reaching a point where str and int are
compared, Python will still produce a result based on the prior elements.

```py path=short_circuit.py
a = (1, 2)
b = (999999, "hello")

reveal_type(a == b)  # revealed: Literal[False]
reveal_type(a != b)  # revealed: Literal[True]
reveal_type(a < b)  # revealed: Literal[True]
reveal_type(a <= b)  # revealed: Literal[True]
reveal_type(a > b)  # revealed: Literal[False]
reveal_type(a >= b)  # revealed: Literal[False]
```

#### Matryoshka Tuples

```py
a = (1, True, "Hello")
b = (a, a, a)
c = (b, b, b)

reveal_type(c == c)  # revealed: Literal[True]
reveal_type(c != c)  # revealed: Literal[False]
reveal_type(c < c)  # revealed: Literal[False]
reveal_type(c <= c)  # revealed: Literal[True]
reveal_type(c > c)  # revealed: Literal[False]
reveal_type(c >= c)  # revealed: Literal[True]
```

#### Non Boolean Rich Comparisons

```py
class A:
    def __eq__(self, o) -> str: ...
    def __ne__(self, o) -> int: ...
    def __lt__(self, o) -> float: ...
    def __le__(self, o) -> object: ...
    def __gt__(self, o) -> tuple: ...
    def __ge__(self, o) -> list: ...

a = (A(), A())

reveal_type(a == a)  # revealed: bool
reveal_type(a != a)  # revealed: bool
reveal_type(a < a)  # revealed: bool
reveal_type(a <= a)  # revealed: bool
reveal_type(a > a)  # revealed: bool
reveal_type(a >= a)  # revealed: bool
```

### Membership Test Comparisons

"Membership Test Comparisons" refers to the operators `in` and `not in`.

```py
def int_instance() -> int:
    return 42

a = (1, 2)
b = ((3, 4), (1, 2))
c = ((1, 2, 3), (4, 5, 6))
d = ((int_instance(), int_instance()), (int_instance(), int_instance()))

reveal_type(a in b)  # revealed: Literal[True]
reveal_type(a not in b)  # revealed: Literal[False]

reveal_type(a in c)  # revealed: Literal[False]
reveal_type(a not in c)  # revealed: Literal[True]

reveal_type(a in d)  # revealed: bool
reveal_type(a not in d)  # revealed: bool
```

### Identity Comparisons

"Identity Comparisons" refers to `is` and `is not`.

```py
a = (1, 2)
b = ("a", "b")
c = (1, 2, 3)

reveal_type(a is (1, 2))  # revealed: bool
reveal_type(a is not (1, 2))  # revealed: bool

# TODO should be Literal[False] once we implement comparison of mismatched literal types
reveal_type(a is b)  # revealed: bool
# TODO should be Literal[True] once we implement comparison of mismatched literal types
reveal_type(a is not b)  # revealed: bool

reveal_type(a is c)  # revealed: Literal[False]
reveal_type(a is not c)  # revealed: Literal[True]
```

## Homogeneous

For tuples like `tuple[int, ...]`, `tuple[Any, ...]`

// TODO
