diff --git a/mypy/checker.py b/mypy/checker.py index 7dfdcb83a90b..1a8ebe030fce 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1768,7 +1768,47 @@ 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. + 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 = ( + 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 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 c15720035256..a3747ae2dd13 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]