Skip to content

Commit 3cbbbb0

Browse files
committed
Add an additional argument to GetArg to represent where to look
1 parent b5dd144 commit 3cbbbb0

6 files changed

Lines changed: 67 additions & 20 deletions

File tree

spec-draft.rst

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ Big Q: what should be an error and what should return Never?
1818
# *[... for t in ...] arguments
1919
| <ident>[<variadic-type-arg(<type>)> +]
2020

21-
# This is syntax because taking an int literal makes it a
22-
# special form.
23-
| GetArg[<type>, <int-literal>]
21+
| <string-or-int-literal> # Only accepted in arguments to new functions?
2422

2523

2624
# Type conditional checks are just boolean compositions of
@@ -48,12 +46,15 @@ Big Q: what should be an error and what should return Never?
4846
if <type-bool>
4947

5048

51-
``type-for(T)`` is a parameterized grammar rule, which can take
52-
different types. Not sure if we actually need this though---now it is
53-
only used for Any/All.
49+
``type-for(T)`` is a parameterized grammar rule, which can take different types. Not sure if we actually need this though---now it is only used for Any/All.
5450

5551
---
5652

53+
* ``GetArg[T, Base, Idx: Literal[str]]`` - returns the type argument number ``Idx`` to ``T`` when interpreted as ``Base``, or ``Never`` if it cannot be. (That is, if we have ``class A(B[int]): ...``, then ``GetArg[A, B, 0] == int``.)
54+
* ``GetArgs[T, Base]`` - returns a tuple containing all of the type arguments of ``T`` when interpreted as ``Base``, or ``Never`` if it cannot be.
55+
* ``FromUnion[T]`` - returns a tuple containing all of the union elements, or a 1-ary tuple containing T if it is not a union.
56+
57+
5758
# TODO: NewProtocol needs a way of doing bases also...
5859
# TODO: New TypedDict setup
5960

@@ -77,10 +78,6 @@ only used for Any/All.
7778

7879
# TODO: how to deal with special forms like Callable and tuple[T, ...]
7980

80-
* ``GetArgs[T]`` - returns a tuple containing all of the type arguments
81-
* ``FromUnion[T]`` - returns a tuple containing all of the union
82-
elements, or a 1-ary tuple containing T if it is not a union.
83-
8481
# TODO: How to do IsUnion? Might need a ``Length`` for tuples?
8582

8683

tests/test_qblike.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class Link[T]:
3131
]
3232

3333
# Conditional type alias!
34-
type FilterLinks[T] = Link[PropsOnly[GetArg[T, 0]]] if Is[T, Link] else T
34+
type FilterLinks[T] = Link[PropsOnly[GetArg[T, Link, 0]]] if Is[T, Link] else T
3535

3636

3737
# Basic filtering

tests/test_type_dir.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import textwrap
2-
from typing import TypeVar, Literal, Union
2+
from typing import Never, Literal, Union, TypeVar
33

44
from typemap.type_eval import eval_typing
55
from typemap.typing import (
66
NewProtocol,
77
Member,
8+
GetArg,
89
GetName,
910
GetType,
1011
Iter,
@@ -65,10 +66,14 @@ class Last[O]:
6566
last: O | Literal[True]
6667

6768

69+
# XXX: the mixing of int and float as arguments to Base is questionable!
6870
class Final(Mine, Ordinary, Wrapper[float], AnotherBase[float], Last[int]):
6971
pass
7072

7173

74+
type BaseArg[T] = GetArg[T, Base, 0] if Is[T, Base] else Never
75+
76+
7277
type AllOptional[T] = NewProtocol[
7378
*[Member[GetName[p], GetType[p] | None] for p in Iter[Attrs[T]]]
7479
]
@@ -239,3 +244,8 @@ def test_type_dir_7():
239244
typemap.typing.Param[typing.Literal['b'], int, typing.Literal['=']]], \
240245
dict[str, int]], typing.Literal['ClassVar'], typing.Never]"
241246
)
247+
248+
249+
def test_type_dir_8():
250+
d = eval_typing(BaseArg[Final])
251+
assert d is int

typemap/type_eval/_apply_generic.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,5 @@ def apply(cls: type[Any]) -> dict[str, Any]:
226226
dct[k] = _eval_typing.eval_typing(v)
227227

228228
dct["__annotations__"] = annos
229+
dct["__generalized_mro__"] = mro_boxed
229230
return dct

typemap/type_eval/_typing_inspect.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ def is_literal(t: Any) -> bool:
125125
return is_generic_alias(t) and get_origin(t) is Literal # type: ignore [comparison-overlap]
126126

127127

128+
def get_head(t: Any) -> type | None:
129+
if is_generic_alias(t):
130+
return get_origin(t)
131+
elif isinstance(t, type):
132+
return t
133+
else:
134+
return None
135+
136+
128137
def is_eval_proxy(t: Any) -> TypeGuard[type[_eval_typing._EvalProxy]]:
129138
return isinstance(t, type) and issubclass(t, _eval_typing._EvalProxy)
130139

typemap/typing.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ class Member[N: str, T, Q: str = typing.Never, D = typing.Never]:
8080
pass
8181

8282

83-
type GetName[T: Member] = GetArg[T, 0] # type: ignore[valid-type]
84-
type GetType[T: Member] = GetArg[T, 1] # type: ignore[valid-type]
85-
type GetQuals[T: Member] = GetArg[T, 2] # type: ignore[valid-type]
86-
type GetDefiner[T: Member] = GetArg[T, 3] # type: ignore[valid-type]
83+
type GetName[T: Member] = GetArg[T, Member, 0] # type: ignore[valid-type]
84+
type GetType[T: Member] = GetArg[T, Member, 1] # type: ignore[valid-type]
85+
type GetQuals[T: Member] = GetArg[T, Member, 2] # type: ignore[valid-type]
86+
type GetDefiner[T: Member] = GetArg[T, Member, 3] # type: ignore[valid-type]
8787

8888

8989
##################################################################
@@ -233,12 +233,42 @@ def GetAttr(self, arg):
233233
return typing.get_type_hints(type_eval.eval_typing(lhs))[name]
234234

235235

236+
def _get_args(tp, base) -> typing.Any:
237+
# XXX: check against base!!
238+
evaled = type_eval.eval_typing(tp)
239+
240+
tp_head = _typing_inspect.get_head(tp)
241+
base_head = _typing_inspect.get_head(base)
242+
# XXX: not sure this is what we want!
243+
# at the very least we want unions I think
244+
if not tp_head or not base_head:
245+
return None
246+
247+
if tp_head is base_head:
248+
return typing.get_args(evaled)
249+
250+
# Scan the fully-annotated MRO to find the base
251+
elif gen_mro := getattr(evaled, "__generalized_mro__", None):
252+
for box in gen_mro:
253+
if box.cls is base_head:
254+
return tuple(box.args.values())
255+
return None
256+
257+
else:
258+
# or error??
259+
return None
260+
261+
236262
@_SpecialForm
237-
def GetArg(self, arg):
238-
tp, idx = arg
239-
args = typing.get_args(type_eval.eval_typing(tp))
263+
def GetArg(self, arg) -> typing.Any:
264+
# XXX: Unions
265+
tp, base, idx = arg
266+
args = _get_args(tp, base)
267+
if args is None:
268+
return typing.Never
269+
240270
try:
241-
return args[idx]
271+
return args[_from_literal(idx)]
242272
except IndexError:
243273
return typing.Never
244274

0 commit comments

Comments
 (0)