From d2dc190897aa70e72ab66f953ae0c62e59cafb9f Mon Sep 17 00:00:00 2001 From: Maxwell Muoto <41130755+max-muoto@users.noreply.github.com> Date: Wed, 3 Jul 2024 10:14:06 -0500 Subject: [PATCH 1/4] Have nametuple `__replace__` return self --- mypy/semanal_namedtuple.py | 3 ++- test-data/unit/check-namedtuple.test | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 768dd265b338..16d0f6174de9 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -57,6 +57,7 @@ TYPED_NAMEDTUPLE_NAMES, AnyType, CallableType, + Instance, LiteralType, TupleType, Type, @@ -633,7 +634,7 @@ def add_method( if self.options.python_version >= (3, 13): add_method( "__replace__", - ret=None, + ret=Instance(info, []), 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..25feca35b8d5 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -1407,9 +1407,12 @@ 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 "Tuple[builtins.int, fallback=__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" + [builtins fixtures/tuple.pyi] [typing fixtures/typing-namedtuple.pyi] From 90d53a720ee682d383041a6f60259e55bf20dd6f Mon Sep 17 00:00:00 2001 From: Maxwell Muoto <41130755+max-muoto@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:42:51 -0500 Subject: [PATCH 2/4] Test generics --- mypy/semanal_namedtuple.py | 3 ++- test-data/unit/check-namedtuple.test | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 16d0f6174de9..bf526a1ee990 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -632,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=Instance(info, []), + 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 25feca35b8d5..fb3c70fe2e1d 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -1408,11 +1408,22 @@ class A(NamedTuple): x: int replaced = A(x=0).__replace__(x=1) -reveal_type(replaced) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.A]" +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[builtins.int]" +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] From 4f8045661dc5a8368bbed59aa28dfe7a233f04d5 Mon Sep 17 00:00:00 2001 From: Maxwell Muoto <41130755+max-muoto@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:47:40 -0500 Subject: [PATCH 3/4] Fix --- test-data/unit/check-namedtuple.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index fb3c70fe2e1d..55fe55516ace 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -1408,7 +1408,7 @@ class A(NamedTuple): x: int replaced = A(x=0).__replace__(x=1) -reveal_type(replaced) # N: Revealed type is "__main__.A" +reveal_type(replaced) # N: Revealed type is "Tuple[builtins.int, fallback=__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 a2a02de19a7660a75f5797b647124718592acd46 Mon Sep 17 00:00:00 2001 From: Maxwell Muoto <41130755+max-muoto@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:06:35 -0500 Subject: [PATCH 4/4] Tweak --- test-data/unit/check-namedtuple.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 55fe55516ace..f10217b9aa5f 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -1408,7 +1408,7 @@ class A(NamedTuple): x: int replaced = A(x=0).__replace__(x=1) -reveal_type(replaced) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.A]" +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" @@ -1421,7 +1421,7 @@ 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[builtins.int]" +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]