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
8 changes: 4 additions & 4 deletions spec-draft.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ We introduce a ``Param`` type the contains all the information about a function

ParamQuals = typing.Literal["*", "**", "default", "keyword"]

type PosParam[T] = Param[Literal[None], T]
type PosDefaultParam[T] = Param[Literal[None], T, Literal["default"]]
type PosParam[N: str | None, T] = Param[N, T, Literal["positional"]]
type PosDefaultParam[N: str | None, T] = Param[N, T, Literal["positional", "default"]]
type DefaultParam[N: str, T] = Param[N, T, Literal["default"]]
type NamedParam[N: str, T] = Param[N, T, Literal["keyword"]]
type NamedDefaultParam[N: str, T] = Param[N, T, Literal["keyword", "default"]]
Expand All @@ -55,7 +55,7 @@ as (we are omiting the ``Literal`` in places)::

Callable[
[
Param[None, int],
Param["a", int, "positional"],
Param["b", int],
Param["c", int, "default"],
Param[None, int, "*"],
Expand All @@ -71,7 +71,7 @@ or, using the type abbreviations we provide::

Callable[
[
PosParam[int],
PosParam["a", int],
Param["b", int],
DefaultParam["c", int,
ArgsParam[int, "*"],
Expand Down
2 changes: 1 addition & 1 deletion tests/test_type_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def test_type_dir_1b():
assert format_helper.format_class(d) == textwrap.dedent("""\
class CMethod:
@classmethod
def cbase2(_arg0: type[tests.test_type_dir.CMethod], _arg1: int, /, a: bool | None) -> int: ...
def cbase2(cls: type[tests.test_type_dir.CMethod], lol: int, /, a: bool | None) -> int: ...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh is part of the point here to be able to preserve these names?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, since we have them anyways, it's weird to just throw them away

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really part of the type anymore, though.
(Although arguably we keep other things like that...)

""")


Expand Down
183 changes: 182 additions & 1 deletion tests/test_type_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
List,
Literal,
Never,
Self,
Tuple,
TypeVar,
Union,
Expand All @@ -30,6 +31,7 @@
Iter,
Length,
Member,
Members,
NewProtocol,
Param,
SpecialFormEllipsis,
Expand Down Expand Up @@ -137,6 +139,38 @@ class F[bool]:
type NestedTree = str | list[NestedTree] | list[IntTree]


def test_eval_types_4():
d = eval_typing(
Callable[
[
Param[Literal["a"], int, Literal["positional"]],
Param[Literal["b"], int],
Param[Literal["c"], int, Literal["default"]],
Param[None, int, Literal["*"]],
Param[Literal["d"], int, Literal["keyword"]],
Param[Literal["e"], int, Literal["default", "keyword"]],
Param[None, int, Literal["**"]],
],
int,
]
)
assert (
d
== Callable[
[
Param[Literal["a"], int, Literal["positional"]],
Param[Literal["b"], int],
Param[Literal["c"], int, Literal["default"]],
Param[None, int, Literal["*"]],
Param[Literal["d"], int, Literal["keyword"]],
Param[Literal["e"], int, Literal["default", "keyword"]],
Param[None, int, Literal["**"]],
],
int,
]
)


class TA:
x: int
y: list[float]
Expand Down Expand Up @@ -381,7 +415,7 @@ def test_eval_getarg_callable_old():
assert args == Any


def test_eval_getarg_callable():
def test_eval_getarg_callable_01():
t = Callable[[int, str], str]
args = eval_typing(GetArg[t, Callable, 0])
assert (
Expand Down Expand Up @@ -413,6 +447,153 @@ def test_eval_getarg_callable():
assert args == Any


type IndirectProtocol[T] = NewProtocol[*[m for m in Iter[Members[T]]],]
type GetMethodLike[T, Name] = GetArg[
tuple[
*[
GetType[p]
for p in Iter[Members[T]]
if (
IsSub[GetType[p], Callable]
or IsSub[GetType[p], staticmethod]
or IsSub[GetType[p], classmethod]
)
and IsSub[Name, GetName[p]]
],
],
tuple,
0,
]


def test_eval_getarg_callable_02a():
class C:
def f(self, x: int, /, y: int, *, z: int) -> int: ...

f = eval_typing(GetMethodLike[C, Literal["f"]])
t = eval_typing(GetArg[f, Callable, 0])
assert (
t
== tuple[
Param[Literal["self"], C, Literal["positional"]],
Param[Literal["x"], int, Literal["positional"]],
Param[Literal["y"], int],
Param[Literal["z"], int, Literal["keyword"]],
]
)
t = eval_typing(GetArg[f, Callable, 1])
assert t is int


def test_eval_getarg_callable_02b():
class C:
def f(self, x: int, /, y: int, *, z: int) -> int: ...

f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]])
t = eval_typing(GetArg[f, Callable, 0])
assert (
t
== tuple[
Param[Literal["self"], Self, Literal["positional"]],
Param[Literal["x"], int, Literal["positional"]],
Param[Literal["y"], int],
Param[Literal["z"], int, Literal["keyword"]],
]
)
t = eval_typing(GetArg[f, Callable, 1])
assert t is int


def test_eval_getarg_callable_03a():
class C:
@classmethod
def f(cls, x: int, /, y: int, *, z: int) -> int: ...

f = eval_typing(GetMethodLike[C, Literal["f"]])
t = eval_typing(GetArg[f, classmethod, 0])
assert t == C
t = eval_typing(GetArg[f, classmethod, 1])
assert (
t
== tuple[
Param[Literal["x"], int, Literal["positional"]],
Param[Literal["y"], int],
Param[Literal["z"], int, Literal["keyword"]],
]
)
t = eval_typing(GetArg[f, classmethod, 2])
assert t is int


def test_eval_getarg_callable_03b():
class C:
@classmethod
def f(cls, x: int, /, y: int, *, z: int) -> int: ...

f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]])
t = eval_typing(GetArg[f, Callable, 0])
assert (
t
== tuple[
Param[Literal["cls"], type[C], Literal["positional"]],
Param[Literal["x"], int, Literal["positional"]],
Param[Literal["y"], int],
Param[Literal["z"], int, Literal["keyword"]],
]
)
t = eval_typing(GetArg[f, Callable, 1])
assert t is int


