From 879888967e3dd9102bfb81bcc85512d62031b129 Mon Sep 17 00:00:00 2001 From: Weijian He <230282609+Madin-H23@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:48:58 +0800 Subject: [PATCH] Reject Self in type parameter bounds PEP 673 only allows Self in annotations within a class definition. Type variable bounds were not checked, so an in-class legacy TypeVar(bound=Self) or a PEP 695 method-level [T: Self] was silently accepted. Fixes #21960 --- mypy/semanal.py | 18 ++++++++++++++++-- test-data/unit/check-selftype.test | 12 ++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 5ef5223c23462..0c5226aba461a 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="a type variable 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 @@ -5067,7 +5071,13 @@ def process_typevar_parameters( if has_values: self.fail("TypeVar cannot have both values and an upper bound", context) return None - tv_arg = self.get_typevarlike_argument("TypeVar", param_name, param_value, context) + tv_arg = self.get_typevarlike_argument( + "TypeVar", + param_name, + param_value, + context, + prohibit_self_type="a type variable bound", + ) if tv_arg is None: return None upper_bound = tv_arg @@ -5119,6 +5129,7 @@ def get_typevarlike_argument( allow_param_spec_literals: bool = False, allow_unpack: bool = False, report_invalid_typevar_arg: bool = True, + prohibit_self_type: str | None = None, ) -> Type | None: try: # We want to use our custom error message below, so we suppress @@ -5131,6 +5142,7 @@ def get_typevarlike_argument( allow_param_spec_literals=allow_param_spec_literals, allow_unpack=allow_unpack, analyzing_tvar_def=param_name == "default", + prohibit_self_type=prohibit_self_type, ) if analyzed is None: # Type variables are special: we need to place them in the symbol table @@ -7824,6 +7836,7 @@ def expr_to_analyzed_type( allow_unpack: bool = False, unique_name: str | None = None, analyzing_tvar_def: bool = False, + prohibit_self_type: str | None = None, ) -> Type | None: if unique_name is not None and isinstance(expr, CallExpr): # This is a legacy syntax intended mostly for Python 2, we keep it for @@ -7856,6 +7869,7 @@ def expr_to_analyzed_type( allow_param_spec_literals=allow_param_spec_literals, allow_unpack=allow_unpack, analyzing_tvar_def=analyzing_tvar_def, + prohibit_self_type=prohibit_self_type, ) def analyze_type_expr(self, expr: Expression) -> None: diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 6f73df5e05c8e..51c6e37586872 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -1494,6 +1494,18 @@ def func() -> None: class C(Self): ... # E: Self type is only allowed in annotations within class definition +[case testTypingSelfInvalidAsTypeParameterBound] +# flags: --python-version 3.12 +from typing import Self, TypeVar + +class C: + def f[T: Self](self, x: T) -> T: ... # E: Self type cannot be used in a type variable bound + +class D: + T = TypeVar("T", bound=Self) # E: Self type cannot be used in a type variable bound # E: TypeVar "bound" must be a type + +class E[T: Self]: ... # E: Self type cannot be used in a type variable bound + [case testTypingSelfInvalidArgs] from typing import Self, List