From b41157b8c300086cf876a583cc83e36ca4e63730 Mon Sep 17 00:00:00 2001 From: Bhuvansh855 Date: Wed, 27 May 2026 12:57:58 +0530 Subject: [PATCH 1/3] Fix false positive for constrained NamedTuple self types --- mypy/checker.py | 32 +++++++++++++++++++++++++++++- test-data/unit/check-selftype.test | 13 ++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7dfdcb83a90b7..6c6db223d2753 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1768,7 +1768,37 @@ def require_correct_self_argument(self, func: Type, defn: FuncDef) -> bool: # This level of erasure matches the one in checkmember.check_self_arg(), # better keep these two checks consistent. erased = get_proper_type(erase_typevars(erase_to_bound(arg_type))) - if not is_subtype(ref_type, erased, ignore_type_params=True): + + # Generic NamedTuple methods using constrained TypeVars can produce + # false positives here because constrained TypeVars are checked using + # union semantics during subtype comparison. + # + # Example: + # S = TypeVar("S", str, bytes) + # + # class Result(NamedTuple, Generic[S]): + # ... + # + # In this case: + # tuple[S] is compared against tuple[str] + # + # and incorrectly rejected. + # + # Skip this check for constrained generic NamedTuple tuple self types. + skip_namedtuple_constrained_check = ( + isinstance(ref_type, TupleType) + and isinstance(arg_type, TupleType) + and ref_type.partial_fallback.type.tuple_type is not None + and any( + isinstance(item, TypeVarType) and item.values + for item in ref_type.items + ) + ) + + if ( + not skip_namedtuple_constrained_check + and not is_subtype(ref_type, erased, ignore_type_params=True) + ): if ( isinstance(erased, Instance) and erased.type.is_protocol diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index c157200352562..a3747ae2dd134 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -2378,3 +2378,16 @@ class Bar(Enum): def bar(cls) -> Bar: ... [builtins fixtures/classmethod.pyi] + +[case testNamedTupleConstrainedTypeVarSelfType] +from typing import Generic, NamedTuple, TypeVar + +S = TypeVar("S", str, bytes) + +class Result(NamedTuple, Generic[S]): + value: S + + def get_value(self) -> S: + return self.value + +[builtins fixtures/tuple.pyi] From 2fe2202cdd7a799703b0e99a214ce31a56d406c6 Mon Sep 17 00:00:00 2001 From: Bhuvansh855 Date: Wed, 27 May 2026 14:58:09 +0530 Subject: [PATCH 2/3] Fix false positive for constrained NamedTuple self types --- mypy/checker.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6c6db223d2753..d95aaae3483a0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1785,14 +1785,25 @@ def require_correct_self_argument(self, func: Type, defn: FuncDef) -> bool: # and incorrectly rejected. # # Skip this check for constrained generic NamedTuple tuple self types. + has_constrained_typevar = False + namedtuple_ref_type: TupleType | None = None + + proper_ref_type = get_proper_type(ref_type) + proper_arg_type = get_proper_type(arg_type) + + if isinstance(proper_ref_type, TupleType): + namedtuple_ref_type = proper_ref_type + + for item in proper_ref_type.items: + if isinstance(item, TypeVarType) and item.values: + has_constrained_typevar = True + break + skip_namedtuple_constrained_check = ( - isinstance(ref_type, TupleType) - and isinstance(arg_type, TupleType) - and ref_type.partial_fallback.type.tuple_type is not None - and any( - isinstance(item, TypeVarType) and item.values - for item in ref_type.items - ) + namedtuple_ref_type is not None + and isinstance(proper_arg_type, TupleType) + and namedtuple_ref_type.partial_fallback.type.is_named_tuple + and has_constrained_typevar ) if ( From ccf41fc44deda7ed7436e37a3c51b4c06af74c3c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 09:46:15 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checker.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d95aaae3483a0..1a8ebe030fcea 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1806,9 +1806,8 @@ def require_correct_self_argument(self, func: Type, defn: FuncDef) -> bool: and has_constrained_typevar ) - if ( - not skip_namedtuple_constrained_check - and not is_subtype(ref_type, erased, ignore_type_params=True) + if not skip_namedtuple_constrained_check and not is_subtype( + ref_type, erased, ignore_type_params=True ): if ( isinstance(erased, Instance)