-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[red-knot] Refactoring the inference logic of lexicographic compariso…
…ns (#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
- Loading branch information
1 parent
42c35b6
commit 6a4d207
Showing
4 changed files
with
208 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters