Skip to content

Commit cc45df1

Browse files
authored
Churning on the PEP, add a script for building it (#49)
1 parent 25e4ba0 commit cc45df1

3 files changed

Lines changed: 277 additions & 157 deletions

File tree

pre-pep.rst

Lines changed: 260 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
PEP: <REQUIRED: pep number>
2-
Title: Type-level Computation
3-
Author: Michael J. Sullivan <sully@msully.net>, Daniel Park <dnwpark@protonmail.com>, Yury Selivanov <yury@vercel.com>
1+
PEP: 9999
2+
Title: Type Manipulation!
3+
Author: Michael J. Sullivan <sully@msully.net>, Daniel W. Park <dnwpark@protonmail.com>, Yury Selivanov <yury@edgedb.com>
44
Sponsor: <name of sponsor>
55
PEP-Delegate: <PEP delegate's name>
66
Discussions-To: Pending
7-
Status: DRAFT
7+
Status: Draft
88
Type: Standards Track
99
Topic: Typing
10-
Requires: <pep numbers>
10+
Requires: 0000
1111
Created: <date created on, in dd-mmm-yyyy format>
1212
Python-Version: 3.15 or 3.16
1313
Post-History: Pending
@@ -19,15 +19,17 @@ Abstract
1919

2020
We propose to add powerful type-level type introspection and type
2121
construction facilities to the type system, inspired in large part by
22-
TypeScript's conditional and mapping types, but adapted to the quite
22+
TypeScript's conditional and mapped types, but adapted to the quite
2323
different conditions of Python typing.
2424

2525
Motivation
2626
==========
2727

2828
Python has a gradual type system, but at the heart of it is a fairly
29-
conventional and tame static type system. In Python as a language, on
30-
the other hand, it is not unusual to perform complex metaprogramming,
29+
conventional and tame static type system (apart from untagged union
30+
types and type narrowing, which are common in gradual type systems but
31+
not in traditional static ones). In Python as a language, on the
32+
other hand, it is not unusual to perform complex metaprogramming,
3133
especially at the library layer.
3234

3335
Typically, type safety is lost when doing these sorts of things. Some
@@ -422,16 +424,203 @@ Implementation
422424
]
423425

424426

425-
Rationale
426-
=========
427+
Specification of Needed Preliminaries
428+
=====================================
429+
430+
(Some content is still in `spec-draft.rst <spec-draft.rst>`_).
431+
432+
We have two subproposals that are necessary to get mileage out of the
433+
main part of this proposal.
434+
435+
436+
Unpack of typevars for ``**kwargs``
437+
-----------------------------------
438+
439+
A minor proposal that could be split out maybe:
440+
441+
Supporting ``Unpack`` of typevars for ``**kwargs``::
442+
443+
def f[K: BaseTypedDict](**kwargs: Unpack[K]) -> K:
444+
return kwargs
427445

