From 2f6b9f1803bb9e5d86267d44fa355b599b1adaf6 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 15 Apr 2026 18:33:25 -0700 Subject: [PATCH 1/2] PEP 827: Produce errors for invalid type operations Currently the PEP calls for returning `Never`. --- peps/pep-0827.rst | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index 979c73067f0..f579370f445 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -588,7 +588,7 @@ which produce aliases that have some dunder methods overloaded for Many of the operators specified have type bounds listed for some of their operands. These should be interpreted more as documentation than as exact type bounds. Trying to evaluate operators with invalid -arguments will produce ``Never`` as the return. (There is some +arguments will produce an error. (There is some discussion of potential alternatives :ref:`below `.) Note that in some of these bounds below we write things like @@ -622,10 +622,11 @@ Basic operators ''''''''''''''' * ``GetArg[T, Base, Idx: Literal[int]]``: returns the type argument - number ``Idx`` to ``T`` when interpreted as ``Base``, or ``Never`` - if it cannot be. (That is, if we have ``class A(B[C]): ...``, then + number ``Idx`` to ``T`` when interpreted as ``Base``, or generates a type + error if it cannot be or if the index is invalid. + (That is, if we have ``class A(B[C]): ...``, then ``GetArg[A, B, Literal[0]] == C`` - while ``GetArg[A, A, Literal[0]] == Never``). + while ``GetArg[A, A, Literal[0]]`` is a type error). Negative indexes work in the usual way. @@ -640,7 +641,7 @@ Basic operators ``Param`` types. * ``GetArgs[T, Base]``: returns a tuple containing all of the type - arguments of ``T`` when interpreted as ``Base``, or ``Never`` if it + arguments of ``T`` when interpreted as ``Base``, or an error if it cannot be. * ``Length[T: tuple]`` - Gets the length of a tuple as an int literal @@ -688,7 +689,7 @@ Object inspection methods). * ``GetMember[T, S: Literal[str]]``: Produces a ``Member`` type for the - member named ``S`` from the class ``T``, or ``Never`` if it does not exist. + member named ``S`` from the class ``T``, or an error if it does not exist. * ``GetMemberType[T, S: Literal[str]]``: Extract the type of the member named ``S`` from the class ``T``, or ``Never`` if it does not exist. @@ -1784,18 +1785,30 @@ This proposal is less "strictly-typed" than TypeScript TypeScript has better typechecking at the alias definition site: For ``P[K]``, ``K`` needs to have ``keyof P``. The ``extends`` -conditional type operator narrows the type to help spuport this. +conditional type operator narrows the type to help support this. + +It's not possible to define a type alias in TypeScript that fails at +expansion time, but it *is* possible to do so in this system. -We could do potentially better but it would require quite a bit more -machinery. +We could potentially also make this impossible but it would require +quite a bit more machinery. * ``KeyOf[T]`` - literal keys of ``T`` * ``Member[T]``, when statically checking a type alias, could be treated as having some type like ``tuple[Member[KeyOf[T], object, str, ..., ...], ...]`` -* ``GetMemberType[T, S: KeyOf[T]]`` - but this isn't supported yet. - TypeScript supports it. -* We would also need to do context sensitive type bound inference +* ``GetMemberType[T, S: KeyOf[T]]`` - Make ``GetMember`` have a bound + requiring the index be a key... but this kind of dependent bound + isn't supported currently. (TypeScript supports it.) +* We would also need to do context sensitive type bound + inference. This is subtle but obviously this sort of thing is done + at term level. + +We think that this isn't worth the complexity, and is also not even +obviously better. TypeScript commonly requires doing many conditionals where +often it is always intended that they take the true branch--typically +the false branch returns ``never``, and these can be quite difficult +to track down. Potential Future Extensions @@ -1886,8 +1899,6 @@ simulated in other ways. Open Issues =========== -* What invalid operations should be errors and what should return ``Never``? - * :ref:`Unpack of typevars for **kwargs `: Should whether we try to infer literal types for extra arguments be configurable in the ``TypedDict`` serving as the bound somehow? If From 36371285597149285bd052b149e386ef0033f9c8 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Thu, 16 Apr 2026 14:47:26 -0700 Subject: [PATCH 2/2] a bit more explicit discussion --- peps/pep-0827.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index f579370f445..807fe1b5104 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -588,7 +588,9 @@ which produce aliases that have some dunder methods overloaded for Many of the operators specified have type bounds listed for some of their operands. These should be interpreted more as documentation than as exact type bounds. Trying to evaluate operators with invalid -arguments will produce an error. (There is some +arguments will produce an error. When this happens, the value of the +failed operator is ``Any``, so that downstream evaluation does not +cascade further errors. (There is some discussion of potential alternatives :ref:`below `.) Note that in some of these bounds below we write things like @@ -628,6 +630,8 @@ Basic operators ``GetArg[A, B, Literal[0]] == C`` while ``GetArg[A, A, Literal[0]]`` is a type error). + If ``T`` is ``Any``, the result is ``Any``. + Negative indexes work in the usual way. (Note that runtime evaluators of type annotations are likely @@ -644,12 +648,16 @@ Basic operators arguments of ``T`` when interpreted as ``Base``, or an error if it cannot be. + If ``T`` is ``Any``, the result is ``Any``. + * ``Length[T: tuple]`` - Gets the length of a tuple as an int literal (or ``Literal[None]`` if it is unbounded) * ``Slice[S: tuple, Start: Literal[int | None], End: Literal[int | None]]``: Slices a tuple type. + If ``S`` is ``Any``, the result is ``Any``. + * ``GetSpecialAttr[T, Attr: Literal[str]]``: Extracts the value of the special attribute named ``Attr`` from the class ``T``. Valid attributes are ``__name__``, ``__module__``, and ``__qualname__``. @@ -691,9 +699,13 @@ Object inspection * ``GetMember[T, S: Literal[str]]``: Produces a ``Member`` type for the member named ``S`` from the class ``T``, or an error if it does not exist. + If ``T`` is ``Any``, the result is ``Any``. + * ``GetMemberType[T, S: Literal[str]]``: Extract the type of the member named ``S`` from the class ``T``, or ``Never`` if it does not exist. + If ``T`` is ``Any``, the result is ``Any``. + * ``Member[N: Literal[str], T, Q: MemberQuals, Init, D]``: ``Member``, is a simple type, not an operator, that is used to describe members of classes. Its type parameters encode the information about each