Skip to content
Merged
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
203 changes: 202 additions & 1 deletion tests/test_type_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions typemap/type_eval/_apply_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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:
Expand Down