Skip to content

Commit 1ad2288

Browse files
authored
PEP 747: Fix rules related to UnionType (T1 | T2). Contrast TypeExpr with TypeAlias. Apply other feedback. (#3856)
1 parent 8c02849 commit 1ad2288

File tree

1 file changed

+127
-76
lines changed

1 file changed

+127
-76
lines changed

peps/pep-0747.rst

Lines changed: 127 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,8 @@ A ``TypeExpr`` value represents a :ref:`type expression <typing:type-expression>
254254
such as ``str | None``, ``dict[str, int]``, or ``MyTypedDict``.
255255
A ``TypeExpr`` type is written as
256256
``TypeExpr[T]`` where ``T`` is a type or a type variable. It can also be
257-
written without brackets as just ``TypeExpr``, in which case a type
258-
checker should apply its usual type inference mechanisms to determine
259-
the type of its argument, possibly ``Any``.
257+
written without brackets as just ``TypeExpr``, which is treated the same as
258+
to ``TypeExpr[Any]``.
260259

261260

262261
Using TypeExprs
@@ -278,7 +277,6 @@ or a variable type:
278277
::
279278

280279
STR_TYPE: TypeExpr = str # variable type
281-
assert_type(STR_TYPE, TypeExpr[str])
282280

283281
Note however that an *unannotated* variable assigned a type expression literal
284282
will not be inferred to be of ``TypeExpr`` type by type checkers because PEP
@@ -352,7 +350,7 @@ not spell a type are not ``TypeExpr`` values.
352350
::
353351

354352
OPTIONAL_INT_TYPE: TypeExpr = TypeExpr[int | None] # OK
355-
assert isassignable(Optional[int], OPTIONAL_INT_TYPE)
353+
assert isassignable(int | None, OPTIONAL_INT_TYPE)
356354

357355
.. _non_universal_typeexpr:
358356

@@ -442,14 +440,29 @@ so must be disambiguated based on its argument type:
442440
- As a value expression, ``Annotated[x, ...]`` has type ``object``
443441
if ``x`` has a type that is not ``type[C]`` or ``TypeExpr[T]``.
444442

445-
**Union**: The type expression ``T1 | T2`` is ambiguous with the value ``int1 | int2``,
446-
so must be disambiguated based on its argument type:
443+
**Union**: The type expression ``T1 | T2`` is ambiguous with
444+
the value ``int1 | int2``, ``set1 | set2``, ``dict1 | dict2``, and more,
445+
so must be disambiguated based on its argument types:
446+
447+
- As a value expression, ``x | y`` has type equal to the return type of ``type(x).__or__``
448+
if ``type(x)`` overrides the ``__or__`` method.
449+
450+
- When ``x`` has type ``builtins.type``, ``types.GenericAlias``, or the
451+
internal type of a typing special form, ``type(x).__or__`` has a return type
452+
in the format ``TypeExpr[T1 | T2]``.
453+
454+
- As a value expression, ``x | y`` has type equal to the return type of ``type(y).__ror__``
455+
if ``type(y)`` overrides the ``__ror__`` method.
456+
457+
- When ``y`` has type ``builtins.type``, ``types.GenericAlias``, or the
458+
internal type of a typing special form, ``type(y).__ror__`` has a return type
459+
in the format ``TypeExpr[T1 | T2]``.
447460

448-
- As a value expression, ``x | y`` has type ``TypeExpr[x | y]``
449-
if ``x`` has type ``TypeExpr[t1]`` (or ``type[t1]``)
450-
and ``y`` has type ``TypeExpr[t2]`` (or ``type[t2]``).
451-
- As a value expression, ``x | y`` has type ``int``
452-
if ``x`` has type ``int`` and ``y`` has type ``int``
461+
- As a value expression, ``x | y`` has type ``UnionType``
462+
in all other situations.
463+
464+
- This rule is intended to be consistent with the preexisting fallback rule
465+
used by static type checkers.
453466

454467
The **stringified type expression** ``"T"`` is ambiguous with both
455468
the stringified annotation expression ``"T"``
@@ -466,71 +479,24 @@ New kinds of type expressions that are introduced should define how they
466479
will be recognized in a value expression context.
467480

468481

469-
Implicit Annotation Expression Values
470-
'''''''''''''''''''''''''''''''''''''
471-
472-
Although this PEP is mostly concerned with *type expressions* rather than
473-
*annotation expressions*, it is straightforward to extend the rules for
474-
:ref:`recognizing type expressions <implicit_typeexpr_values>`
475-
to similar rules for recognizing annotation expressions,
476-
so this PEP takes the opportunity to define those rules as well:
477-
478-
The following **unparameterized annotation expressions** can be recognized unambiguously:
479-
480-
- As a value expression, ``X`` has type ``object``,
481-
for each of the following values of X:
482-
483-
- ``<TypeAlias>``
484-
485-
The following **parameterized annotation expressions** can be recognized unambiguously:
486-
487-
- As a value expression, ``X`` has type ``object``,
488-
for each of the following values of X:
489-
490-
- ``<Required> '[' ... ']'``
491-
- ``<NotRequired> '[' ... ']'``
492-
- ``<ReadOnly> '[' ... ']'``
493-
- ``<ClassVar> '[' ... ']'``
494-
- ``<Final> '[' ... ']'``
495-
- ``<InitVar> '[' ... ']'``
496-
- ``<Unpack> '[' ... ']'``
497-
498-
**Annotated**: The annotation expression ``Annotated[...]`` is ambiguous with
499-
the type expression ``Annotated[...]``,
500-
so must be :ref:`disambiguated based on its argument type <recognizing_annotated>`.
501-
502-
The following **syntactic annotation expressions**
503-
cannot be recognized in a value expression context at all:
504-
505-
- ``'*' unpackable``
506-
- ``name '.' 'args'`` (where ``name`` must be an in-scope ParamSpec)
507-
- ``name '.' 'kwargs'`` (where ``name`` must be an in-scope ParamSpec)
508-
509-
The **stringified annotation expression** ``"T"`` is ambiguous with both
510-
the stringified type expression ``"T"``
511-
and the string literal ``"T"``, and
512-
cannot be recognized in a value expression context at all:
513-
514-
- As a value expression, ``"T"`` continues to have type ``Literal["T"]``.
515-
516-
No other kinds of annotation expressions currently exist.
517-
518-
New kinds of annotation expressions that are introduced should define how they
519-
will (or will not) be recognized in a value expression context.
520-
521-
522482
Literal[] TypeExprs
523483
'''''''''''''''''''
524484

525-
To simplify static type checking, a ``Literal[...]`` value is *not*
526-
considered assignable to a ``TypeExpr`` variable even if all of its members
527-
spell valid types:
485+
A value of ``Literal[...]`` type is *not* considered assignable to
486+
a ``TypeExpr`` variable even if all of its members spell valid types because
487+
dynamic values are not allowed in type expressions:
528488

529489
::
530490

531491
STRS_TYPE_NAME: Literal['str', 'list[str]'] = 'str'
532492
STRS_TYPE: TypeExpr = STRS_TYPE_NAME # ERROR: Literal[] value is not a TypeExpr
533493

494+
However ``Literal[...]`` itself is still a ``TypeExpr``:
495+
496+
::
497+
498+
DIRECTION_TYPE: TypeExpr[Literal['left', 'right']] = Literal['left', 'right'] # OK
499+
534500

535501
Static vs. Runtime Representations of TypeExprs
536502
'''''''''''''''''''''''''''''''''''''''''''''''
@@ -569,19 +535,35 @@ Subtyping
569535
Whether a ``TypeExpr`` value can be assigned from one variable to another is
570536
determined by the following rules:
571537

538+
Relationship with type
539+
''''''''''''''''''''''
540+
572541
``TypeExpr[]`` is covariant in its argument type, just like ``type[]``:
573542

574543
- ``TypeExpr[T1]`` is a subtype of ``TypeExpr[T2]`` iff ``T1`` is a
575544
subtype of ``T2``.
576545
- ``type[C1]`` is a subtype of ``TypeExpr[C2]`` iff ``C1`` is a subtype
577546
of ``C2``.
578547

579-
A plain ``type`` can be assigned to a plain ``TypeExpr`` but not the
580-
other way around:
548+
An unparameterized ``type`` can be assigned to an unparameterized ``TypeExpr``
549+
but not the other way around:
581550

582551
- ``type[Any]`` is assignable to ``TypeExpr[Any]``. (But not the
583552
other way around.)
584553

554+
Relationship with UnionType
555+
'''''''''''''''''''''''''''
556+
557+
``TypeExpr[U]`` is a subtype of ``UnionType`` iff ``U`` is
558+
the type expression ``X | Y | ...``:
559+
560+
- ``TypeExpr[X | Y | ...]`` is a subtype of ``UnionType``.
561+
562+
``UnionType`` is assignable to ``TypeExpr[Any]``.
563+
564+
Relationship with object
565+
''''''''''''''''''''''''
566+
585567
``TypeExpr[]`` is a kind of ``object``, just like ``type[]``:
586568

587569
- ``TypeExpr[T]`` for any ``T`` is a subtype of ``object``.
@@ -623,11 +605,33 @@ Changed signatures
623605
''''''''''''''''''
624606

625607
The following signatures related to type expressions introduce
626-
``TypeExpr`` where previously ``object`` existed:
608+
``TypeExpr`` where previously ``object`` or ``Any`` existed:
627609

628610
- ``typing.cast``
629611
- ``typing.assert_type``
630612

613+
The following signatures transforming union type expressions introduce
614+
``TypeExpr`` where previously ``UnionType`` existed so that a more-precise
615+
``TypeExpr`` type can be inferred:
616+
617+
- ``builtins.type[T].__or__``
618+
619+
- Old: ``def __or__(self, value: Any, /) -> types.UnionType: ...``
620+
- New: ``def __or__[T2](self, value: TypeExpr[T2], /) -> TypeExpr[T | T2]: ...``
621+
622+
- ``builtins.type[T].__ror__``
623+
624+
- Old: ``def __ror__(self, value: Any, /) -> types.UnionType: ...``
625+
- New: ``def __ror__[T1](self, value: TypeExpr[T1], /) -> TypeExpr[T1 | T]: ...``
626+
627+
- ``types.GenericAlias.{__or__,__ror__}``
628+
- «the internal type of a typing special form»``.{__or__,__ror__}``
629+
630+
However the implementations of those methods continue to return ``UnionType``
631+
instances at runtime so that runtime ``isinstance`` checks like
632+
``isinstance('42', int | str)`` and ``isinstance(int | str, UnionType)``
633+
continue to work.
634+
631635

632636
Unchanged signatures
633637
''''''''''''''''''''
@@ -662,12 +666,32 @@ not propose those changes now:
662666

663667
- Returns annotation expressions
664668

669+
The following signatures accepting union type expressions continue
670+
to use ``UnionType``:
671+
672+
- ``builtins.isinstance``
673+
- ``builtins.issubclass``
674+
- ``typing.get_origin`` (used in an ``@overload``)
675+
676+
The following signatures transforming union type expressions continue
677+
to use ``UnionType`` because it is not possible to infer a more-precise
678+
``TypeExpr`` type:
679+
680+
- ``types.UnionType.{__or__,__ror__}``
681+
665682

666683
Backwards Compatibility
667684
=======================
668685

669-
Previously the rules for recognizing type expression objects
670-
in a value expression context were not defined, so static type checkers
686+
As a value expression, ``X | Y`` previously had type ``UnionType`` (via :pep:`604`)
687+
but this PEP gives it the more-precise static type ``TypeExpr[X | Y]``
688+
(a subtype of ``UnionType``) while continuing to return a ``UnionType`` instance at runtime.
689+
Preserving compability with ``UnionType`` is important because ``UnionType``
690+
supports ``isinstance`` checks, unlike ``TypeExpr``, and existing code relies
691+
on being able to perform those checks.
692+
693+
The rules for recognizing other kinds of type expression objects
694+
in a value expression context were not previously defined, so static type checkers
671695
`varied in what types were assigned <https://discuss.python.org/t/typeform-spelling-for-a-type-annotation-object-at-runtime/51435/34>`_
672696
to such objects. Existing programs manipulating type expression objects
673697
were already limited in manipulating them as plain ``object`` values,
@@ -711,12 +735,38 @@ assigned to variables and manipulated like any other data in a program:
711735
``TypeExpr[]`` is how you spell the type of a variable containing a
712736
type annotation object describing a type.
713737

714-
``TypeExpr[]`` is similar to ``type[]``, but ``type[]`` can only used to
738+
``TypeExpr[]`` is similar to ``type[]``, but ``type[]`` can only
715739
spell simple **class objects** like ``int``, ``str``, ``list``, or ``MyClass``.
716740
``TypeExpr[]`` by contrast can additionally spell more complex types,
717741
including those with brackets (like ``list[int]``) or pipes (like ``int | None``),
718742
and including special types like ``Any``, ``LiteralString``, or ``Never``.
719743

744+
A ``TypeExpr`` variable looks similar to a ``TypeAlias`` definition, but
745+
can only be used where a dynamic value is expected.
746+
``TypeAlias`` (and the ``type`` statement) by contrast define a name that can
747+
be used where a fixed type is expected:
748+
749+
- Okay, but discouraged in Python 3.12+:
750+
751+
::
752+
753+
MaybeFloat: TypeAlias = float | None
754+
def sqrt(n: float) -> MaybeFloat: ...
755+
756+
- Yes:
757+
758+
::
759+
760+
type MaybeFloat = float | None
761+
def sqrt(n: float) -> MaybeFloat: ...
762+
763+
- No:
764+
765+
::
766+
767+
maybe_float: TypeExpr = float | None
768+
def sqrt(n: float) -> maybe_float: ... # ERROR: Can't use TypeExpr value in a type annotation
769+
720770
It is uncommon for a programmer to define their *own* function which accepts
721771
a ``TypeExpr`` parameter or returns a ``TypeExpr`` value. Instead it is more common
722772
for a programmer to pass a literal type expression to an *existing* function
@@ -891,8 +941,9 @@ The following will be true when
891941
`mypy#9773 <https://github.com/python/mypy/issues/9773>`__ is implemented:
892942

893943
The mypy type checker supports ``TypeExpr`` types.
894-
A reference implementation of the runtime component is provided in the
895-
``typing_extensions`` module.
944+
945+
A reference implementation of the runtime component is provided in the
946+
``typing_extensions`` module.
896947

897948

898949
Rejected Ideas

0 commit comments

Comments
 (0)