mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:53 +00:00
Rename Red Knot (#17820)
This commit is contained in:
parent
e6a798b962
commit
b51c4f82ea
1564 changed files with 1598 additions and 1578 deletions
|
@ -0,0 +1,204 @@
|
|||
# Comparison: Membership Test
|
||||
|
||||
In Python, the term "membership test operators" refers to the operators `in` and `not in`. To
|
||||
customize their behavior, classes can implement one of the special methods `__contains__`,
|
||||
`__iter__`, or `__getitem__`.
|
||||
|
||||
For references, see:
|
||||
|
||||
- <https://docs.python.org/3/reference/expressions.html#membership-test-details>
|
||||
- <https://docs.python.org/3/reference/datamodel.html#object.__contains__>
|
||||
- <https://snarky.ca/unravelling-membership-testing/>
|
||||
|
||||
## Implements `__contains__`
|
||||
|
||||
Classes can support membership tests by implementing the `__contains__` method:
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __contains__(self, item: str) -> bool:
|
||||
return True
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `A`, in comparing `Literal[42]` with `A`"
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
# error: [unsupported-operator] "Operator `not in` is not supported for types `int` and `A`, in comparing `Literal[42]` with `A`"
|
||||
reveal_type(42 not in A()) # revealed: bool
|
||||
```
|
||||
|
||||
## Implements `__iter__`
|
||||
|
||||
Classes that don't implement `__contains__`, but do implement `__iter__`, also support containment
|
||||
checks; the needle will be sought in their iterated items:
|
||||
|
||||
```py
|
||||
class StringIterator:
|
||||
def __next__(self) -> str:
|
||||
return "foo"
|
||||
|
||||
class A:
|
||||
def __iter__(self) -> StringIterator:
|
||||
return StringIterator()
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
reveal_type(42 not in A()) # revealed: bool
|
||||
```
|
||||
|
||||
## Implements `__getitems__`
|
||||
|
||||
The final fallback is to implement `__getitem__` for integer keys. Python will call `__getitem__`
|
||||
with `0`, `1`, `2`... until either the needle is found (leading the membership test to evaluate to
|
||||
`True`) or `__getitem__` raises `IndexError` (the raised exception is swallowed, but results in the
|
||||
membership test evaluating to `False`).
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __getitem__(self, key: int) -> str:
|
||||
return "foo"
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
reveal_type(42 not in A()) # revealed: bool
|
||||
```
|
||||
|
||||
## Wrong Return Type
|
||||
|
||||
Python coerces the results of containment checks to `bool`, even if `__contains__` returns a
|
||||
non-bool:
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __contains__(self, item: str) -> str:
|
||||
return "foo"
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
```
|
||||
|
||||
## Literal Result for `in` and `not in`
|
||||
|
||||
`__contains__` with a literal return type may result in a `BooleanLiteral` outcome.
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class AlwaysTrue:
|
||||
def __contains__(self, item: int) -> Literal[1]:
|
||||
return 1
|
||||
|
||||
class AlwaysFalse:
|
||||
def __contains__(self, item: int) -> Literal[""]:
|
||||
return ""
|
||||
|
||||
reveal_type(42 in AlwaysTrue()) # revealed: Literal[True]
|
||||
reveal_type(42 not in AlwaysTrue()) # revealed: Literal[False]
|
||||
|
||||
reveal_type(42 in AlwaysFalse()) # revealed: Literal[False]
|
||||
reveal_type(42 not in AlwaysFalse()) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
## No Fallback for `__contains__`
|
||||
|
||||
If `__contains__` is implemented, checking membership of a type it doesn't accept is an error; it
|
||||
doesn't result in a fallback to `__iter__` or `__getitem__`:
|
||||
|
||||
```py
|
||||
class CheckContains: ...
|
||||
class CheckIter: ...
|
||||
class CheckGetItem: ...
|
||||
|
||||
class CheckIterIterator:
|
||||
def __next__(self) -> CheckIter:
|
||||
return CheckIter()
|
||||
|
||||
class A:
|
||||
def __contains__(self, item: CheckContains) -> bool:
|
||||
return True
|
||||
|
||||
def __iter__(self) -> CheckIterIterator:
|
||||
return CheckIterIterator()
|
||||
|
||||
def __getitem__(self, key: int) -> CheckGetItem:
|
||||
return CheckGetItem()
|
||||
|
||||
reveal_type(CheckContains() in A()) # revealed: bool
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `CheckIter` and `A`"
|
||||
reveal_type(CheckIter() in A()) # revealed: bool
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `CheckGetItem` and `A`"
|
||||
reveal_type(CheckGetItem() in A()) # revealed: bool
|
||||
|
||||
class B:
|
||||
def __iter__(self) -> CheckIterIterator:
|
||||
return CheckIterIterator()
|
||||
|
||||
def __getitem__(self, key: int) -> CheckGetItem:
|
||||
return CheckGetItem()
|
||||
|
||||
reveal_type(CheckIter() in B()) # revealed: bool
|
||||
# Always use `__iter__`, regardless of iterated type; there's no NotImplemented
|
||||
# in this case, so there's no fallback to `__getitem__`
|
||||
reveal_type(CheckGetItem() in B()) # revealed: bool
|
||||
```
|
||||
|
||||
## Invalid Old-Style Iteration
|
||||
|
||||
If `__getitem__` is implemented but does not accept integer arguments, then the membership test is
|
||||
not supported and should trigger a diagnostic.
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __getitem__(self, key: str) -> str:
|
||||
return "foo"
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `A`, in comparing `Literal[42]` with `A`"
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `str` and `A`, in comparing `Literal["hello"]` with `A`"
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
```
|
||||
|
||||
## Return type that doesn't implement `__bool__` correctly
|
||||
|
||||
`in` and `not in` operations will fail at runtime if the object on the right-hand side of the
|
||||
operation has a `__contains__` method that returns a type which is not convertible to `bool`. This
|
||||
is because of the way these operations are handled by the Python interpreter at runtime. If we
|
||||
assume that `y` is an object that has a `__contains__` method, the Python expression `x in y`
|
||||
desugars to a `contains(y, x)` call, where `contains` looks something like this:
|
||||
|
||||
```ignore
|
||||
def contains(y, x):
|
||||
return bool(type(y).__contains__(y, x))
|
||||
```
|
||||
|
||||
where the `bool()` conversion itself implicitly calls `__bool__` under the hood.
|
||||
|
||||
TODO: Ideally the message would explain to the user what's wrong. E.g,
|
||||
|
||||
```ignore
|
||||
error: [operator] cannot use `in` operator on object of type `WithContains`
|
||||
note: This is because the `in` operator implicitly calls `WithContains.__contains__`, but `WithContains.__contains__` is invalidly defined
|
||||
note: `WithContains.__contains__` is invalidly defined because it returns an instance of `NotBoolable`, which cannot be evaluated in a boolean context
|
||||
note: `NotBoolable` cannot be evaluated in a boolean context because its `__bool__` attribute is not callable
|
||||
```
|
||||
|
||||
It may also be more appropriate to use `unsupported-operator` as the error code.
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
class WithContains:
|
||||
def __contains__(self, item) -> NotBoolable:
|
||||
return NotBoolable()
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 in WithContains()
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 not in WithContains()
|
||||
```
|
|
@ -0,0 +1,391 @@
|
|||
# Comparison: Rich Comparison
|
||||
|
||||
Rich comparison operations (`==`, `!=`, `<`, `<=`, `>`, `>=`) in Python are implemented through
|
||||
double-underscore methods that allow customization of comparison behavior.
|
||||
|
||||
For references, see:
|
||||
|
||||
- <https://docs.python.org/3/reference/datamodel.html#object.__lt__>
|
||||
- <https://snarky.ca/unravelling-rich-comparison-operators/>
|
||||
|
||||
## Rich Comparison Dunder Implementations For Same Class
|
||||
|
||||
Classes can support rich comparison by implementing dunder methods like `__eq__`, `__ne__`, etc. The
|
||||
most common case involves implementing these methods for the same type:
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class EqReturnType: ...
|
||||
class NeReturnType: ...
|
||||
class LtReturnType: ...
|
||||
class LeReturnType: ...
|
||||
class GtReturnType: ...
|
||||
class GeReturnType: ...
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: A) -> EqReturnType:
|
||||
return EqReturnType()
|
||||
|
||||
def __ne__(self, other: A) -> NeReturnType:
|
||||
return NeReturnType()
|
||||
|
||||
def __lt__(self, other: A) -> LtReturnType:
|
||||
return LtReturnType()
|
||||
|
||||
def __le__(self, other: A) -> LeReturnType:
|
||||
return LeReturnType()
|
||||
|
||||
def __gt__(self, other: A) -> GtReturnType:
|
||||
return GtReturnType()
|
||||
|
||||
def __ge__(self, other: A) -> GeReturnType:
|
||||
return GeReturnType()
|
||||
|
||||
reveal_type(A() == A()) # revealed: EqReturnType
|
||||
reveal_type(A() != A()) # revealed: NeReturnType
|
||||
reveal_type(A() < A()) # revealed: LtReturnType
|
||||
reveal_type(A() <= A()) # revealed: LeReturnType
|
||||
reveal_type(A() > A()) # revealed: GtReturnType
|
||||
reveal_type(A() >= A()) # revealed: GeReturnType
|
||||
```
|
||||
|
||||
## Rich Comparison Dunder Implementations for Other Class
|
||||
|
||||
In some cases, classes may implement rich comparison dunder methods for comparisons with a different
|
||||
type:
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class EqReturnType: ...
|
||||
class NeReturnType: ...
|
||||
class LtReturnType: ...
|
||||
class LeReturnType: ...
|
||||
class GtReturnType: ...
|
||||
class GeReturnType: ...
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: B) -> EqReturnType:
|
||||
return EqReturnType()
|
||||
|
||||
def __ne__(self, other: B) -> NeReturnType:
|
||||
return NeReturnType()
|
||||
|
||||
def __lt__(self, other: B) -> LtReturnType:
|
||||
return LtReturnType()
|
||||
|
||||
def __le__(self, other: B) -> LeReturnType:
|
||||
return LeReturnType()
|
||||
|
||||
def __gt__(self, other: B) -> GtReturnType:
|
||||
return GtReturnType()
|
||||
|
||||
def __ge__(self, other: B) -> GeReturnType:
|
||||
return GeReturnType()
|
||||
|
||||
class B: ...
|
||||
|
||||
reveal_type(A() == B()) # revealed: EqReturnType
|
||||
reveal_type(A() != B()) # revealed: NeReturnType
|
||||
reveal_type(A() < B()) # revealed: LtReturnType
|
||||
reveal_type(A() <= B()) # revealed: LeReturnType
|
||||
reveal_type(A() > B()) # revealed: GtReturnType
|
||||
reveal_type(A() >= B()) # revealed: GeReturnType
|
||||
```
|
||||
|
||||
## Reflected Comparisons
|
||||
|
||||
Fallback to the right-hand side’s comparison methods occurs when the left-hand side does not define
|
||||
them. Note: class `B` has its own `__eq__` and `__ne__` methods to override those of `object`, but
|
||||
these methods will be ignored here because they require a mismatched operand type.
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class EqReturnType: ...
|
||||
class NeReturnType: ...
|
||||
class LtReturnType: ...
|
||||
class LeReturnType: ...
|
||||
class GtReturnType: ...
|
||||
class GeReturnType: ...
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: B) -> EqReturnType:
|
||||
return EqReturnType()
|
||||
|
||||
def __ne__(self, other: B) -> NeReturnType:
|
||||
return NeReturnType()
|
||||
|
||||
def __lt__(self, other: B) -> LtReturnType:
|
||||
return LtReturnType()
|
||||
|
||||
def __le__(self, other: B) -> LeReturnType:
|
||||
return LeReturnType()
|
||||
|
||||
def __gt__(self, other: B) -> GtReturnType:
|
||||
return GtReturnType()
|
||||
|
||||
def __ge__(self, other: B) -> GeReturnType:
|
||||
return GeReturnType()
|
||||
|
||||
class Unrelated: ...
|
||||
|
||||
class B:
|
||||
# To override builtins.object.__eq__ and builtins.object.__ne__
|
||||
# TODO these should emit an invalid override diagnostic
|
||||
def __eq__(self, other: Unrelated) -> B:
|
||||
return B()
|
||||
|
||||
def __ne__(self, other: Unrelated) -> B:
|
||||
return B()
|
||||
|
||||
# Because `object.__eq__` and `object.__ne__` accept `object` in typeshed,
|
||||
# this can only happen with an invalid override of these methods,
|
||||
# but we still support it.
|
||||
reveal_type(B() == A()) # revealed: EqReturnType
|
||||
reveal_type(B() != A()) # revealed: NeReturnType
|
||||
|
||||
reveal_type(B() < A()) # revealed: GtReturnType
|
||||
reveal_type(B() <= A()) # revealed: GeReturnType
|
||||
|
||||
reveal_type(B() > A()) # revealed: LtReturnType
|
||||
reveal_type(B() >= A()) # revealed: LeReturnType
|
||||
|
||||
class C:
|
||||
def __gt__(self, other: C) -> EqReturnType:
|
||||
return EqReturnType()
|
||||
|
||||
def __ge__(self, other: C) -> NeReturnType:
|
||||
return NeReturnType()
|
||||
|
||||
reveal_type(C() < C()) # revealed: EqReturnType
|
||||
reveal_type(C() <= C()) # revealed: NeReturnType
|
||||
```
|
||||
|
||||
## Reflected Comparisons with Subclasses
|
||||
|
||||
When subclasses override comparison methods, these overridden methods take precedence over those in
|
||||
the parent class. Class `B` inherits from `A` and redefines comparison methods to return types other
|
||||
than `A`.
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class EqReturnType: ...
|
||||
class NeReturnType: ...
|
||||
class LtReturnType: ...
|
||||
class LeReturnType: ...
|
||||
class GtReturnType: ...
|
||||
class GeReturnType: ...
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __ne__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __lt__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __le__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __gt__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __ge__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
class B(A):
|
||||
def __eq__(self, other: A) -> EqReturnType:
|
||||
return EqReturnType()
|
||||
|
||||
def __ne__(self, other: A) -> NeReturnType:
|
||||
return NeReturnType()
|
||||
|
||||
def __lt__(self, other: A) -> LtReturnType:
|
||||
return LtReturnType()
|
||||
|
||||
def __le__(self, other: A) -> LeReturnType:
|
||||
return LeReturnType()
|
||||
|
||||
def __gt__(self, other: A) -> GtReturnType:
|
||||
return GtReturnType()
|
||||
|
||||
def __ge__(self, other: A) -> GeReturnType:
|
||||
return GeReturnType()
|
||||
|
||||
reveal_type(A() == B()) # revealed: EqReturnType
|
||||
reveal_type(A() != B()) # revealed: NeReturnType
|
||||
|
||||
reveal_type(A() < B()) # revealed: GtReturnType
|
||||
reveal_type(A() <= B()) # revealed: GeReturnType
|
||||
|
||||
reveal_type(A() > B()) # revealed: LtReturnType
|
||||
reveal_type(A() >= B()) # revealed: LeReturnType
|
||||
```
|
||||
|
||||
## Reflected Comparisons with Subclass But Falls Back to LHS
|
||||
|
||||
In the case of a subclass, the right-hand side has priority. However, if the overridden dunder
|
||||
method has an mismatched type to operand, the comparison will fall back to the left-hand side.
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
def __lt__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __gt__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
class B(A):
|
||||
def __lt__(self, other: int) -> B:
|
||||
return B()
|
||||
|
||||
def __gt__(self, other: int) -> B:
|
||||
return B()
|
||||
|
||||
reveal_type(A() < B()) # revealed: A
|
||||
reveal_type(A() > B()) # revealed: A
|
||||
```
|
||||
|
||||
## Operations involving instances of classes inheriting from `Any`
|
||||
|
||||
`Any` and `Unknown` represent a set of possible runtime objects, wherein the bounds of the set are
|
||||
unknown. Whether the left-hand operand's dunder or the right-hand operand's reflected dunder depends
|
||||
on whether the right-hand operand is an instance of a class that is a subclass of the left-hand
|
||||
operand's class and overrides the reflected dunder. In the following example, because of the
|
||||
unknowable nature of `Any`/`Unknown`, we must consider both possibilities: `Any`/`Unknown` might
|
||||
resolve to an unknown third class that inherits from `X` and overrides `__gt__`; but it also might
|
||||
not. Thus, the correct answer here for the `reveal_type` is `int | Unknown`.
|
||||
|
||||
(This test is referenced from `mdtest/binary/instances.md`)
|
||||
|
||||
```py
|
||||
from does_not_exist import Foo # error: [unresolved-import]
|
||||
|
||||
reveal_type(Foo) # revealed: Unknown
|
||||
|
||||
class X:
|
||||
def __lt__(self, other: object) -> int:
|
||||
return 42
|
||||
|
||||
class Y(Foo): ...
|
||||
|
||||
# TODO: Should be `int | Unknown`; see above discussion.
|
||||
reveal_type(X() < Y()) # revealed: int
|
||||
```
|
||||
|
||||
## Equality and Inequality Fallback
|
||||
|
||||
This test confirms that `==` and `!=` comparisons default to identity comparisons (`is`, `is not`)
|
||||
when argument types do not match the method signature.
|
||||
|
||||
Please refer to the [docs](https://docs.python.org/3/reference/datamodel.html#object.__eq__)
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
# TODO both these overrides should emit invalid-override diagnostic
|
||||
def __eq__(self, other: int) -> A:
|
||||
return A()
|
||||
|
||||
def __ne__(self, other: int) -> A:
|
||||
return A()
|
||||
|
||||
reveal_type(A() == A()) # revealed: bool
|
||||
reveal_type(A() != A()) # revealed: bool
|
||||
```
|
||||
|
||||
## Object Comparisons with Typeshed
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
reveal_type(A() == object()) # revealed: bool
|
||||
reveal_type(A() != object()) # revealed: bool
|
||||
reveal_type(object() == A()) # revealed: bool
|
||||
reveal_type(object() != A()) # revealed: bool
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `object`"
|
||||
# revealed: Unknown
|
||||
reveal_type(A() < object())
|
||||
```
|
||||
|
||||
## Numbers Comparison with typeshed
|
||||
|
||||
```py
|
||||
reveal_type(1 == 1.0) # revealed: bool
|
||||
reveal_type(1 != 1.0) # revealed: bool
|
||||
reveal_type(1 < 1.0) # revealed: bool
|
||||
reveal_type(1 <= 1.0) # revealed: bool
|
||||
reveal_type(1 > 1.0) # revealed: bool
|
||||
reveal_type(1 >= 1.0) # revealed: bool
|
||||
|
||||
reveal_type(1 == 2j) # revealed: bool
|
||||
reveal_type(1 != 2j) # revealed: bool
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
|
||||
reveal_type(1 < 2j) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `<=` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
|
||||
reveal_type(1 <= 2j) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `>` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
|
||||
reveal_type(1 > 2j) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `>=` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
|
||||
reveal_type(1 >= 2j) # revealed: Unknown
|
||||
|
||||
def f(x: bool, y: int):
|
||||
reveal_type(x < y) # revealed: bool
|
||||
reveal_type(y < x) # revealed: bool
|
||||
reveal_type(4.2 < x) # revealed: bool
|
||||
reveal_type(x < 4.2) # revealed: bool
|
||||
```
|
||||
|
||||
## Chained comparisons with objects that don't implement `__bool__` correctly
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
Python implicitly calls `bool` on the comparison result of preceding elements (but not for the last
|
||||
element) of a chained comparison.
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
class Comparable:
|
||||
def __lt__(self, item) -> NotBoolable:
|
||||
return NotBoolable()
|
||||
|
||||
def __gt__(self, item) -> NotBoolable:
|
||||
return NotBoolable()
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 < Comparable() < 20
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 < Comparable() < Comparable()
|
||||
|
||||
Comparable() < Comparable() # fine
|
||||
```
|
||||
|
||||
## Callables as comparison dunders
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class AlwaysTrue:
|
||||
def __call__(self, other: object) -> Literal[True]:
|
||||
return True
|
||||
|
||||
class A:
|
||||
__eq__: AlwaysTrue = AlwaysTrue()
|
||||
__lt__: AlwaysTrue = AlwaysTrue()
|
||||
|
||||
reveal_type(A() == A()) # revealed: Literal[True]
|
||||
reveal_type(A() < A()) # revealed: Literal[True]
|
||||
reveal_type(A() > A()) # revealed: Literal[True]
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue