diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 09c407c..19684e5 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -21,7 +21,7 @@ import pytest -from typemap.type_eval import eval_typing +from typemap.type_eval import _ensure_context, eval_typing from typemap.typing import _BoolLiteral from typemap_extensions import ( @@ -508,6 +508,207 @@ def f(self, x): ... ) +def test_getmember_05(): + # member method, generic self, complex return type + class A: + def member_method[T](self: T) -> GetMemberType[T, Literal["x"]]: ... + + class B(A): + x: int + + class C(A): + x: str + + m = eval_typing(GetMember[A, Literal["member_method"]]) + assert eval_typing(GetName[m]) == Literal["member_method"] + assert eval_typing(IsAssignable[GetType[m], GenericCallable]) + assert eval_typing(GetQuals[m]) == Literal["ClassVar"] + assert eval_typing(GetDefiner[m]) == A + + ft = m.__args__[1].__args__[1] + with _ensure_context(): + assert ( + eval_typing(ft(A)) == Callable[[Param[Literal["self"], A]], Never] + ) + assert eval_typing(ft(B)) == Callable[[Param[Literal["self"], B]], int] + assert eval_typing(ft(C)) == Callable[[Param[Literal["self"], C]], str] + + +def test_getmember_06(): + # member method, generic self, complex param type + class A: + def member_method[T]( + self: T, x: GetMemberType[T, Literal["x"]] + ) -> None: ... + + class B(A): + x: int + + class C(A): + x: str + + m = eval_typing(GetMember[A, Literal["member_method"]]) + assert eval_typing(GetName[m]) == Literal["member_method"] + assert eval_typing(IsAssignable[GetType[m], GenericCallable]) + assert eval_typing(GetQuals[m]) == Literal["ClassVar"] + assert eval_typing(GetDefiner[m]) == A + + ft = m.__args__[1].__args__[1] + with _ensure_context(): + assert ( + eval_typing(ft(A)) + == Callable[ + [Param[Literal["self"], A], Param[Literal["x"], Never]], None + ] + ) + assert ( + eval_typing(ft(B)) + == Callable[ + [Param[Literal["self"], B], Param[Literal["x"], int]], None + ] + ) + assert ( + eval_typing(ft(C)) + == Callable[ + [Param[Literal["self"], C], Param[Literal["x"], str]], None + ] + ) + + +def test_getmember_07(): + # class method, generic self, complex return type + class A: + @classmethod + def class_method[T](cls: type[T]) -> GetMemberType[T, Literal["x"]]: ... + + class B(A): + x: int + + class C(A): + x: str + + m = eval_typing(GetMember[A, Literal["class_method"]]) + assert eval_typing(GetName[m]) == Literal["class_method"] + assert eval_typing(IsAssignable[GetType[m], GenericCallable]) + assert eval_typing(GetQuals[m]) == Literal["ClassVar"] + assert eval_typing(GetDefiner[m]) == A + + ft = m.__args__[1].__args__[1] + with _ensure_context(): + assert eval_typing(ft(A)) == classmethod[A, tuple[()], Never] + assert eval_typing(ft(B)) == classmethod[B, tuple[()], int] + assert eval_typing(ft(C)) == classmethod[C, tuple[()], str] + + +def test_getmember_08(): + # class method, generic self, complex param type + class A: + @classmethod + def class_method[T]( + cls: type[T], x: GetMemberType[T, Literal["x"]] + ) -> None: ... + + class B(A): + x: int + + class C(A): + x: str + + m = eval_typing(GetMember[A, Literal["class_method"]]) + assert eval_typing(GetName[m]) == Literal["class_method"] + assert eval_typing(IsAssignable[GetType[m], GenericCallable]) + assert eval_typing(GetQuals[m]) == Literal["ClassVar"] + assert eval_typing(GetDefiner[m]) == A + + ft = m.__args__[1].__args__[1] + with _ensure_context(): + assert ( + eval_typing(ft(A)) + == classmethod[A, tuple[Param[Literal["x"], Never]], None] + ) + assert ( + eval_typing(ft(B)) + == classmethod[B, tuple[Param[Literal["x"], int]], None] + ) + assert ( + eval_typing(ft(C)) + == classmethod[C, tuple[Param[Literal["x"], str]], None] + ) + + +def test_getmember_09(): + # member method, generic self, iterating over members + class A: + def f[T]( + self: T, + ) -> tuple[ + *[ + GetType[m] + for m in Iter[Attrs[T]] + if not IsAssignable[ + Slice[GetName[m], None, Literal[1]], Literal["_"] + ] + ] + ]: ... + + class B(A): + a: bool + b: str + _c: int + _d: float + + m = eval_typing(GetMember[A, Literal["f"]]) + assert eval_typing(GetName[m]) == Literal["f"] + assert eval_typing(IsAssignable[GetType[m], GenericCallable]) + assert eval_typing(GetQuals[m]) == Literal["ClassVar"] + assert eval_typing(GetDefiner[m]) == A + + ft = m.__args__[1].__args__[1] + with _ensure_context(): + assert ( + eval_typing(ft(A)) + == Callable[[Param[Literal["self"], A]], tuple[()]] + ) + assert ( + eval_typing(ft(B)) + == Callable[[Param[Literal["self"], B]], tuple[bool, str]] + ) + + +def test_getmember_10(): + # class method, generic self, iterating over members + class A: + @classmethod + def f[T]( + cls: type[T], + ) -> tuple[ + *[ + GetType[m] + for m in Iter[Attrs[T]] + if not IsAssignable[ + Slice[GetName[m], None, Literal[1]], Literal["_"] + ] + ] + ]: ... + + class B(A): + a: bool + b: str + _x: int + _y: float + + m = eval_typing(GetMember[B, Literal["f"]]) + assert eval_typing(GetName[m]) == Literal["f"] + assert eval_typing(IsAssignable[GetType[m], GenericCallable]) + assert eval_typing(GetQuals[m]) == Literal["ClassVar"] + assert eval_typing(GetDefiner[m]) == A + + ft = m.__args__[1].__args__[1] + with _ensure_context(): + assert eval_typing(ft(A)) == classmethod[A, tuple[()], tuple[()]] + assert eval_typing(ft(B)) == classmethod[B, tuple[()], tuple[bool, str]] + + def test_getarg_never(): d = eval_typing(GetArg[Never, object, Literal[0]]) assert d is Never diff --git a/typemap/type_eval/_apply_generic.py b/typemap/type_eval/_apply_generic.py index e40c451..eadc96d 100644 --- a/typemap/type_eval/_apply_generic.py +++ b/typemap/type_eval/_apply_generic.py @@ -347,8 +347,9 @@ def get_local_defns( if stuck and stuff.__type_params__: type_params = stuff.__type_params__ str_args = boxed.str_args + canonical_cls = boxed.canonical_cls - def _make_lambda(fn, o, sa, tp): + def _make_lambda(fn, o, sa, tp, cls): from ._eval_operators import _function_type_from_sig def lam(*vs): @@ -362,14 +363,16 @@ def lam(*vs): ) sig = _resolved_function_signature(fn, args) return _function_type_from_sig( - sig, o, receiver_type=None + sig, o, receiver_type=cls ) return lam gc = GenericCallable[ # type: ignore[valid-type,misc] tuple[*type_params], # type: ignore[valid-type] - _make_lambda(stuff, orig, str_args, type_params), + _make_lambda( + stuff, orig, str_args, type_params, canonical_cls + ), ] annos[name] = typing.ClassVar[gc] elif local_fn is not None: