Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions stdlib/@tests/test_cases/check_operator.py
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)
68 changes: 56 additions & 12 deletions stdlib/_operator.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ from _typeshed import (
ReadableBuffer,
SupportsAdd,
SupportsBool,
SupportsDunderGE,
SupportsDunderGT,
SupportsDunderLE,
SupportsDunderLT,
SupportsGetItem,
SupportsMul,
SupportsRAdd,
Expand All @@ -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.

Copy link
Copy Markdown
Member

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 _typeshed

Copy link
Copy Markdown
Member Author

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 returning object more explicitly.

@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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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]):
Expand Down Expand Up @@ -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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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: ...
Expand Down
Loading