Skip to content

Commit b0778a9

Browse files
authored
More spec work (#14)
1 parent 329054d commit b0778a9

1 file changed

Lines changed: 109 additions & 27 deletions

File tree

spec-draft.rst

Lines changed: 109 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
===================================
2+
Unpack of typevars for ``**kwargs``
3+
===================================
4+
15
A minor proposal that could be split out maybe:
26

37
Supporting ``Unpack`` of typevars for ``**kwargs``::
@@ -6,6 +10,7 @@ Supporting ``Unpack`` of typevars for ``**kwargs``::
610
return kwargs
711

812
Here ``BaseTypedDict`` is defined as::
13+
914
class BaseTypedDict(typing.TypedDict):
1015
pass
1116

@@ -15,14 +20,67 @@ This is basically a combination of "PEP 692 – Using TypedDict for more precise
1520
1621
This is potentially moderately useful on its own but is being done to support processing **kwargs with type level computation.
1722
18-
-----------------------------------------------------------------------
23+
==========================
24+
Extended Callables, take 2
25+
==========================
1926

20-
Grammar specification of the extensions to the type language.
27+
We introduce a ``Param`` type the contains all the information about a function param::
2128

22-
It's important that there be a clearly specified type language for the type-level computation---we can't just be using some poorly specified subset of all Python.
29+
class Param[N: str | None, T, Q: ParamQuals = typing.Never]:
30+
pass
31+
32+
ParamQuals = typing.Literal["*", "**", "default", "keyword"]
33+
34+
And then, we can represent the type of a function like::
35+
def func(
36+
a: int,
37+
/,
38+
b: int,
39+
c: int = 0,
40+
*args: int,
41+
d: int,
42+
e: int = 0,
43+
**kwargs: int
44+
) -> int:
45+
...
46+
47+
as (we are omiting the ``Literal`` in places)::
48+
49+
Callable[
50+
[
51+
Param[None, int],
52+
Param["b", int],
53+
Param["c", int, "default"],
54+
Param[None, int, "*"],
55+
Param["d", int, "keyword"],
56+
Param["e", int, Literal["default", "keyword"]],
57+
Param[None, int, "**"],
58+
],
59+
int,
60+
]
2361

24-
# TODO: Big Q: what should be an error and what should return Never?
2562

63+
---------
64+
Rationale
65+
---------
66+
We need extended callable support, in order to inspect and produce callables via type-level computation. mypy supports `extended callables <https://mypy.readthedocs.io/en/stable/additional_features.html#extended-callable-types>`__ but they are deprecated in favor of callback protocols.
67+
68+
69+
Unfortunately callback protocols don't work well for type level computation. (They probably could be made to work, but it would require a separate facility for creating and introspecting *methods*, which wouldn't be any simpler.)
70+
71+
I am proposing a fully new extended callable syntax because:
72+
1. The ``mypy_extensions`` functions are full no-ops, and we need real runtime objects
73+
2. They use parentheses and not brackets, which really goes against the philosophy here
74+
3. We can make an API that more nicely matches what we are going to do for inspecting members
75+
(We could introduce extended callables that closely mimic the ``mypy_extensions`` version though, if something new is a non starter)
76+
4. I thought they were missing support for something but may have been wrong. They can handle positional-only.
77+
78+
79+
============================================================
80+
Grammar specification of the extensions to the type language
81+
============================================================
82+
83+
It's important that there be a clearly specified type language for the type-level computation---we can't just be using some poorly specified subset of all Python.
2684

2785
::
2886

@@ -63,46 +121,70 @@ It's important that there be a clearly specified type language for the type-leve
63121

64122
``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.
65123

66-
---
124+
-----
125+
126+
==============
127+
Type operators
128+
==============
67129

68130
* ``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[C]): ...``, then ``GetArg[A, B, 0] == C`` while ``GetArg[A, A, 0] == Never``).
131+
N.B: *Unfortunately* ``Base`` must be a proper class, *not* a protocol. So, for example, ``GetArg[Ty, Iterable, 0]]`` to get the type of something
132+
iterable *won't* work. This is because we can't do protocol checks at runtime in general.
69133
Special forms unfortunately require some special handling: the arguments list of a ``Callable`` will be packed in a tuple, and a ``...`` will become ``SpecialFormEllipsis``.
70134

71135

72136
* ``GetArgs[T, Base]`` - returns a tuple containing all of the type arguments of ``T`` when interpreted as ``Base``, or ``Never`` if it cannot be.
73137
* ``FromUnion[T]`` - returns a tuple containing all of the union elements, or a 1-ary tuple containing T if it is not a union.
74138

75139

76-
# TODO: NewProtocol needs a way of doing bases also...
77-
# TODO: New TypedDict setup
140+
------------------------------
141+
Object inspection and creation
142+
------------------------------
78143

79144
* ``NewProtocol[*Ps: Member]``
80145

146+
81147
* ``Members[T]`` produces a ``tuple`` of ``Member`` types.
82-
* ``Member[N: Literal[str], T, Q: Quals, D]``
148+
* ``Member[N: Literal[str], T, Q: MemberQuals, D]`` - ``N`` is the name, ``T`` is the type, ``Q`` is a union of qualifiers, ``D`` is the defining class of the member
149+
* ``MemberQuals = Literal['ClassVar', 'Final']`` - ``MemberQuals`` is the type of "qualifiers" that can apply to a member; currently ClassVar and Final
150+
151+
TODO: How do we indicate ``@classmethod`` and ``@staticmethod``; should we have wrapper types for them. (That *kind of* matches reality...)
152+
153+
TODO: What do we do about decorators in general, *at runtime*...
83154

