Bug Report
PEP 695 explicitly allows a type parameter's bound to reference an earlier type parameter declared in the same type parameter list ("Type Parameter Scopes" section: "Type parameters are visible to other type parameters declared elsewhere in the list", with the only restriction being that an earlier parameter cannot reference a later one). Mypy rejects this with a name-defined error, both for classes and for functions, and for both the new PEP 695 bracket syntax and the classic TypeVar syntax.
To Reproduce
Minimal reproduction (class):
class Container[T]:
pass
class Foo[T, S: Container[T]]:
x: S
Minimal reproduction (function, same failure):
class Container[T]:
pass
def foo[T, S: Container[T]](x: S) -> None:
pass
Both are plain generic classes, not Protocol — the failure is not specific to protocols; the exact same error occurs when Container is a Protocol.
Expected Behavior
Both snippets should type-check cleanly. T is declared before S in the same type parameter list, and S's bound (Container[T]) only refers back to an earlier parameter, which PEP 695 explicitly permits.
For context, a bound with no parameters of its own works exactly as expected:
from typing import Sized
def get_length[T: Sized](item: T) -> int:
return len(item)
get_length("ok") # accepted
get_length(42) # error: Value of type variable "T" of "get_length" cannot be "int"
The failure only appears once the bound itself needs a type parameter (Container[T]), and that parameter must come from a sibling in the same list.
Actual Behavior
error: Name "T" is not defined [name-defined]
Your Environment
Mypy version: 2.1.0 (reproduced) and 2.3.1, the latest released version as of this report (also reproduced, installed in isolation)
Python version used: 3.13.12
Operating system: Linux
Additional context
Closely related to #20190 / #20021 (a PEP 696 default value referencing a sibling, fixed by #20021) — but that fix doesn't cover this: re-tested on 2.3.1 (which already includes it), still fails identically. #20190 is about a default value (Y=X), this one is about a bound (Y: Bound[X]) — likely the same underlying scope-resolution issue, but seemingly not fixed by the same change.
Additional information:
I asked claude to help me in the redaction of this ticket, but this is a real problem i meet in the real world, and i am very surprised this bug has not been repported yet.
Bug Report
PEP 695 explicitly allows a type parameter's bound to reference an earlier type parameter declared in the same type parameter list ("Type Parameter Scopes" section: "Type parameters are visible to other type parameters declared elsewhere in the list", with the only restriction being that an earlier parameter cannot reference a later one). Mypy rejects this with a name-defined error, both for classes and for functions, and for both the new PEP 695 bracket syntax and the classic TypeVar syntax.
To Reproduce
Minimal reproduction (class):
class Container[T]:
pass
class Foo[T, S: Container[T]]:
x: S
Minimal reproduction (function, same failure):
class Container[T]:
pass
def foo[T, S: Container[T]](x: S) -> None:
pass
Both are plain generic classes, not Protocol — the failure is not specific to protocols; the exact same error occurs when Container is a Protocol.
Expected Behavior
Both snippets should type-check cleanly. T is declared before S in the same type parameter list, and S's bound (Container[T]) only refers back to an earlier parameter, which PEP 695 explicitly permits.
For context, a bound with no parameters of its own works exactly as expected:
from typing import Sized
def get_length[T: Sized](item: T) -> int:
return len(item)
get_length("ok") # accepted
get_length(42) # error: Value of type variable "T" of "get_length" cannot be "int"
The failure only appears once the bound itself needs a type parameter (Container[T]), and that parameter must come from a sibling in the same list.
Actual Behavior
error: Name "T" is not defined [name-defined]
Your Environment
Mypy version: 2.1.0 (reproduced) and 2.3.1, the latest released version as of this report (also reproduced, installed in isolation)
Python version used: 3.13.12
Operating system: Linux
Additional context
Closely related to #20190 / #20021 (a PEP 696 default value referencing a sibling, fixed by #20021) — but that fix doesn't cover this: re-tested on 2.3.1 (which already includes it), still fails identically. #20190 is about a default value (Y=X), this one is about a bound (Y: Bound[X]) — likely the same underlying scope-resolution issue, but seemingly not fixed by the same change.
Additional information:
I asked claude to help me in the redaction of this ticket, but this is a real problem i meet in the real world, and i am very surprised this bug has not been repported yet.