-
|
Pyright playground of code in this post: https://pyright-play.net/?code=JYWwDg9g... Let's say I have a container capable of addition: >>> H(2) + 2 # H[int]
H(4)
>>> H(2) + 2.0 # H[float]
H(4.0)
>>> H(2) + H(2) # H[int]
H(4)
>>> 2 + H(2) # H[int]
H(4)
>>> 2.0 + H(2) # H[float]
H(4.0)
>>> H(None) # H[None], which is like an "empty" container
H(None)
>>> H(None) + 1 # H[None]
H(None)
>>> None + H(2) # H[None]
H(None)I might try to define that as something like this: import operator
from typing import Any, Generic, Literal, TypeVar, overload
import optype
_T = TypeVar("_T", covariant=True)
_T_co = TypeVar("_T_co", covariant=True)
_OtherT = TypeVar("_OtherT")
_ResultT = TypeVar("_ResultT")
class H(Generic[_T_co]):
def __init__(self, init_val: _T_co, /) -> None:
self._val = init_val
def __repr__(self) -> str:
return f"{type(self).__qualname__}({self._val!r})"
@overload
def __add__(self: "H[None]", rhs: Any) -> "H[None]": ...
@overload
def __add__(self: "H[Any]", rhs: "H[None] | None") -> "H[None]": ...
@overload
def __add__(
self: "H[optype.CanAdd[_OtherT, _ResultT]]", rhs: "H[_OtherT]"
) -> "H[_ResultT]": ...
@overload
def __add__(
self: "H[optype.CanAdd[_OtherT, _ResultT]]", rhs: _OtherT
) -> "H[_ResultT]": ...
@overload
def __add__(
self: "H[_T]", rhs: "H[optype.CanAdd[_T, _ResultT]]"
) -> "H[_ResultT]": ...
@overload
def __add__(self: "H[_T]", rhs: optype.CanAdd[_T, _ResultT]) -> "H[_ResultT]": ...
def __add__(self, rhs: object | None) -> "H":
if self._val is None:
return H(None)
elif isinstance(rhs, H):
if rhs._val is None:
return H(None)
result = operator.add(self._val, rhs._val)
else:
if rhs is None:
return H(None)
result = operator.add(self._val, rhs)
return NotImplemented if result is NotImplemented else H(result)
@overload
def __radd__(self: "H[None]", lhs: Any) -> "H[None]": ...
@overload
def __radd__(self: "H[Any]", lhs: None) -> "H[None]": ...
@overload
def __radd__(
self: "H[optype.CanRAdd[_OtherT, _ResultT]]", lhs: _OtherT
) -> "H[_ResultT]": ...
@overload
def __radd__(self: "H[_T]", lhs: optype.CanRAdd[_T, _ResultT]) -> "H[_ResultT]": ...
def __radd__(self: "H", lhs: object | None) -> "H":
if self._val is None:
return H(None)
if isinstance(lhs, H):
result = operator.add(lhs._val, self._val)
else:
result = operator.add(lhs, self._val)
return NotImplemented if result is NotImplemented else H(result)I'd like to add handlers for @overload
def __matmul__(self: "H[None]", rhs: Any) -> "H[None]": ...
@overload
def __matmul__(self: "H[_T]", rhs: Literal[0]) -> "H[None]": ...
@overload
def __matmul__(self: "H[_T]", rhs: int) -> "H[_T]": ...
def __matmul__(self, rhs: int) -> "H":
if rhs < 0:
raise ValueError
if self._val is None or rhs == 0:
return H(None)
return sum((self._val for _ in range(rhs - 1)), start=self._val) # type: ignore[call-overload] # ty: ignore[no-matching-overload]
@overload
def __rmatmul__(self: "H[None]", lhs: Any) -> "H[None]": ...
@overload
def __rmatmul__(self: "H[_T]", lhs: Literal[0]) -> "H[None]": ...
@overload
def __rmatmul__(self: "H[_T]", lhs: int) -> "H[_T]": ...
def __rmatmul__(self, lhs: int) -> "H":
return self.__matmul__(lhs)Note that the argument to from decimal import Decimal
from fractions import Fraction
from typing import reveal_type
# should be H[None]
reveal_type(None + H(42))
# should be H[None]
reveal_type(H(None) + 42)
# should be H[None]
reveal_type(42 + H(None))
# should be H[None]
reveal_type(H(42) + None)
# should be H[None]
reveal_type(H(None) + H(42))
# should be H[None]
reveal_type(H(42) + H(None))
# should be H[int]
reveal_type(H(42) + H(42))
# should be H[float]; ty thinks this is H[Unknown]
reveal_type(H(-273.15) + H(-273.15))
# should be H[Decimal]
reveal_type(H(Decimal("98.1")) + H(Decimal("98.1")))
# should be H[Fraction]; ty thinks this is H[Fraction | int | float | complex]
reveal_type(H(Fraction(10, 6)) + H(Fraction(10, 6)))
# should be H[None]
reveal_type(2 @ H(None))
# should be H[None]
reveal_type(0 @ H(42))
# should be H[int]; ty thinks this is H[Literal[42]]
reveal_type(2 @ H(42))
# should be H[float]; ty thinks this is H[int | float]
reveal_type(2 @ H(-273.15))
# should be H[Decimal]
reveal_type(2 @ H(Decimal("98.1")))
# should be H[Fraction]
reveal_type(2 @ H(Fraction(10, 6)))
# pyright and mypy properly flag this next one as not supported. ty thinks it is fine,
# but the result is H[Unknown]. pyrefly also thinks it's fine and H[H[Unknown]] for some
# reason.
reveal_type(H(frozenset({"hello", "world"})) + H(frozenset({"hello", "world"})))
# Can we get checkers to spot this one, too?
reveal_type(2 @ H(frozenset({"hello", "world"}))) |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 4 replies
-
this one sounds like astral-sh/ty#3234
This looks like it might be an issue with ty's constraint solver. The
Ty tends to add a bunch of But in case of Pyrefly I have no idea what's going on. It's probably worth reporting though (after checking that this hasn't been solved in the aeons that it took me to answer this, of course).
I'd indeed try to narrow the |
Beta Was this translation helpful? Give feedback.
I'm guessing that this is rejected because
bool + bool -> int, andCanAddSamerequires someTof the formT + T -> T. So what you could do is useCanAddSame[int, int], so that this restriction is relaxed toT + T | int -> T | int, which I think should acceptbool.