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>
44Sponsor: <name of sponsor>
55PEP-Delegate: <PEP delegate's name>
66Discussions-To: Pending
7- Status: DRAFT
7+ Status: Draft
88Type: Standards Track
99Topic: Typing
10- Requires: <pep numbers>
10+ Requires: 0000
1111Created: <date created on, in dd-mmm-yyyy format>
1212Python-Version: 3.15 or 3.16
1313Post-History: Pending
@@ -19,15 +19,17 @@ Abstract
1919
2020We propose to add powerful type-level type introspection and type
2121construction 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
2323different conditions of Python typing.
2424
2525Motivation
2626==========
2727
2828Python 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,
3133especially at the library layer.
3234
3335Typically, 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
431529Specification
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
437626Backwards Compatibility
@@ -449,7 +638,12 @@ None are expected.
449638How 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
455649Reference Implementation
@@ -461,9 +655,60 @@ Reference Implementation
461655Rejected 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
469714Open Issues
0 commit comments