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
18 changes: 7 additions & 11 deletions spec-draft.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,12 @@ Methods are returned as callables using the new ``Param`` based extended callabl

TODO: What do we do about decorators in general, *at runtime*... This seems pretty cursed. We can probably sometimes evaluate them, if there are annotations at runtime.

We also have helpers for extracting those names; they are all definable in terms of ``GetArg``.
(These names are too long -- but we can't do ``Type``. I kind of want to do the *longer* ``MemberName``?)
We also have helpers for extracting those names; they are all definable in terms of ``GetArg``. (Some of them are shared with ``Param``, discussed below.)
(These names are too long -- but we can't do ``Type``.)

* ``GetName[T: Member]``
* ``GetType[T: Member]``
* ``GetQuals[T: Member]``
* ``GetName[T: Member | Param]``
* ``GetType[T: Member | Param]``
* ``GetQuals[T: Member | Param]``
* ``GetDefiner[T: Member]``

* ``NewProtocolWithBases[Bases, Ps: tuple[Member]]`` - A variant that allows specifying bases too. (UNIMPLEMENTED)
Expand All @@ -174,14 +174,10 @@ We also have helpers for extracting those names; they are all definable in terms
Callable inspection and creation
--------------------------------

* TODO: Should ``GetArg`` on a callable automatically convert ``[int, str]`` or whatever into something using ``Param``? Or should a separate operator be needed?
``Callable`` types always have their arguments exposed in the extended Callable format discussed above.

* ``GetParamName[T: Param]``
* ``GetParamType[T: Param]``
* ``GetParamQuals[T: Param]``
The names, type, and qualifiers share getter operations with ``Member``.

This is unsatisfying; maybe they all need to be just ``ParamName`` and also ``MemberName`` above.
We could also merge the getters for ``Param`` and ``Member``.

----

Expand Down
36 changes: 35 additions & 1 deletion tests/test_type_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
Length,
Member,
NewProtocol,
Param,
SpecialFormEllipsis,
StrConcat,
StrSlice,
Expand Down Expand Up @@ -349,7 +350,8 @@ def test_eval_getargs():
assert args == tuple[Any, Any]


def test_eval_getarg_callable():
@unittest.skip
def test_eval_getarg_callable_old():
# oh hmmmmmmm -- yeah maybe callable could be fully bespoke if we
# disallowed putting Callable here...!
t = Callable[[int, str], str]
Expand Down Expand Up @@ -377,6 +379,38 @@ def test_eval_getarg_callable():
assert args == Any


def test_eval_getarg_callable():
t = Callable[[int, str], str]
args = eval_typing(GetArg[t, Callable, 0])
assert (
args
== tuple[
Param[Literal[None], int, Never], Param[Literal[None], str, Never]
]
)

t = Callable[int, str]
args = eval_typing(GetArg[t, Callable, 0])
assert args == tuple[Param[Literal[None], int, Never]]

t = Callable[[], str]
args = eval_typing(GetArg[t, Callable, 0])
assert args == tuple[()]

# XXX: Is this what we want? Or should it be *args, **kwargs
t = Callable[..., str]
args = eval_typing(GetArg[t, Callable, 0])
assert args == SpecialFormEllipsis

t = Callable
args = eval_typing(GetArg[t, Callable, 0])
assert args == SpecialFormEllipsis

t = Callable
args = eval_typing(GetArg[t, Callable, 1])
assert args == Any


def test_eval_getarg_tuple():
t = tuple[int, ...]
args = eval_typing(GetArg[t, tuple, 1])
Expand Down
57 changes: 44 additions & 13 deletions typemap/type_eval/_eval_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,11 +500,33 @@ def _eval_FromUnion(tp, *, ctx):

@type_eval.register_evaluator(GetAttr)
@_lift_over_unions
def _eval_GetAttr(lhs, prop, *, ctx):
# TODO: the prop missing, etc!
def _eval_GetAttr(tp, prop, *, ctx):
# XXX: extras?
name = _eval_literal(prop, ctx)
return typing.get_type_hints(lhs)[name]
name = _from_literal(prop)
hints = {
**get_annotated_type_hints(tp, include_extras=True),
**get_annotated_method_hints(tp),
}
if name in hints:
return hints[name][0]
else:
return typing.Never


def _fix_callable_args(base, args):
idx = FUNC_LIKES[base]
if idx >= len(args):
return args
args = list(args)
special = _fix_type(args[idx])
if typing.get_origin(special) is tuple:
args[idx] = tuple[
*[
t if isinstance(t, Param) else Param[typing.Literal[None], t]
for t in typing.get_args(special)
]
]
return tuple(args)


def _get_raw_args(tp, base_head, ctx) -> typing.Any:
Expand All @@ -515,7 +537,11 @@ def _get_raw_args(tp, base_head, ctx) -> typing.Any:
return None

if tp_head is base_head:
return typing.get_args(evaled)
args = typing.get_args(evaled)
if _is_method_like(tp):
args = _fix_callable_args(base_head, args)

return args

# Scan the fully-annotated MRO to find the base
box = _apply_generic.box(tp)
Expand All @@ -538,10 +564,12 @@ def _get_args(tp, base, ctx) -> typing.Any:
def _fix_type(tp):
"""Fix up a type getting returned from GetArg

In particular, this means turning a list into a tuple of the list
elements and turning ... into SpecialFormEllipsis.
In particular, this means turning a list into a tuple of the
Paramified list elements and turning ... into SpecialFormEllipsis.

"""
if isinstance(tp, (tuple, list)):
# XXX: Can we always do this or is it a problem
return tuple[*tp]
elif tp is ...:
return SpecialFormEllipsis
Expand Down Expand Up @@ -708,13 +736,16 @@ def _add_quals(typ, quals):
return typ


FUNC_LIKES = {
GenericCallable: 0,
collections.abc.Callable: 0,
staticmethod: 0,
classmethod: 1,
}


def _is_method_like(typ):
return typing.get_origin(typ) in (
GenericCallable,
collections.abc.Callable,
staticmethod,
classmethod,
)
return typing.get_origin(typ) in FUNC_LIKES


@type_eval.register_evaluator(NewProtocol)
Expand Down
22 changes: 11 additions & 11 deletions typemap/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,25 @@ class GenericCallable[


class Member[N: str, T, Q: MemberQuals = typing.Never, D = typing.Never]:
pass


type GetName[T: Member] = GetArg[T, Member, 0] # type: ignore[valid-type]
type GetType[T: Member] = GetArg[T, Member, 1] # type: ignore[valid-type]
type GetQuals[T: Member] = GetArg[T, Member, 2] # type: ignore[valid-type]
type GetDefiner[T: Member] = GetArg[T, Member, 3] # type: ignore[valid-type]
name: N
typ: T
quals: Q
definer: D


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


class Param[N: str | None, T, Q: ParamQuals = typing.Never]:
pass
name: N
typ: T
quals: Q


type GetParamName[T: Param] = GetArg[T, Param, 0] # type: ignore[valid-type]
type GetParamType[T: Param] = GetArg[T, Param, 1] # type: ignore[valid-type]
type GetParamQuals[T: Param] = GetArg[T, Param, 2] # type: ignore[valid-type]
type GetName[T: Member | Param] = GetAttr[T, typing.Literal["name"]]
type GetType[T: Member | Param] = GetAttr[T, typing.Literal["typ"]]
type GetQuals[T: Member | Param] = GetAttr[T, typing.Literal["quals"]]
type GetDefiner[T: Member] = GetAttr[T, typing.Literal["definer"]]


class Attrs[T]:
Expand Down