428-
[Describe why particular design decisions were made.]
446+
Here ``BaseTypedDict`` is defined as::
447+
448+
class BaseTypedDict(typing.TypedDict):
449+
pass
450+
451+
But any typeddict would be allowed there. (Or, maybe we should allow ``dict``?)
452+
453+
This is basically a combination of
454+
"PEP 692 – Using TypedDict for more precise ``**kwargs`` typing"
455+
and the behavior of ``Unpack`` for ``*args``
456+
from "PEP 646 – Variadic Generics".
457+
458+
This is potentially moderately useful on its own but is being done to
459+
support processing ``**kwargs`` with type level computation.
460+
461+
---
462+
463+
Extended Callables, take 2
464+
--------------------------
465+
466+
We introduce a ``Param`` type the contains all the information about a function param::
467+
468+
class Param[N: str | None, T, Q: ParamQuals = typing.Never]:
469+
pass
470+
471+
ParamQuals = typing.Literal["*", "**", "default", "keyword"]
472+
473+
type PosParam[N: str | None, T] = Param[N, T, Literal["positional"]]
474+
type PosDefaultParam[N: str | None, T] = Param[N, T, Literal["positional", "default"]]
475+
type DefaultParam[N: str, T] = Param[N, T, Literal["default"]]
476+
type NamedParam[N: str, T] = Param[N, T, Literal["keyword"]]
477+
type NamedDefaultParam[N: str, T] = Param[N, T, Literal["keyword", "default"]]
478+
type ArgsParam[T] = Param[Literal[None], T, Literal["*"]]
479+
type KwargsParam[T] = Param[Literal[None], T, Literal["**"]]
480+
481+
And then, we can represent the type of a function like::
482+
483+
def func(
484+
a: int,
485+
/,
486+
b: int,
487+
c: int = 0,
488+
*args: int,
489+
d: int,
490+
e: int = 0,
491+
**kwargs: int
492+
) -> int:
493+
...
494+
495+
as (we are omiting the ``Literal`` in places)::
496+
497+
Callable[
498+
[
499+
Param["a", int, "positional"],
500+
Param["b", int],
501+
Param["c", int, "default"],
502+
Param[None, int, "*"],
503+
Param["d", int, "keyword"],
504+
Param["e", int, Literal["default", "keyword"]],
505+
Param[None, int, "**"],
506+
],
507+
int,
508+
]
509+
510+
511+
or, using the type abbreviations we provide::
512+
513+
Callable[
514+
[
515+
PosParam["a", int],
516+
Param["b", int],
517+
DefaultParam["c", int,
518+
ArgsParam[int, "*"],
519+
NamedParam["d", int],
520+
NamedDefaultParam["e", int],
521+
KwargsParam[int],
522+
],
523+
int,
524+
]
525+
526+
(Rationale discussed :ref:`below <callable-rationale>`.)
429527

430528

431529
Specification
432530
=============
433531

434-
See `spec-draft.rst <spec-draft.rst>`_ for the current draft specification.
532+
As was visible in the examples above, we introduce a few new syntactic
533+
forms of valid types, but much of the power comes from type level
534+
**operators** that will be defined in the ``typing`` module.
535+
536+
537+
Grammar specification of the extensions to the type language
538+
------------------------------------------------------------
539+
540+
Note first that no changes to the **Python** grammar are being
541+
proposed, only to the grammar of what Python expressions are
542+
considered as valid types.
543+
544+
(It's also slightly imprecise to call this a grammar: where operator
545+
names are mentioned directly, like ``IsSub``, they require that name
546+
to be imported, and it could also be used qualified as
547+
``typing.IsSub`` or imported as a different name.)
548+
549+
::
550+
551+
<type> = ...
552+
# Type booleans are all valid types too
553+
| <type-bool>
554+
555+
# Conditional types
556+
| <type> if <type-bool> else <type>
557+
558+
# Types with variadic arguments can have
559+
# *[... for t in ...] arguments
560+
| <ident>[<variadic-type-arg> +]
561+
562+
| <string-or-int-literal> # Only accepted in arguments to new functions?
563+
564+
# Type conditional checks are boolean compositions of
565+
# "subtype checking" and boolean Literal type checking.
566+
<type-bool> =
567+
IsSub[<type>, <type>]
568+
| Bool[<type>]
569+
| not <type-bool>
570+
| <type-bool> and <type-bool>
571+
| <type-bool> or <type-bool>
572+
573+
# Do we want these next two? Maybe not.
574+
| Any[<variadic-type-arg> +]
575+
| All[<variadic-type-arg> +]
576+
577+
<variadic-type-arg> =
578+
<type> ,
579+
| * <type-for-iter> ,
580+
581+
582+
<type-for> = [ <type> <type-for-iter>+ <type-for-if>* ]
583+
<type-for-iter> =
584+
# Iterate over a tuple type
585+
for <var> in Iter[<type>]
586+
<type-for-if> =
587+
if <type-bool>
588+
589+
590+
.. _rt-support:
591+
592+
593+
Runtime evaluation support
594+
--------------------------
595+
596+
Rationale
597+
=========
598+
599+
.. _callable-rationale:
600+
601+
Extended Callables
602+
------------------
603+
604+
We need extended callable support, in order to inspect and produce
605+
callables via type-level computation. mypy supports `extended
606+
callables
607+
<https://mypy.readthedocs.io/en/stable/additional_features.html#extended-callable-types>`__
608+
but they are deprecated in favor of callback protocols.
609+
610+
Unfortunately callback protocols don't work well for type level
611+
computation. (They probably could be made to work, but it would
612+
require a separate facility for creating and introspecting *methods*,
613+
which wouldn't be any simpler.)
614+
615+
I am proposing a fully new extended callable syntax because:
616+
1. The ``mypy_extensions`` functions are full no-ops, and we need
617+
real runtime objects
618+
2. They use parentheses and not brackets, which really goes against
619+
the philosophy here.
620+
3. We can make an API that more nicely matches what we are going to
621+
do for inspecting members (We could introduce extended callables that
622+
closely mimic the ``mypy_extensions`` version though, if something new
623+
is a non starter)
435624

436625

437626
Backwards Compatibility
@@ -449,7 +638,12 @@ None are expected.
449638
How to Teach This
450639
=================
451640

452-
Honestly this seems very hard!
641+
I think some inspiration can be taken from how TypeScript teaches
642+
their equivalent features.
643+
644+
(Though not complete inspiration---some important subtleties of things
645+
like mapped types are unmentioned in current documentation
646+
("homomorphic mappings").)
453647

454648

455649
Reference Implementation
@@ -461,9 +655,60 @@ Reference Implementation
461655
Rejected Ideas
462656
==============
463657

464-
* Don't attempt to support runtime evaluation, make
658+
Renounce all cares of runtime evaluation
659+
----------------------------------------
660+
661+
This would have a lot of simplifying features.
662+
663+
We wouldn't need to worry about making ``IsSub`` be checkable at
664+
runtime,
665+
666+
XXX
667+
668+
669+
Support TypeScript style pattern matching in subtype checking
670+
-------------------------------------------------------------
671+
672+
This would almost certainly only be possible if we also decide not to
673+
care about runtime evaluation, as above.
674+
675+
.. _less_syntax:
676+
677+
678+
Use type operators for conditional and iteration
679+
------------------------------------------------
680+
681+
Instead of writing:
682+
* ``tt if tb else tf``
683+
* ``*[tres for T in Iter[ttuple]]``
684+
685+
we could use type operator forms like:
686+
* ``Cond[tb, tt, tf]``
687+
* ``UnpackMap[ttuple, lambda T: tres]``
688+
* or ``UnpackMap[ttuple, T, tres]`` where ``T`` must be a declared
689+
``TypeVar``
690+
691+
Boolean operations would likewise become operators (``Not``, ``And``,
692+
etc).
693+
694+
The advantage of this is that constructing a type annotation never
695+
needs to do non-trivial computation, and thus we don't need
696+
:ref:`runtime hooks <rt-support>` to support evaluating them.
697+
698+
It would also mean that it would be much easier to extract the raw
699+
type annotation. (The lambda form would still be somewhat fiddly.
700+
The non-lambda form would be trivial to extract, but requiring the
701+
declaration of a ``TypeVar`` goes against the grain of recent
702+
changes.)
703+
704+
Another advantage is not needing any notion of a special
705+
``<type-bool>`` class of types.
706+
707+
The disadvantage is that is that the syntax seems a *lot*
708+
worse. Supporting filtering while mapping would make it even more bad
709+
(maybe an extra argument for a filter?).
465710

466-
[Why certain ideas that were brought while discussing this PEP were not ultimately pursued.]
711+
We can explore other options too if needed.
467712

468713

469714
Open Issues

scripts/build-peps.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/sh -ex
2+
3+
mkdir -p build
4+
cd build
5+
if [ ! -d peps ]; then
6+
git clone --depth=1 https://github.com/python/peps/
7+
fi
8+
cd peps/peps
9+
if [ ! -s pep-9999.rst ]; then
10+
ln -s ../../../pre-pep.rst pep-9999.rst
11+
fi
12+
cd ..
13+
make html
14+
rm -rf ../html
15+
cp -r build ../html

0 commit comments

Comments
 (0)