From a52967dc0ac724c54564285545d2905dca725c3d Mon Sep 17 00:00:00 2001 From: Dextheking1 Date: Thu, 24 Sep 2026 12:25:57 +0200 Subject: [PATCH] Reject Self in PEP 695 type parameter bounds, constraints and defaults Fixes python/mypy#21960. Self is only valid in the locations listed in PEP 673; a type parameter bound, constraint or default is not one of them. Previously mypy silently accepted e.g. `def f[T: Self](self: T)` when it appeared inside a class body, because the Self check only looked at whether analysis was inside a class definition. Pass prohibit_self_type when analyzing PEP 695 upper bounds, value constraints and defaults so these are rejected with a clear error, matching pyright/ty/zuban. --- mypy/semanal.py | 13 +++++++++++-- test-data/unit/check-selftype.test | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 5ef5223c23462..4a45032528e97 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1902,7 +1902,11 @@ def analyze_type_param( ) -> TypeVarLikeExpr | None: fullname = self.qualified_name(type_param.name) if type_param.upper_bound: - upper_bound = self.anal_type(type_param.upper_bound, allow_placeholder=True) + upper_bound = self.anal_type( + type_param.upper_bound, + allow_placeholder=True, + prohibit_self_type="type parameter bound", + ) # TODO: we should validate the upper bound is valid for a given kind. if upper_bound is None: # This and below copies special-casing for old-style type variables, that @@ -1926,6 +1930,7 @@ def analyze_type_param( allow_tuple_literal=type_param.kind == PARAM_SPEC_KIND, allow_unpack=type_param.kind == TYPE_VAR_TUPLE_KIND, analyzing_tvar_def=True, + prohibit_self_type="type parameter default", ) if default is None: default = PlaceholderType(None, [], context.line) @@ -1943,7 +1948,11 @@ def analyze_type_param( values: list[Type] = [] if type_param.values: for value in type_param.values: - analyzed = self.anal_type(value, allow_placeholder=True) + analyzed = self.anal_type( + value, + allow_placeholder=True, + prohibit_self_type="type parameter constraint", + ) if analyzed is None: analyzed = PlaceholderType(None, [], context.line) if has_type_vars(analyzed): diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 6f73df5e05c8e..947a7181078cc 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -1494,6 +1494,23 @@ def func() -> None: class C(Self): ... # E: Self type is only allowed in annotations within class definition +[case testTypingSelfInvalidTypeParamPositions] +# https://github.com/python/mypy/issues/21960 +# flags: --python-version 3.13 +from typing import Self + +class Foo: + def meth[T: Self](self: T) -> None: ... # E: Self type cannot be used in type parameter bound + +class Bar[T: Self]: ... # E: Self type cannot be used in type parameter bound + +def func[T: (Self, int)](x: T) -> None: ... # E: Self type cannot be used in type parameter constraint + +type Alias[T: Self] = list[T] # E: Self type cannot be used in type parameter bound + +class Ok[T]: + def meth(self: Self) -> None: ... + [case testTypingSelfInvalidArgs] from typing import Self, List