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
24 changes: 23 additions & 1 deletion tests/test_qblike.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

from typing import Literal, Unpack

from typemap.type_eval import eval_call, eval_typing
from typemap.type_eval import (
eval_call,
eval_call_with_types,
eval_typing,
)
from typemap.typing import (
BaseTypedDict,
NewProtocol,
Expand Down Expand Up @@ -124,3 +128,21 @@ class select[...]:
class PropsOnly[tests.test_qblike.Tgt]:
name: tests.test_qblike.Property[str]
""")


def test_qblike_4():
t = eval_call_with_types(
select,
A,
x=bool,
w=bool,
z=bool,
)
fmt = format_helper.format_class(t)

assert fmt == textwrap.dedent("""\
class select[...]:
x: tests.test_qblike.Property[int]
w: tests.test_qblike.Property[list[str]]
z: tests.test_qblike.Link[tests.test_qblike.PropsOnly[tests.test_qblike.Tgt]]
""")
321 changes: 320 additions & 1 deletion tests/test_type_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@

import pytest

from typemap.type_eval import eval_typing
from typemap.type_eval import eval_call_with_types, eval_typing
from typemap.typing import (
Attrs,
FromUnion,
GenericCallable,
GetArg,
GetArgs,
GetAttr,
Expand Down Expand Up @@ -1025,3 +1026,321 @@ def test_type_eval_annotated_03():
def test_type_eval_annotated_04():
res = eval_typing(GetAnnotations[GetAttr[AnnoTest, Literal["b"]]])
assert res == Literal["blah"]


def test_type_call_callable_01():
res = eval_call_with_types(Callable[[], int])
assert res is int


def test_type_call_callable_02():
res = eval_call_with_types(Callable[[Param[Literal["x"], int]], int], int)
assert res is int


def test_type_call_callable_03():
res = eval_call_with_types(
Callable[[Param[Literal["x"], int, Literal["keyword"]]], int], x=int
)
assert res is int


def test_type_call_callable_04():
class C: ...

res = eval_call_with_types(Callable[[Param[Literal["self"], Self]], int], C)
assert res is int


def test_type_call_callable_05():
class C: ...

res = eval_call_with_types(Callable[[Param[Literal["self"], Self]], C], C)
assert res is C


def test_type_call_callable_06():
class C: ...

res = eval_call_with_types(
Callable[[Param[Literal["self"], Self], Param[Literal["x"], int]], int],
C,
int,
)
assert res is int


def test_type_call_callable_07():
class C: ...

res = eval_call_with_types(
Callable[
[
Param[Literal["self"], Self],
Param[Literal["x"], int, Literal["keyword"]],
],
int,
],
C,
x=int,
)
assert res is int


def test_type_call_callable_08():
T = TypeVar("T")
res = eval_call_with_types(Callable[[Param[Literal["x"], T]], str], int)
assert res is str


def test_type_call_callable_09():
T = TypeVar("T")
res = eval_call_with_types(Callable[[Param[Literal["x"], T]], T], int)
assert res is int


def test_type_call_callable_10():
T = TypeVar("T")

class C(Generic[T]): ...

res = eval_call_with_types(Callable[[Param[Literal["x"], C[T]]], T], C[int])
assert res is int


def test_type_call_callable_11():
T = TypeVar("T")

class C(Generic[T]): ...

class D(C[int]): ...

class E(D): ...

res = eval_call_with_types(Callable[[Param[Literal["x"], C[T]]], T], D)
assert res is int
res = eval_call_with_types(Callable[[Param[Literal["x"], C[T]]], T], E)
assert res is int


def test_type_call_local_function_01():
def func(x: int) -> int: ...

res = eval_call_with_types(func, int)
assert res is int


def test_type_call_local_function_02():
def func(*, x: int) -> int: ...

res = eval_call_with_types(func, x=int)
assert res is int


def test_type_call_local_function_03():
def func[T](x: T) -> T: ...

res = eval_call_with_types(func, int)
assert res is int


def test_type_call_local_function_04():
class C: ...

def func(x: C) -> C: ...

res = eval_call_with_types(func, C)
assert res is C


def test_type_call_local_function_05():
class C: ...

def func[T](x: T) -> T: ...

res = eval_call_with_types(func, C)
assert res is C


def test_type_call_local_function_06():
T = TypeVar("T")

class C(Generic[T]): ...

def func[U](x: C[U]) -> C[U]: ...

res = eval_call_with_types(func, C[int])
assert res == C[int]


def test_type_call_local_function_07():
T = TypeVar("T")

class C(Generic[T]): ...

class D(C[int]): ...

class E(D): ...

def func[U](x: C[U]) -> U: ...

res = eval_call_with_types(func, D)
assert res is int
res = eval_call_with_types(func, E)
assert res is int


def test_type_call_local_function_08():
class C[T]: ...

class D(C[int]): ...

class E(C[str]): ...

class F(D, E): ...

def func[U](x: C[U]) -> U: ...

res = eval_call_with_types(func, F)
assert res is int


def test_type_call_local_function_09():
class C[T, U]: ...

def func[V](x: C[int, V]) -> V: ...

res = eval_call_with_types(func, C[int, str])
assert res is str


def test_type_call_bind_error_01():
T = TypeVar("T")

with pytest.raises(
ValueError, match="Type variable T is already bound to int, but got str"
):
eval_call_with_types(
Callable[[Param[Literal["x"], T], Param[Literal["y"], T]], T],
int,
str,
)


def test_type_call_bind_error_02():
def func[T](x: T, y: T) -> T: ...

with pytest.raises(
ValueError, match="Type variable T is already bound to int, but got str"
):
eval_call_with_types(func, int, str)


def test_type_call_bind_error_03():
T = TypeVar("T")

class C(Generic[T]): ...

with pytest.raises(
ValueError, match="Type variable T is already bound to int, but got str"
):
eval_call_with_types(
Callable[[Param[Literal["x"], C[T]], Param[Literal["y"], C[T]]], T],
C[int],
C[str],
)


def test_type_call_bind_error_04():
class C[T]: ...

def func[T](x: C[T], y: C[T]) -> T: ...

with pytest.raises(
ValueError, match="Type variable T is already bound to int, but got str"
):
eval_call_with_types(func, C[int], C[str])


def test_type_call_bind_error_05():
class C[T]: ...

class D[T]: ...

def func[T](x: C[T]) -> T: ...

with pytest.raises(ValueError, match="Argument type mismatch for x"):
eval_call_with_types(func, D[int])


type GetCallableMember[T, N: str] = GetArg[
tuple[
*[
GetType[m]
for m in Iter[Members[T]]
if (
IsSub[GetType[m], Callable]
or IsSub[GetType[m], GenericCallable]
)
and IsSub[GetName[m], N]
]
],
tuple,
0,
]


def test_type_call_member_01():
class C:
def invoke(self, x: int) -> int: ...

res = eval_call_with_types(GetCallableMember[C, Literal["invoke"]], C, int)
assert res is int


def test_type_call_member_02():
class C:
def invoke[T](self, x: T) -> T: ...

res = eval_call_with_types(GetCallableMember[C, Literal["invoke"]], C, int)
assert res is int


def test_type_call_member_03():
class C[T]:
def invoke(self, x: str) -> str: ...

res = eval_call_with_types(
GetCallableMember[C[int], Literal["invoke"]], C[int], str
)
assert res is str


def test_type_call_member_04():
class C[T]:
def invoke(self, x: T) -> T: ...

res = eval_call_with_types(
GetCallableMember[C[int], Literal["invoke"]], C[int], int
)
assert res is int


def test_type_call_member_05():
class C[T]:
def invoke(self) -> C[T]: ...

res = eval_call_with_types(
GetCallableMember[C[int], Literal["invoke"]], C[int]
)
assert res == C[int]


def test_type_call_member_06():
class C[T]:
def invoke[U](self, x: U) -> C[U]: ...

res = eval_call_with_types(
GetCallableMember[C[int], Literal["invoke"]], C[int], str
)
assert res == C[str]
3 changes: 2 additions & 1 deletion typemap/type_eval/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ._apply_generic import flatten_class

# XXX: this needs to go second due to nasty circularity -- try to fix that!!
from ._eval_call import eval_call
from ._eval_call import eval_call, eval_call_with_types
from ._subtype import issubtype
from ._subsim import issubsimilar

Expand All @@ -19,6 +19,7 @@
"eval_typing",
"register_evaluator",
"eval_call",
"eval_call_with_types",
"flatten_class",
"issubtype",
"issubsimilar",
Expand Down
Loading