From 4e882131741aa151ac2de557553492a5bfde0ea5 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 14 Oct 2026 12:36:20 -0700 Subject: [PATCH 1/7] Get rid of the lambda in GenericCallable. --- peps/pep-0827.rst | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index 0df2ebf9b0b..80cacb0b578 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -503,8 +503,6 @@ to the grammar of what Python expressions are considered as valid types. # Type member access | . - | GenericCallable[, lambda : ] - # Type conditional checks are boolean compositions of # boolean type operators = @@ -539,9 +537,6 @@ Where: There are three and a half core syntactic features introduced: type booleans, conditional types, unpacked comprehension types, and type member access. -:ref:`"Generic callables" ` are also technically a -syntactic feature, but are discussed as an operator. - Type booleans ''''''''''''' @@ -828,10 +823,10 @@ exposes ``K`` and a ``.default`` associated type, which exposes ``D``. Generic Callable '''''''''''''''' -* ``GenericCallable[Vs, lambda : Ty]``: A generic callable. ``Vs`` +* ``GenericCallable[Vs, Ty]``: A generic callable. ``Vs`` are a tuple type of unbound type variables and ``Ty`` should be a ``Callable``, ``staticmethod``, or ``classmethod`` that has access - to the variables in ``Vs`` via the bound variables in ````. + to the variables in ``Vs``. For now, we restrict the use of ``GenericCallable`` to the type argument of ``Member``, to disallow its use for @@ -1346,8 +1341,8 @@ The type of the method is generic, and the generic is bound at the **method**, not the class. We need a way to represent such a generic function as a programmer might write it for a ``NewProtocol``. -One option that is somewhat appealing but doesn't work would be to use -unbound type variables and let them be generalized:: +One option that we aren't taking is simply allowing the use of +unbound type variables and letting them be generalized:: type Foo = NewProtocol[ Member[ @@ -1356,29 +1351,20 @@ unbound type variables and let them be generalized:: ] ] -The problem is that this is basically incompatible with runtime -evaluation support, since evaluating the alias ``Foo`` will need to -evaluate the ``IsAssignable``, and so we will lose one side of the -conditional at least. Similar problems will happen when evaluating -``Members`` on a class with generic functions. By wrapping the body -in a lambda, we can delay evaluation in both of these cases. (The -``Members`` case of delaying evaluation works quite nicely for -functions with explicit generic annotations. For old-style generics, -we'll probably have to try to evaluate it and then raise an error when -we encounter a variable.) - -With our real syntax, this looks like:: +We felt that we wanted an explicit binding site for type variables, +though, and so we specify them with ``GenericCallable``:: type Foo = NewProtocol[ Member[ Literal["process"], GenericCallable[ tuple[T], - lambda T: Callable[[T], T if IsAssignable[T, list] else list[T]], + Callable[[T], T if IsAssignable[T, list] else list[T]], ], ] ] + The reason we suggest restricting the use of ``GenericCallable`` to the type argument of ``Member`` is because impredicative polymorphism (where you can instantiate type variables with other @@ -1387,10 +1373,6 @@ positions deep inside function types) are cans of worms when combined with type inference [#undecidable]_. While it would be nice to support, we don't want to open that can of worms now. -The unbound type variable tuple is so that bounds and defaults and -``TypeVarTuple``-ness can be specified, though maybe we want to come -up with a new approach. - Backwards Compatibility ======================= From 2bc8bf2c13ceebc5b9d4438525e071f5e92034da Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 14 Oct 2026 15:23:47 -0700 Subject: [PATCH 2/7] Get rid of runtime evaluation hooks --- peps/pep-0827.rst | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index 80cacb0b578..ca63d98620f 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -601,10 +601,7 @@ ones. This section defines the operators being introduced, and explains how they are to be evaluated in a typechecking or type evaluation context. The actual runtime classes being introduced, though, are just regular classes, -and subscripting them produces normal ``typing`` generic alias objects -(with the partial exception of the boolean operators and ``Iter``, -which produce aliases that have some dunder methods overloaded for -:ref:`runtime hooks `). +and subscripting them produces normal ``typing`` generic alias objects. Many of the operators specified have type bounds listed for some of their operands. These should be interpreted more as documentation than @@ -911,24 +908,19 @@ library, but intend to release a third-party evaluator library. While most of the extensions to the type system are "inert" type operator applications, the syntax also includes list iteration, -conditionals, and attribute access, which will be automatically +conditionals, and attribute access, which would ordinarily be evaluated when the ``__annotate__`` method of a class, alias, or function is called. -In order to allow an evaluator library to trigger type evaluation in -those cases, we add a new hook to ``typing``: - -* ``special_form_evaluator``: This is a ``ContextVar`` that holds a - callable that will be invoked with a ``typing._GenericAlias`` - argument when ``__bool__`` is called on a - :ref:`Boolean Operator ` or ``__iter__`` is called - on ``typing.Iter``. - The returned value will then have ``bool`` or ``iter`` called upon - it before being returned. - - If set to ``None`` (the default), the boolean operators will return - ``False`` while ``Iter`` will evaluate to ``iter(())``. +To address this, we depend on :pep:`849` (or something essentially +isomorphic to it), which will allow retrieving the AST (or string) +representation of arbitrary annotations. +:pep:`849` introduces a new helper function +``typing.evaluate_type_ast`` that converts an AST into typing +objects. It will be extended to understand the new syntax +introduces in this PEP and produce appropriate ``typing`` objects +to represent them. There has been some discussion of adding a ``Format.AST`` mode for fetching annotations (see this `PEP draft <#ast_format_>`_). That @@ -1123,7 +1115,7 @@ based on iterating over all attributes. p.name, p.type, # All arguments are keyword-only - Literal[ParamKind.KEYWORD_ONLY], + Literal[typing.ParamKind.KEYWORD_ONLY], # GetDefault is Never when there's no default, so use it # directly as D. GetDefault[p.init], @@ -1905,7 +1897,6 @@ substantial influence on this proposal! Footnotes ========= -.. _#broadcasting: https://numpy.org/doc/stable/user/basics.broadcasting.html .. _#fastapi: https://fastapi.tiangolo.com/ .. _#pydantic: https://docs.pydantic.dev/latest/ .. _#fastapi-tutorial: https://fastapi.tiangolo.com/tutorial/sql-databases/#heroupdate-the-data-model-to-update-a-hero @@ -1917,7 +1908,6 @@ Footnotes .. _#runtime: https://github.com/vercel/python-typemap .. _#starlark: https://starlark-lang.org/ .. _#survey: https://engineering.fb.com/2025/12/22/developer-tools/python-typing-survey-2025-code-quality-flexibility-typing-adoption/ -.. _#ast_format: https://imogenbits-peps.readthedocs.io/en/ast_format/pep-9999/ .. [#undecidable] From 7cee918d70814199c3aa75f9f8e6e67038f2ebc9 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 14 Oct 2026 15:33:49 -0700 Subject: [PATCH 3/7] Get rid of Bool and Iter --- peps/pep-0827.rst | 105 ++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 70 deletions(-) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index ca63d98620f..d0de2eb93b5 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -503,15 +503,13 @@ to the grammar of what Python expressions are considered as valid types. # Type member access | . - # Type conditional checks are boolean compositions of - # boolean type operators + # Type conditional checks are boolean compositions of types = - [ +] - | not - | and - | or - | any() - | all() + not + | and + | or + | any() + | all() = , @@ -521,30 +519,19 @@ to the grammar of what Python expressions are considered as valid types. = + * = # Iterate over a tuple type - for in Iter[] + for in = if -Where: - -* ```` refers to any of the names defined in the - :ref:`Boolean Operators ` section, whether used directly, - qualified, or under another name. - -* ```` is identical to ```` except that the - result type is a ```` instead of a ````. - There are three and a half core syntactic features introduced: type booleans, conditional types, unpacked comprehension types, and type member access. Type booleans ''''''''''''' -Type booleans are a special subset of the type language that can be -used in the body of conditionals. They consist of the :ref:`Boolean -Operators `, defined below, potentially combined with +Type booleans consist of type expressions combined with ``and``, ``or``, ``not``, ``all``, and ``any``. For ``all`` and -``any``, the argument is a comprehension of type booleans, evaluated +``any``, the argument is a comprehension of types, evaluated in the same way as the :ref:`unpacked comprehensions `. When evaluated in type annotation context, they will be equivalent to @@ -563,15 +550,12 @@ The type ``true_typ if bool_typ else false_typ`` is a conditional type, which resolves to ``true_typ`` if ``bool_typ`` is equivalent to ``Literal[True]`` and to ``false_typ`` otherwise. -``bool_typ`` is a type, but it needs to syntactically be a type boolean, -defined above. - .. _pep827-unpacked: Unpacked comprehension '''''''''''''''''''''' -An unpacked comprehension, ``*[ty for t in Iter[iter_ty]]`` may appear +An unpacked comprehension, ``*[ty for t in iter_ty]`` may appear anywhere in a type that ``Unpack[...]`` is currently allowed, and it evaluates essentially to an ``Unpack`` of a tuple produced by a list comprehension iterating over the arguments of tuple type ``iter_ty``. @@ -631,12 +615,6 @@ Boolean operators Technically this relation is "consistency" in the typing spec, not equivalence. -* ``Bool[T]``: Returns ``Literal[True]`` if ``T`` is also ``Literal[True]``. - Equivalent to ``IsAssignable[T, Literal[True]] and not IsAssignable[T, Never]``. - - This is useful for invoking "helper aliases" that return a boolean - literal type. - Basic operators ''''''''''''''' @@ -985,7 +963,7 @@ as a literal type--all of these mechanisms lean very heavily on literal types. c.name, ConvertField[typing.GetMemberType[ModelT, c.name]], ] - for c in typing.Iter[typing.Attrs[K]] + for c in typing.Attrs[K] ] ] ]: @@ -1038,7 +1016,7 @@ contains all the ``Property`` attributes of ``T``. type PropsOnly[T] = typing.NewProtocol[ *[ typing.Member[p.name, PointerArg[p.type]] - for p in typing.Iter[typing.Attrs[T]] + for p in typing.Attrs[T] if typing.IsAssignable[p.type, Property] ] ] @@ -1072,7 +1050,7 @@ suite, but here is a possible implementation of just ``Create``:: p.quals, GetDefault[p.init], ] - for p in typing.Iter[typing.Attrs[T]] + for p in typing.Attrs[T] if not typing.IsAssignable[ Literal[True], GetFieldItem[p.init, Literal["primary_key"]], @@ -1120,7 +1098,7 @@ based on iterating over all attributes. # directly as D. GetDefault[p.init], ] - for p in typing.Iter[typing.Attrs[T]] + for p in typing.Attrs[T] ], ], None, @@ -1129,7 +1107,7 @@ based on iterating over all attributes. ] type AddInit[T] = typing.NewProtocol[ InitFnType[T], - *[x for x in typing.Iter[typing.Members[T]]], + *[x for x in typing.Members[T]], ] ``UpdateClass`` can then be used to create a class decorator (a la @@ -1172,7 +1150,7 @@ Using type iteration and ``GetArg``, we can give a proper type to ``zip``. def zip[*Ts]( *args: *Ts, strict: bool = False - ) -> Iterator[tuple[*[ElemOf[t] for t in typing.Iter[tuple[*Ts]]]]]: + ) -> Iterator[tuple[*[ElemOf[t] for t in tuple[*Ts]]]]: return builtins.zip(*args, strict=strict) # type: ignore[call-overload] Using the ``Slice`` operator and type alias recursion, we can @@ -1206,9 +1184,9 @@ raise an error. type Zip[T, S] = ( tuple[()] - if typing.Bool[Empty[T]] and typing.Bool[Empty[S]] + if Empty[T] and Empty[S] else typing.RaiseError[Literal["Zip length mismatch"], T, S] - if typing.Bool[Empty[T]] or typing.Bool[Empty[S]] + if Empty[T] or Empty[S] else tuple[*Zip[DropLast[T], DropLast[S]], tuple[Last[T], Last[S]]] ) @@ -1227,36 +1205,24 @@ We present implementations of a selection of them:: # Pick # Constructs a type by picking the set of properties Keys from T. type Pick[T, Keys] = typing.NewProtocol[ - *[ - p - for p in typing.Iter[typing.Members[T]] - if typing.IsAssignable[p.name, Keys] - ] + *[p for p in typing.Members[T] if typing.IsAssignable[p.name, Keys]] ] # Omit # Constructs a type by picking all properties from T and then removing Keys. # Note that unlike in TS, our Omit does not depend on Exclude. type Omit[T, Keys] = typing.NewProtocol[ - *[ - p - for p in typing.Iter[typing.Members[T]] - if not typing.IsAssignable[p.name, Keys] - ] + *[p for p in typing.Members[T] if not typing.IsAssignable[p.name, Keys]] ] # KeyOf[T] # Constructs a union of the names of every member of T. - type KeyOf[T] = Union[*[p.name for p in typing.Iter[typing.Members[T]]]] + type KeyOf[T] = Union[*[p.name for p in typing.Members[T]]] # Exclude # Constructs a type by excluding from T all union members assignable to U. type Exclude[T, U] = Union[ - *[ - x - for x in typing.Iter[typing.FromUnion[T]] - if not typing.IsAssignable[x, U] - ] + *[x for x in typing.FromUnion[T] if not typing.IsAssignable[x, U]] ] # Extract @@ -1264,7 +1230,7 @@ We present implementations of a selection of them:: type Extract[T, U] = Union[ *[ x - for x in typing.Iter[typing.FromUnion[T]] + for x in typing.FromUnion[T] # Just the inverse of Exclude, really if typing.IsAssignable[x, U] ] @@ -1273,10 +1239,7 @@ We present implementations of a selection of them:: # Partial # Constructs a type with all properties of T set to optional (T | None). type Partial[T] = typing.NewProtocol[ - *[ - typing.Member[p.name, p.type | None, p.quals] - for p in typing.Iter[typing.Attrs[T]] - ] + *[typing.Member[p.name, p.type | None, p.quals] for p in typing.Attrs[T]] ] # PartialTD @@ -1285,7 +1248,7 @@ We present implementations of a selection of them:: type PartialTD[T] = typing.NewTypedDict[ *[ typing.Member[p.name, p.type, p.quals | Literal["NotRequired"]] - for p in typing.Iter[typing.Attrs[T]] + for p in typing.Attrs[T] ] ] @@ -1433,7 +1396,7 @@ something like: type PropsOnly[T] = typing.NewProtocol[ { p.name: PointerArg[p.type] - for p in typing.Iter[typing.Attrs[T]] + for p in typing.Attrs[T] if typing.IsAssignable[p.type, Property] } ] @@ -1469,7 +1432,7 @@ First, the syntax would be something like:: type PropsOnly[T] = typing.NewProtocol[ { k: PointerArg[ty] - for k, ty in typing.IterItems[typing.Attrs[T]] + for k, ty in typing.Attrs[T] if typing.IsAssignable[ty, Property] } ] @@ -1643,7 +1606,7 @@ look like:: type PropsOnly[T] = typing.NewProtocol[ *[ typing.Member[typing.GetName[p], PointerArg[typing.GetType[p]]] - for p in typing.Iter[typing.Attrs[T]] + for p in typing.Attrs[T] if typing.IsAssignable[typing.GetType[p], Property] ] ] @@ -1656,7 +1619,7 @@ Use type operators for conditional and iteration Instead of writing: * ``tt if tb else tf`` - * ``*[tres for T in Iter[ttuple]]`` + * ``*[tres for T in ttuple]`` we could use type operator forms like: * ``Cond[tb, tt, tf]`` @@ -1678,12 +1641,14 @@ The non-lambda form would be trivial to extract, but requiring the declaration of a ``TypeVar`` goes against the grain of recent changes.) -Another advantage is not needing any notion of a special -```` class of types. - The disadvantage is that the syntax seems a *lot* worse. Supporting filtering while mapping would make it even more bad -(maybe an extra argument for a filter?) +(requiring an extra argument for the filter?) + +The implementation actually *does* have these operators internally, as +``_Cond`` and ``_UnpackMap`` (along with others), to represent the +annotation after it has been translated from the AST. We could +consider exposing them for backporting purposes, if we wanted. We can explore other options too if needed. From b32c73d9aca1c0af211d94c17cd77837fcfab3e2 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 14 Oct 2026 16:22:27 -0700 Subject: [PATCH 4/7] Update rejected ideeas --- peps/pep-0827.rst | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index d0de2eb93b5..bdee21ce3eb 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -900,11 +900,6 @@ objects. It will be extended to understand the new syntax introduces in this PEP and produce appropriate ``typing`` objects to represent them. -There has been some discussion of adding a ``Format.AST`` mode for -fetching annotations (see this `PEP draft <#ast_format_>`_). That -would combine extremely well with this proposal, as it would make it -easy to still fetch fully unevaluated annotations. - Examples / Tutorial =================== @@ -1526,6 +1521,22 @@ For better or worse, though, runtime use of type annotations is widespread, e.g. ``pydantic`` depends on it, and one of our motivating examples (automatically deriving FastAPI CRUD models) depends on it too. +Implement runtime evaluation by calling ``__annotate__`` in a special environment +--------------------------------------------------------------------------------- + +The initial draft of this PEP anticipated evaluating annotations by +calling ``__annotate__`` with type variables substituted in the +closure and arranging for the right things to happen. + +The hope here is that it would avoid the need to add a new way of +retrieving the annotations (``Format.AST``, here). + +It didn't really accomplish that goal, because other tools still will +want to get unevaluated annotations, certain type iteration behaviors +couldn't really be implemented properly with it, and it imposed severe +limitations on syntax and semantics. + + Support TypeScript style pattern matching in subtype checking ------------------------------------------------------------- @@ -1551,12 +1562,7 @@ Perhaps the most plausible variant would be something like:: type ArrayArg[T] = El if IsAssignable[T, list[Infer[El]]] else Never -Then, if we wanted to evaluate it at runtime, we'd need to do -something gnarly involving a custom ``globals`` environment that -catches the unbound ``Infer`` arguments. - -Additionally, without major syntactic changes (using type operators -instead of ternary), we wouldn't be able to match TypeScript's +It would also be potentially difficult to to match TypeScript's behavior of lifting the conditional over unions. From bd20a7cadfac93375a65e95fffe84038383f9f47 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 14 Oct 2026 17:29:22 -0700 Subject: [PATCH 5/7] More stray stuff --- peps/pep-0827.rst | 50 +++++++++++------------------------------------ 1 file changed, 11 insertions(+), 39 deletions(-) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index bdee21ce3eb..35f09fbd48f 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -494,7 +494,7 @@ to the grammar of what Python expressions are considered as valid types. | # Conditional types - | if else + | if else # Types with variadic arguments can have # *[... for t in ...] arguments @@ -513,7 +513,7 @@ to the grammar of what Python expressions are considered as valid types. = , - | * [ ] , + | * [ ] , = + * @@ -537,11 +537,6 @@ in the same way as the :ref:`unpacked comprehensions `. When evaluated in type annotation context, they will be equivalent to ``Literal[True]`` or ``Literal[False]``. -We restrict what operators may be used in a conditional -so that at runtime, we can have those operators produce "type" values -with appropriate behavior, without needing to change the behavior of -existing ``Literal[False]`` values and the like. - Conditional types ''''''''''''''''' @@ -571,8 +566,7 @@ members and function params have "associated" type members, which can be accessed by dot notation: ``m.name``, ``m.type``, etc. This operation is not lifted over union types. Using it on the wrong -sort of type will be an error. It must be that way at runtime, -and we want typechecking to match. +sort of type will be an error. Type operators @@ -897,7 +891,7 @@ representation of arbitrary annotations. :pep:`849` introduces a new helper function ``typing.evaluate_type_ast`` that converts an AST into typing objects. It will be extended to understand the new syntax -introduces in this PEP and produce appropriate ``typing`` objects +introduced in this PEP and produce appropriate ``typing`` objects to represent them. Examples / Tutorial @@ -1464,7 +1458,7 @@ As an example, here is how mixing ``()`` and ``[]`` could look like:: type PropsOnly[T] = typing.NewProtocol( { p.name: PointerArg[p.type] - for p in typing.Iter(typing.Attrs(T)) + for p in typing.Attrs(T) if typing.IsAssignable(p.type, Property) } ) @@ -1481,7 +1475,6 @@ and ``Param`` will have associated types for ``.name`` and ``.type``. We could just make it work for those particular types, or we could introduce a general mechanism that might look something like:: - @typing.has_associated_types class Member[ N: str, T, @@ -1496,30 +1489,15 @@ introduce a general mechanism that might look something like:: type definer = D -The decorator (or a base class) is needed if we want the dot notation -for the associated types to be able to work at runtime, since we need -to customize the behavior of ``__getattr__`` on the -``typing._GenericAlias`` produced by the class so that it captures -both the type parameters to ``Member`` and the alias. - -(Though possibly we could change the behavior of ``_GenericAlias`` -itself to avoid the need for that.) - Rejected Ideas ============== Renounce all cares of runtime evaluation ---------------------------------------- -This would give us more flexibility to experiment with syntactic -forms, and would allow us to dispense with some ugliness such as -requiring ``typing.Iter`` in unpacked comprehension types and having a -limited set of ```` expressions that can appear in -conditional types. - -For better or worse, though, runtime use of type annotations is -widespread, e.g. ``pydantic`` depends on it, and one of our motivating -examples (automatically deriving FastAPI CRUD models) depends on it too. +For better or worse, runtime use of type annotations is widespread, +e.g. ``pydantic`` depends on it, and one of our motivating examples +(automatically deriving FastAPI CRUD models) depends on it too. Implement runtime evaluation by calling ``__annotate__`` in a special environment --------------------------------------------------------------------------------- @@ -1562,7 +1540,7 @@ Perhaps the most plausible variant would be something like:: type ArrayArg[T] = El if IsAssignable[T, list[Infer[El]]] else Never -It would also be potentially difficult to to match TypeScript's +It would also be potentially difficult to match TypeScript's behavior of lifting the conditional over unions. @@ -1638,14 +1616,8 @@ etc). The advantage of this is that constructing a type annotation never needs to do non-trivial computation (assuming we also get rid of dot -notation), and thus we don't need :ref:`runtime hooks ` to -support evaluating them. - -It would also mean that it would be much easier to extract the raw -type annotation. (The lambda form would still be somewhat fiddly. -The non-lambda form would be trivial to extract, but requiring the -declaration of a ``TypeVar`` goes against the grain of recent -changes.) +notation), and thus we could avoid needing to depend on :pep:`849` to +get the unevaluated AST. The disadvantage is that the syntax seems a *lot* worse. Supporting filtering while mapping would make it even more bad From 20dc69aff99d6f8553f2a48d8ec9adf3052772b8 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 14 Oct 2026 17:33:59 -0700 Subject: [PATCH 6/7] add a note about Any --- peps/pep-0827.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index 35f09fbd48f..47a80c8cc31 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -1828,6 +1828,18 @@ Open Issues that the operators match and that every operand matches in both arguments invariantly. +* What exactly should the result of conditionals and unpacked + comprehensions be when operating on ``Any``? + + Currently, conditionals are defined to take the true branch. + + Unpacked comprehensions are currently not specified. The most + straightforward answer is for whatever the comprehension is being + unpacked into to *also* become ``Any``, so that something like + ``tuple[*[T | None for T in Any]]`` would be ``Any``. + Arguably, though, we would like ``tuple[Any | None, ...]`` as the result, + which would require some more careful thought to specific. + Acknowledgements ================ From 01ab58a7070abb48c3702654697ca0a74366732f Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 14 Oct 2026 23:32:03 -0700 Subject: [PATCH 7/7] Update peps/pep-0827.rst Co-authored-by: Jelle Zijlstra --- peps/pep-0827.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index 47a80c8cc31..eb903187f97 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -1838,7 +1838,7 @@ Open Issues unpacked into to *also* become ``Any``, so that something like ``tuple[*[T | None for T in Any]]`` would be ``Any``. Arguably, though, we would like ``tuple[Any | None, ...]`` as the result, - which would require some more careful thought to specific. + which would require some more careful thought to specify. Acknowledgements