ruff/crates/red_knot_python_semantic/resources/mdtest
cake-monotone 6a4d207db7
[red-knot] Refactoring the inference logic of lexicographic comparisons (#14422)
## Summary

closes #14279

### Limitations of the Current Implementation
#### Incorrect Error Propagation

In the current implementation of lexicographic comparisons, if the
result of an Eq operation is Ambiguous, the comparison stops
immediately, returning a bool instance. While this may yield correct
inferences, it fails to capture unsupported-operation errors that might
occur in subsequent comparisons.
```py
class A: ...

(int_instance(), A()) < (int_instance(), A())  # should error
```

#### Weak Inference in Specific Cases

> Example: `(int_instance(), "foo") == (int_instance(), "bar")`
> Current result: `bool`
> Expected result: `Literal[False]`

`Eq` and `NotEq` have unique behavior in lexicographic comparisons
compared to other operators. Specifically:
- For `Eq`, if any non-equal pair exists within the tuples being
compared, we can immediately conclude that the tuples are not equal.
- For `NotEq`, if any equal pair exists, we can conclude that the tuples
are unequal.

```py
a = (str_instance(), int_instance(), "foo")

reveal_type(a == a)  # revealed: bool
reveal_type(a != a)  # revealed: bool

b = (str_instance(), int_instance(), "bar")

reveal_type(a == b)  # revealed: bool  # should be Literal[False]
reveal_type(a != b)  # revealed: bool  # should be Literal[True]
```
#### Incorrect Support for Non-Boolean Rich Comparisons

In CPython, aside from `==` and `!=`, tuple comparisons return a
non-boolean result as-is. Tuples do not convert the value into `bool`.

Note: If all pairwise `==` comparisons between elements in the tuples
return Truthy, the comparison then considers the tuples' lengths.
Regardless of the return type of the dunder methods, the final result
can still be a boolean.

```py
from __future__ import annotations

class A:
    def __eq__(self, o: object) -> str:
        return "hello"

    def __ne__(self, o: object) -> bytes:
        return b"world"

    def __lt__(self, o: A) -> float:
        return 3.14

a = (A(), A())

reveal_type(a == a)  # revealed: bool
reveal_type(a != a)  # revealed: bool
reveal_type(a < a)  # revealed: bool # should be: `float | Literal[False]`

```

### Key Changes
One of the major changes is that comparisons no longer end with a `bool`
result when a pairwise `Eq` result is `Ambiguous`. Instead, the function
attempts to infer all possible cases and unions the results. This
improvement allows for more robust type inference and better error
detection.

Additionally, as the function is now optimized for tuple comparisons,
the name has been changed from the more general
`infer_lexicographic_comparison` to `infer_tuple_rich_comparison`.

## Test Plan

mdtest included
2024-11-19 17:32:43 -08:00
..
annotations Understand typing.Optional in annotations (#14397) 2024-11-17 17:04:58 +00:00
assignment [red-knot] Add support for string annotations (#14151) 2024-11-15 04:10:18 +00:00
binary [red-knot] Add MRO resolution for classes (#14027) 2024-11-04 13:31:38 +00:00
boolean [red-knot] Remove Type::Unbound (#13980) 2024-10-31 20:05:53 +01:00
call [red-knot] function signature representation (#14304) 2024-11-14 23:34:24 +00:00
comparison [red-knot] Refactoring the inference logic of lexicographic comparisons (#14422) 2024-11-19 17:32:43 -08:00
conditional [red-knot] Remove Type::Unbound (#13980) 2024-10-31 20:05:53 +01:00
declaration [red-knot] Remove lint-phase (#13922) 2024-10-25 18:40:52 +00:00
exception [red-knot] function signature representation (#14304) 2024-11-14 23:34:24 +00:00
expression [red-knot] Review remaining 'possibly unbound' call sites (#14284) 2024-11-11 20:48:49 +01:00
import [red-knot] Diagnostic for possibly unbound imports (#14281) 2024-11-11 20:26:01 +01:00
literal [red-knot] Types for subexpressions of annotations (#14426) 2024-11-18 13:03:27 +01:00
loops [red-knot] Shorten the paths for some mdtest files (#14267) 2024-11-11 11:34:33 +00:00
narrow [red-knot] Narrowing for type(x) is C checks (#14432) 2024-11-18 16:21:46 +01:00
regression [red-knot] Do not attach diagnostics to wrong file (#14337) 2024-11-14 15:39:51 +01:00
scopes [red-knot] Review remaining 'possibly unbound' call sites (#14284) 2024-11-11 20:48:49 +01:00
shadowing [red-knot] have mdformat wrap mdtest files to 100 columns (#14020) 2024-10-31 21:00:51 +00:00
stubs [red-knot] Add MRO resolution for classes (#14027) 2024-11-04 13:31:38 +00:00
subscript [red-knot] Infer type of if-expression if test has statically known truthiness (#14048) 2024-11-01 12:23:18 -07:00
unary [red-knot] Infer unary not operation for instances (#13827) 2024-11-13 23:31:36 +00:00
with [red-knot] Shorten the paths for some mdtest files (#14267) 2024-11-11 11:34:33 +00:00
.mdformat.toml [red-knot] have mdformat wrap mdtest files to 100 columns (#14020) 2024-10-31 21:00:51 +00:00
attributes.md [red-knot] Add tests for member lookup on union types (#14296) 2024-11-12 14:11:55 +01:00
generics.md [red-knot] function signature representation (#14304) 2024-11-14 23:34:24 +00:00
metaclass.md [red-knot] Improve error message for metaclass conflict (#14174) 2024-11-08 11:58:57 +00:00
mro.md [red-knot] Add MRO resolution for classes (#14027) 2024-11-04 13:31:38 +00:00
sys_version_info.md [red-knot] Refactoring the inference logic of lexicographic comparisons (#14422) 2024-11-19 17:32:43 -08:00
unpacking.md Fix duplicate unpack diagnostics (#14125) 2024-11-06 11:28:29 +00:00