Repository navigation
Improve ordering comparisons in operator
#16812
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import operator | ||
| from typing import overload | ||
|
|
||
|
|
||
| class Result: ... | ||
|
|
||
|
|
||
| class Operand: ... | ||
|
|
||
|
|
||
| class LessThan(Operand): | ||
| def __lt__(self, other: Operand, /) -> Result: | ||
| return Result() | ||
|
|
||
|
|
||
| class GreaterEqual: | ||
| def __ge__(self, other: Operand, /) -> Result: | ||
| return Result() | ||
|
|
||
|
|
||
| def check_comparisons(lt: LessThan, ge: GreaterEqual) -> None: | ||
| operator.lt(lt, lt) | ||
| operator.gt(lt, lt) | ||
| operator.ge(ge, lt) | ||
| operator.le(lt, ge) | ||
|
|
||
|
|
||
| def check_one_sided_comparisons(lt: LessThan, ge: GreaterEqual, other: Operand) -> None: | ||
| operator.lt(lt, other) | ||
| operator.gt(other, lt) | ||
| operator.ge(ge, other) | ||
| operator.le(other, ge) | ||
| operator.lt(1, other) # type: ignore[misc] | ||
|
|
||
| operators = {"<": operator.lt, "<=": operator.le, ">": operator.gt, ">=": operator.ge, "==": operator.eq, "!=": operator.ne} | ||
| operators["<"](lt, other) | ||
|
|
||
|
|
||
| class Overloaded: | ||
| @overload | ||
| def __lt__(self, other: int, /) -> int: ... | ||
| @overload | ||
| def __lt__(self, other: str, /) -> str: ... | ||
| def __lt__(self, other: int | str, /) -> int | str: | ||
| return other | ||
|
|
||
|
|
||
| def check_overloaded(value: Overloaded, other: int | str) -> None: | ||
| operator.lt(value, other) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,10 +3,6 @@ from _typeshed import ( | |
| ReadableBuffer, | ||
| SupportsAdd, | ||
| SupportsBool, | ||
| SupportsDunderGE, | ||
| SupportsDunderGT, | ||
| SupportsDunderLE, | ||
| SupportsDunderLT, | ||
| SupportsGetItem, | ||
| SupportsMul, | ||
| SupportsRAdd, | ||
|
|
@@ -27,7 +23,35 @@ _K = TypeVar("_K") | |
| _V = TypeVar("_V") | ||
| _P = ParamSpec("_P") | ||
|
|
||
| _SupportsComparison: TypeAlias = SupportsDunderLE[Any] | SupportsDunderGE[Any] | SupportsDunderGT[Any] | SupportsDunderLT[Any] | ||
| # operator returns the rich comparison result without testing its truth value. | ||
| @type_check_only | ||
| class _SupportsDunderLT(Protocol[_T_contra]): | ||
| def __lt__(self, other: _T_contra, /) -> object: ... | ||
|
|
||
| @type_check_only | ||
| class _SupportsDunderGT(Protocol[_T_contra]): | ||
| def __gt__(self, other: _T_contra, /) -> object: ... | ||
|
|
||
| @type_check_only | ||
| class _SupportsDunderLE(Protocol[_T_contra]): | ||
| def __le__(self, other: _T_contra, /) -> object: ... | ||
|
|
||
| @type_check_only | ||
| class _SupportsDunderGE(Protocol[_T_contra]): | ||
| def __ge__(self, other: _T_contra, /) -> object: ... | ||
|
|
||
| # Some projects use _SupportsComparison without a type argument. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand what this comment is trying to say |
||
| _OperandT = TypeVar("_OperandT", default=Any) | ||
| _SupportsComparison: TypeAlias = ( | ||
| _SupportsDunderLT[_OperandT] | _SupportsDunderGT[_OperandT] | _SupportsDunderLE[_OperandT] | _SupportsDunderGE[_OperandT] | ||
| ) | ||
|
|
||
| # _T is the argument type accepted by the other operand's ordering method. | ||
| # _SupportsComparison[Any] preserves the rule that any two operands with | ||
| # ordering methods are accepted. For example, if x.__lt__ has separate int and | ||
| # str overloads and y is int | str, neither overload accepts the whole union. | ||
| # The fallback accepts lt(x, y) because int and str each define an ordering method. | ||
| _ComparisonOperand: TypeAlias = _T | _SupportsComparison[Any] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know that ty supports type aliases to bare typevars, but I'm not totally sure they're supported by all other type checkers; I think it would also be reasonable for a type checker to reject such a type alias as invalid. Have you double-checked that all major type checkers correctly infer this as a generic type alias? |
||
|
|
||
| @type_check_only | ||
| class _SupportsInversion(Protocol[_T_co]): | ||
|
|
@@ -178,13 +202,33 @@ class _SupportsIConcat(Protocol[_T_contra, _T_co]): | |
| def __iadd__(self, value: _T_contra, /) -> _T_co: ... | ||
| def __getitem__(self, key: Any) -> Any: ... # method needs only exist | ||
|
|
||
| # All four comparison functions must have the same signature, or we get false-positive errors | ||
| def lt(a: _SupportsComparison, b: _SupportsComparison, /) -> Any: ... | ||
| def le(a: _SupportsComparison, b: _SupportsComparison, /) -> Any: ... | ||
| def eq(a: object, b: object, /) -> Any: ... | ||
| def ne(a: object, b: object, /) -> Any: ... | ||
| def ge(a: _SupportsComparison, b: _SupportsComparison, /) -> Any: ... | ||
| def gt(a: _SupportsComparison, b: _SupportsComparison, /) -> Any: ... | ||
| # Either operand can implement the comparison. Dispatch tables rely on all four | ||
| # ordering functions having the same signatures (#6462). | ||
| @overload | ||
| def lt(a: _SupportsComparison[_T], b: _ComparisonOperand[_T], /) -> Any: ... | ||
| @overload | ||
| def lt(a: _ComparisonOperand[_T], b: _SupportsComparison[_T], /) -> Any: ... | ||
|
|
||
| @overload | ||
| def le(a: _SupportsComparison[_T], b: _ComparisonOperand[_T], /) -> Any: ... | ||
| @overload | ||
| def le(a: _ComparisonOperand[_T], b: _SupportsComparison[_T], /) -> Any: ... | ||
|
|
||
| # Equality accepts any operands. Using Any also lets mypy infer a callable | ||
| # for a dispatch table containing both equality and ordering functions. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have no idea what "dispatch table" is supposed to refer to here |
||
| def eq(a: Any, b: Any, /) -> Any: ... | ||
| def ne(a: Any, b: Any, /) -> Any: ... | ||
|
|
||
| @overload | ||
| def ge(a: _SupportsComparison[_T], b: _ComparisonOperand[_T], /) -> Any: ... | ||
| @overload | ||
| def ge(a: _ComparisonOperand[_T], b: _SupportsComparison[_T], /) -> Any: ... | ||
|
|
||
| @overload | ||
| def gt(a: _SupportsComparison[_T], b: _ComparisonOperand[_T], /) -> Any: ... | ||
| @overload | ||
| def gt(a: _ComparisonOperand[_T], b: _SupportsComparison[_T], /) -> Any: ... | ||
|
|
||
| def not_(a: SupportsBool, /) -> bool: ... | ||
| def truth(a: SupportsBool, /) -> bool: ... | ||
| def is_(a: object, b: object, /) -> bool: ... | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you trying to say "and this is why we define these protocols here rather than using the ones in
_typeshed"? If so, you should say that explicitly in this comment; a casual reader of this stub won't necessarily know that there even are similar protocols defined in_typeshedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I was just trying to say "that's why these protocols return
object". But that is also why we define them here. I didn't think the latter required comment, but I can add it. And say the bit about returningobjectmore explicitly.