diff --git a/peps/pep-0747.rst b/peps/pep-0747.rst index 98bc6f080e5..1a97983ca7b 100644 --- a/peps/pep-0747.rst +++ b/peps/pep-0747.rst @@ -442,27 +442,23 @@ so must be disambiguated based on its argument type: **Union**: The type expression ``T1 | T2`` is ambiguous with the value ``int1 | int2``, ``set1 | set2``, ``dict1 | dict2``, and more, -so must be disambiguated based on its argument types: +so must use the explicit ``TypeExpr(...)`` syntax: + +- Yes: -- As a value expression, ``x | y`` has type equal to the return type of ``type(x).__or__`` - if ``type(x)`` overrides the ``__or__`` method. + :: - - When ``x`` has type ``builtins.type``, ``types.GenericAlias``, or the - internal type of a typing special form, ``type(x).__or__`` has a return type - in the format ``TypeExpr[T1 | T2]``. + if isassignable(value, TypeExpr(int | str)): ... -- As a value expression, ``x | y`` has type equal to the return type of ``type(y).__ror__`` - if ``type(y)`` overrides the ``__ror__`` method. +- No: - - When ``y`` has type ``builtins.type``, ``types.GenericAlias``, or the - internal type of a typing special form, ``type(y).__ror__`` has a return type - in the format ``TypeExpr[T1 | T2]``. + :: -- As a value expression, ``x | y`` has type ``UnionType`` - in all other situations. + if isassignable(value, int | str): ... - - This rule is intended to be consistent with the preexisting fallback rule - used by static type checkers. +Future PEPs may make it possible to recognize the value expression ``T1 | T2`` directly as an +implicit TypeExpr value and avoid the need to use the explicit ``TypeExpr(...)`` syntax, +but that work is :ref:`deferred for now `. The **stringified type expression** ``"T"`` is ambiguous with both the stringified annotation expression ``"T"`` @@ -551,16 +547,6 @@ but not the other way around: - ``type[Any]`` is assignable to ``TypeExpr[Any]``. (But not the other way around.) -Relationship with UnionType -''''''''''''''''''''''''''' - -``TypeExpr[U]`` is a subtype of ``UnionType`` iff ``U`` is -the type expression ``X | Y | ...``: - -- ``TypeExpr[X | Y | ...]`` is a subtype of ``UnionType``. - -``UnionType`` is assignable to ``TypeExpr[Any]``. - Relationship with object '''''''''''''''''''''''' @@ -610,29 +596,6 @@ The following signatures related to type expressions introduce - ``typing.cast`` - ``typing.assert_type`` -The following signatures transforming union type expressions introduce -``TypeExpr`` where previously ``UnionType`` existed so that a more-precise -``TypeExpr`` type can be inferred: - -- ``builtins.type[T].__or__`` - - - Old: ``def __or__(self, value: Any, /) -> types.UnionType: ...`` - - New: ``def __or__[T2](self, value: TypeExpr[T2], /) -> TypeExpr[T | T2]: ...`` - -- ``builtins.type[T].__ror__`` - - - Old: ``def __ror__(self, value: Any, /) -> types.UnionType: ...`` - - New: ``def __ror__[T1](self, value: TypeExpr[T1], /) -> TypeExpr[T1 | T]: ...`` - -- ``types.GenericAlias.{__or__,__ror__}`` -- «the internal type of a typing special form»``.{__or__,__ror__}`` - -However the implementations of those methods continue to return ``UnionType`` -instances at runtime so that runtime ``isinstance`` checks like -``isinstance('42', int | str)`` and ``isinstance(int | str, UnionType)`` -continue to work. - - Unchanged signatures '''''''''''''''''''' @@ -666,31 +629,11 @@ not propose those changes now: - Returns annotation expressions -The following signatures accepting union type expressions continue -to use ``UnionType``: - -- ``builtins.isinstance`` -- ``builtins.issubclass`` -- ``typing.get_origin`` (used in an ``@overload``) - -The following signatures transforming union type expressions continue -to use ``UnionType`` because it is not possible to infer a more-precise -``TypeExpr`` type: - -- ``types.UnionType.{__or__,__ror__}`` - Backwards Compatibility ======================= -As a value expression, ``X | Y`` previously had type ``UnionType`` (via :pep:`604`) -but this PEP gives it the more-precise static type ``TypeExpr[X | Y]`` -(a subtype of ``UnionType``) while continuing to return a ``UnionType`` instance at runtime. -Preserving compability with ``UnionType`` is important because ``UnionType`` -supports ``isinstance`` checks, unlike ``TypeExpr``, and existing code relies -on being able to perform those checks. - -The rules for recognizing other kinds of type expression objects +The rules for recognizing type expression objects in a value expression context were not previously defined, so static type checkers `varied in what types were assigned `_ to such objects. Existing programs manipulating type expression objects @@ -741,10 +684,16 @@ spell simple **class objects** like ``int``, ``str``, ``list``, or ``MyClass``. including those with brackets (like ``list[int]``) or pipes (like ``int | None``), and including special types like ``Any``, ``LiteralString``, or ``Never``. -A ``TypeExpr`` variable looks similar to a ``TypeAlias`` definition, but -can only be used where a dynamic value is expected. -``TypeAlias`` (and the ``type`` statement) by contrast define a name that can -be used where a fixed type is expected: +A ``TypeExpr`` variable (``maybe_float: TypeExpr``) looks similar to +a ``TypeAlias`` definition (``MaybeFloat: TypeAlias``), but ``TypeExpr`` +can only be used where a dynamic value is expected: + +- No: + + :: + + maybe_float: TypeExpr = float | None + def sqrt(n: float) -> maybe_float: ... # ERROR: Can't use TypeExpr value in a type annotation - Okay, but discouraged in Python 3.12+: @@ -760,13 +709,6 @@ be used where a fixed type is expected: type MaybeFloat = float | None def sqrt(n: float) -> MaybeFloat: ... -- No: - - :: - - maybe_float: TypeExpr = float | None - def sqrt(n: float) -> maybe_float: ... # ERROR: Can't use TypeExpr value in a type annotation - It is uncommon for a programmer to define their *own* function which accepts a ``TypeExpr`` parameter or returns a ``TypeExpr`` value. Instead it is more common for a programmer to pass a literal type expression to an *existing* function @@ -1072,6 +1014,20 @@ The example above could be more-straightforwardly written as the equivalent: def checkcast(typx: TypeExpr[T], value: object) -> T: +.. _recognize_uniontype_as_implicit_typeexpr_value: + +Recognize (T1 | T2) as an implicit TypeExpr value +------------------------------------------------- + +It would be nice if a value expression like ``int | str`` could be recognized +as an implicit ``TypeExpr`` value and be used directly in a context where a +``TypeExpr`` was expected. However making that possible would require making +changes to the rules that type checkers use for the ``|`` operator. These rules +are currently underspecified and would need to be make explicit first, +before making changes to them. The PEP author is not sufficently motivated to +take on that specification work at the time of writing. + + Footnotes =========