84-
# These names are too long -- but we can't do ``Type`` !!
85-
# Kind of want to do the *longer* ``MemberName``
155+
We also have helpers for extracting those names; they are all definable in terms of ``GetArg``.
156+
(These names are too long -- but we can't do ``Type``. I kind of want to do the *longer* ``MemberName``?)
86157

87158
* ``GetName[T: Member]``
88159
* ``GetType[T: Member]``
89160
* ``GetQuals[T: Member]``
90161
* ``GetDefiner[T: Member]``
91-
* Could we also put the defining type there??
92162

93-
---
163+
* ``NewProtocolWithBases[Bases, Ps: tuple[Member]]`` - A variant that allows specifying bases too. (UNIMPLEMENTED)
164+
165+
* ``NewTypedDict[*Ps: Member]`` -- TODO: Needs fleshing out; will work similarly to ``NewProtocol`` but has different flags
166+
94167

95168
* ``GetAttr[T, S: Literal[str]]``
96169
TODO: How should GetAttr interact with descriptors/classmethod? I am leaning towards it should apply the descriptor...
97170

98-
* ``Length[T: tuple]`` - get the length of a tuple as an int literal (...or ``Literal[None]`` if it is unbounded)
171+
172+
----
173+
174+
* ``Length[T: tuple]`` - get the length of a tuple as an int literal (or ``Literal[None]`` if it is unbounded)
175+
176+
----
99177

100178
String manipulation operations for string Literal types.
101179
We can put more in, but this is what typescript has.
102180
``Slice`` and ``Concat`` are a poor man's literal template.
103181
We can actually implement the case functions in terms of them and a
104182
bunch of conditionals.
105183

184+
-------------------
185+
String manipulation
186+
-------------------
187+
106188

107189
* ``Slice[S: Literal[str], Start: Literal[int | None], End: Literal[int | None]]``
108190
* ``Concat[S1: Literal[str], S2: Literal[str]]``
@@ -112,17 +194,20 @@ bunch of conditionals.
112194
* ``Capitalize[S: Literal[str]]``
113195
* ``Uncapitalize[S: Literal[str]]``
114196

197+
----
115198

199+
Two possibilities for creating parameterized functions/types. They are kind of more syntax than functions exactly. I like the lambda one more.
200+
* ``NewParameterized[V, Ty]`` - ``V`` should be a ``TypeVar`` (ugh!) and ``Ty`` should be a ``Callable`` or a ``NewProtocol`` or some such.
201+
* ``NewParameterized[lambda v: Ty]`` - The lambda could take multiple params, and introduce multiple variables. The biggest snag is how to specify bounds; one option is via default arguments.
116202

117-
-------------------------------------------------------------------------
118-
203+
How to *inspect* generic function types? Honestly, it doesn't really work in Typescript. Maybe we don't need to deal with it either.
119204

120-
Big open questions?
205+
=====================
206+
Big (open?) questions
207+
=====================
121208

122209
1.
123-
PROBABLE DECISION: external library *and* restricted checking.
124-
125-
Can we actually implement Is (IsSubtype) at runtime in a satisfactory way?
210+
Can we actually implement Is (IsSubtype) at runtime in a satisfactory way? (PROBABLE DECISION: external library *and* restricted checking.)
126211
- There is a lot that needs to happen, like protocols and variance inference and callable subtyping (which might require matching against type vars...)
127212
Jukka points out that lots of type information is frequently missing at runtime too: attributes are frequently unannotated and
128213

@@ -139,10 +224,7 @@ Can we actually implement Is (IsSubtype) at runtime in a satisfactory way?
139224

140225
It's unsatisfying, though.
141226

142-
2.
143-
DECISION: quals string literals seems fine
144-
145-
How do we deal with modifiers? ClassVar, Final, Required, ReadOnly
227+
2. How do we deal with modifiers? ClassVar, Final, Required, ReadOnly (DECISION: quals string literals seems fine)
146228
- One option is to treat them not as types by as *modifiers* and have them
147229
in a separate field where they are a union of Literals.
148230
So ``x: Final[ClassVar[int]]`` would appear in ``Attrs`` as
@@ -156,8 +238,7 @@ How do we deal with modifiers? ClassVar, Final, Required, ReadOnly
156238

157239

158240
3.
159-
How do we deal with Callables? We need to support extended callable syntax basically.
160-
Or something like it.
241+
How do we deal with Callables? We need to support extended callable syntax basically. Or something like it. (ANSWER: ``Param``)
161242

162243
4.
163244
What do we do about ``Members`` on built-in types? ``typing.get_type_hints(int)`` returns ``{}`` but mypy will not agree!
@@ -170,9 +251,10 @@ Polymorphic callables? How do we represent their type and how do we construct th
170251
What does TS do here? - TS has full impredactive polymorphic functions. You can do System F stuff. *But* trying to do type level operations on them seems to lose track of the polymorphism: the type vars will get instantiated with ``unknown``.
171252

172253
6.
173-
Want to be graceful at runtime, since **many** classes don't have full annotations.
254+
What operations should be error and what should return Never?
255+
174256

175-
=====
257+
----
176258

177259
This proposal is less "well-typed" than typescript... (Well-kinded, maybe?)
178260
Typescript has better typechecking at the alias definition site:

0 commit comments

Comments
 (0)