def test_eval_getarg_callable_04a():
class C:
@staticmethod
def f(x: int, /, y: int, *, z: int) -> int: ...

f = eval_typing(GetMethodLike[C, Literal["f"]])
t = eval_typing(GetArg[f, staticmethod, 0])
assert (
t
== tuple[
Param[Literal["x"], int, Literal["positional"]],
Param[Literal["y"], int],
Param[Literal["z"], int, Literal["keyword"]],
]
)
t = eval_typing(GetArg[f, staticmethod, 1])
assert t is int


def test_eval_getarg_callable_04b():
class C:
@staticmethod
def f(x: int, /, y: int, *, z: int) -> int: ...

f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]])
t = eval_typing(GetArg[f, Callable, 0])
assert (
t
== tuple[
Param[Literal["x"], int, Literal["positional"]],
Param[Literal["y"], int],
Param[Literal["z"], int, Literal["keyword"]],
]
)
t = eval_typing(GetArg[f, Callable, 1])
assert t is int


def test_eval_getarg_callable_05():
class C:
f: Callable[[int], int]

f = eval_typing(GetMethodLike[IndirectProtocol[C], Literal["f"]])
t = eval_typing(GetArg[f, Callable, 0])
assert t == tuple[Param[Literal[None], int, Never],]
t = eval_typing(GetArg[f, Callable, 1])
assert t is int


