From 1882ed78aed75da6882145bb6c4747688b72eec0 Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Wed, 3 Jul 2024 12:37:14 -0500 Subject: [PATCH] Have namedtuple `__replace__` return `Self` (#17475) --- mypy/semanal_namedtuple.py | 4 +++- test-data/unit/check-namedtuple.test | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 768dd265b338..bf526a1ee990 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -57,6 +57,7 @@ TYPED_NAMEDTUPLE_NAMES, AnyType, CallableType, + Instance, LiteralType, TupleType, Type, @@ -631,9 +632,10 @@ def add_method( args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED_OPT) for var in vars], ) if self.options.python_version >= (3, 13): + type_vars = [tv for tv in info.defn.type_vars] add_method( "__replace__", - ret=None, + ret=Instance(info, type_vars), args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED_OPT) for var in vars], ) diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index e9d156754d9c..f10217b9aa5f 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -1407,9 +1407,23 @@ from typing import NamedTuple class A(NamedTuple): x: int -A(x=0).__replace__(x=1) +replaced = A(x=0).__replace__(x=1) +reveal_type(replaced) # N: Revealed type is "__main__.A" + A(x=0).__replace__(x="asdf") # E: Argument "x" to "__replace__" of "A" has incompatible type "str"; expected "int" A(x=0).__replace__(y=1) # E: Unexpected keyword argument "y" for "__replace__" of "A" + +from typing import TypeVar, Generic + +T = TypeVar("T") + +class GenericA(NamedTuple, Generic[T]): + x: T + +replaced_2 = GenericA(x=0).__replace__(x=1) +reveal_type(replaced_2) # N: Revealed type is "__main__.GenericA" +GenericA(x=0).__replace__(x="abc") # E: Argument "x" to "__replace__" of "GenericA" has incompatible type "str"; expected "int" + [builtins fixtures/tuple.pyi] [typing fixtures/typing-namedtuple.pyi]