Skip to content

gh-140665: Substitute PEP 696 defaults referencing other parameters#151811

Open
Builder106 wants to merge 1 commit into
python:mainfrom
Builder106:gh-140665-pep696-default-subst
Open

gh-140665: Substitute PEP 696 defaults referencing other parameters#151811
Builder106 wants to merge 1 commit into
python:mainfrom
Builder106:gh-140665-pep696-default-subst

Conversation

@Builder106

@Builder106 Builder106 commented Jun 20, 2026

Copy link
Copy Markdown

A type parameter's default may reference an earlier parameter in the same scope — class C[T, S = T], or class C[T, S = list[T]]. When a generic is parametrized and a trailing parameter is filled from its default, the default was inserted without substituting the already-bound parameters, so C[int] produced C[int, T] instead of C[int, int] (and S = list[T] stayed list[T], chained S = T, U = S stayed unbound).

The fix adds _resolve_parameter_defaults, called from both substitution paths (_generic_class_getitem and _GenericAlias._determine_new_args). Once the parameters are bound it substitutes them into any argument that was supplied by a default. Detection is gated on the argument tuple actually growing during __typing_prepare_subst__, so an explicitly-passed argument like C[int, T] is preserved as (int, T) — only genuine defaults are resolved. Nested defaults (S = list[T]list[int]) and chained defaults are handled.

Verified on a 3.16.0a0 build and against 3.14.6 runtime objects: C[int] goes from (int, T) to (int, int), and the full test_typing suite passes (738 tests) with the added test_typevar_default_referencing_other_typevar, which covers explicit-vs-default arguments, nested and chained defaults, and a concrete-default guard.

…ters

A type parameter's default may reference an earlier parameter in the same scope (e.g. class C[T, S = T], or S = list[T]), but the default was inserted unsubstituted when a trailing parameter was filled from it. Parametrizing C[int] produced C[int, T] instead of C[int, int], leaking the unbound type variable; nested defaults (S = list[T]) and chained defaults (S = T, U = S) were left unbound too.

Add _resolve_parameter_defaults, called from _generic_class_getitem and _GenericAlias._determine_new_args, which substitutes the now-bound parameters into any argument supplied by a default. Detection is gated on the argument tuple growing during __typing_prepare_subst__, so explicitly-passed arguments such as C[int, T] are left untouched.
@Builder106

Copy link
Copy Markdown
Author

The Sanitizers / TSan (free-threading) failure is unrelated to this change: the failing test is test_ssl, and the only reported data race is inside OpenSSL (ASN1_STRING_cmp in libcrypto.so.3) — a known thread-safety flake on the no-GIL sanitizer build. This PR only touches Lib/typing.py, which isn't among the tests that job runs, and test_typing passes both locally and in the other checks here. Glad to rebase to re-trigger CI if that would help.

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