def test_eval_getarg_tuple():
t = tuple[int, ...]
args = eval_typing(GetArg[t, tuple, 1])
Expand Down
35 changes: 16 additions & 19 deletions typemap/type_eval/_eval_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ def _callable_type_to_signature(callable_type: object) -> inspect.Signature:
elif "keyword" in quals:
kind = inspect.Parameter.KEYWORD_ONLY
saw_keyword_only = True
elif name is None:
elif "positional" in quals or name is None:
kind = inspect.Parameter.POSITIONAL_ONLY
elif saw_keyword_only:
kind = inspect.Parameter.KEYWORD_ONLY
Expand Down Expand Up @@ -395,8 +395,10 @@ def fn(*args, **kwargs):

def _is_pos_only(param):
name, _, quals = typing.get_args(param)
name = _from_literal(name)
return name is None and not (_get_quals(quals) & {"*", "**"})
qual_set = _get_quals(quals)
return "positional" in qual_set or (
name is None and not (_get_quals(quals) & {"*", "**"})
)


def _callable_type_to_method(name, typ):
Expand All @@ -419,16 +421,9 @@ def _callable_type_to_method(name, typ):
cls, params, ret = typing.get_args(typ)
# We have to make class positional only if there is some other
# positional only argument. Annoying!
pname = (
"cls"
if not any(_is_pos_only(p) for p in typing.get_args(params))
else None
)
cls_param = Param[
typing.Literal[pname],
type[cls],
typing.Never,
]
has_pos_only = any(_is_pos_only(p) for p in typing.get_args(params))
quals = typing.Literal["positional"] if has_pos_only else typing.Never
cls_param = Param[typing.Literal["cls"], type[cls], quals]
typ = typing.Callable[[cls_param] + list(typing.get_args(params)), ret]
elif head is staticmethod:
params, ret = typing.get_args(typ)
Expand Down Expand Up @@ -485,22 +480,20 @@ def _ann(x):
else:
specified_receiver = ann

has_name = p.kind in (
inspect.Parameter.POSITIONAL_OR_KEYWORD,
inspect.Parameter.KEYWORD_ONLY,
)
quals = []
if p.kind == inspect.Parameter.VAR_POSITIONAL:
quals.append("*")
if p.kind == inspect.Parameter.VAR_KEYWORD:
quals.append("**")
if p.kind == inspect.Parameter.KEYWORD_ONLY:
quals.append("keyword")
if p.kind == inspect.Parameter.POSITIONAL_ONLY:
quals.append("positional")
if p.default is not empty:
quals.append("default")
params.append(
Param[
typing.Literal[p.name if has_name else None],
typing.Literal[p.name],
_ann(ann),
typing.Literal[*quals] if quals else typing.Never,
]
Expand Down Expand Up @@ -594,7 +587,11 @@ def _fix_callable_args(base, args):
if typing.get_origin(special) is tuple:
args[idx] = tuple[
*[
t if isinstance(t, Param) else Param[typing.Literal[None], t]
(
t
if typing.get_origin(t) is Param
else Param[typing.Literal[None], t]
)
for t in typing.get_args(special)
]
]
Expand Down
8 changes: 5 additions & 3 deletions typemap/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class Member[
definer: D


ParamQuals = Literal["*", "**", "keyword", "default"]
ParamQuals = Literal["*", "**", "keyword", "positional", "default"]


class Param[N: str | None, T, Q: ParamQuals = typing.Never]:
Expand All @@ -101,8 +101,10 @@ class Param[N: str | None, T, Q: ParamQuals = typing.Never]:
quals: Q


type PosParam[T] = Param[Literal[None], T]
type PosDefaultParam[T] = Param[Literal[None], T, Literal["default"]]
type PosParam[N: str | None, T] = Param[N, T, Literal["positional"]]
type PosDefaultParam[N: str | None, T] = Param[
N, T, Literal["positional", "default"]
]
type DefaultParam[N: str, T] = Param[N, T, Literal["default"]]
type NamedParam[N: str, T] = Param[N, T, Literal["keyword"]]
type NamedDefaultParam[N: str, T] = Param[N, T, Literal["keyword", "default"]]
Expand Down