Skip to content

gh-140596: Substitute earlier type parameters into PEP 696 defaults - #155646

Open
KotlinIsland wants to merge 1 commit into
python:mainfrom
KotlinIsland:140596-default-typevar
Open

gh-140596: Substitute earlier type parameters into PEP 696 defaults#155646
KotlinIsland wants to merge 1 commit into
python:mainfrom
KotlinIsland:140596-default-typevar

Conversation

@KotlinIsland

@KotlinIsland KotlinIsland commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

A type parameter default may refer to type parameters that appear earlier in
the same type parameter list, but those references were never substituted:

class Bar[T, S = T]: ...

Bar[int].__args__  # (int, ~T)  -- should be (int, int)

Because the unsubstituted T stayed in the specialization, it leaked into
__parameters__, so omitting a defaulted type parameter when subclassing
failed:

class Baz[U](Bar[U]): ...
TypeError: Some type variables (T) are not listed in Generic[U]

PEP 696 and the typing spec both allow a default to refer to a prior type
parameter and require non-overridden defaults to be substituted in.

Fix

The substitution is done in __typing_prepare_subst__, at the point where the
default is filled in. That is the natural place for it: a type parameter only
falls back to its default once every preceding type parameter has a value, so
args already holds exactly the bindings the default needs.

Doing it there means one shared helper (typing._resolve_type_param_default)
covers TypeVar, ParamSpec and TypeVarTuple defaults, and covers both
_generic_class_getitem and _GenericAlias._determine_new_args — the alias
path had the same bug:

T = TypeVar('T')
U = TypeVar('U', default=T)
Union[T, U][int]  # was `int | ~T`, now `int`

TypeVar.__typing_prepare_subst__ is implemented in C, so it calls the helper
through call_typing_func_object, the same way the neighbouring
_typevar_subst, _paramspec_prepare_subst and _typevartuple_prepare_subst
hooks already do. The call only happens when a default is actually used.

Type parameters that come from an enclosing scope (they are not in the
subscripted object's __parameters__) are left untouched, preserving the
existing behaviour for free type variables.

Behaviour change

A default that refers to a type parameter which is not declared before it now
raises TypeError when the default is used, rather than silently producing a
nonsensical specialization. This covers self-references and cycles, both of
which the typing spec disallows:

class A[T = T]: ...
class B[T = S, S = T]: ...

A[()]
TypeError: The default of type parameter T refers to type parameter T, which is not declared before it

…ults

The default of a type parameter may refer to type parameters that appear
earlier in the same type parameter list. Those references are now replaced
by the values supplied for the earlier type parameters when the default is
filled in, so ``class Bar[T, S = T]`` gives ``Bar[int, int]`` for
``Bar[int]`` instead of leaving ``S`` bound to the unsubstituted ``T``.
Leaving it unsubstituted leaked ``T`` into the specialization, which made
``class Baz[U](Bar[U])`` fail with "Some type variables (T) are not listed
in Generic[U]".

The substitution happens in ``__typing_prepare_subst__``, where the values
of all earlier type parameters are already known, so it applies to
``TypeVar``, ``ParamSpec`` and ``TypeVarTuple`` defaults and to both generic
classes and generic aliases.

A default that refers to a type parameter which is not declared before it,
whether a self-reference (``class A[T = T]``) or a cycle
(``class A[T = S, S = T]``), now raises TypeError when the default is used.
@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #34041211 | 📁 Comparing be6a3c3 against main (bc31217)

  🔍 Preview build  

2 files changed
± reference/compound_stmts.html
± whatsnew/changelog.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant