From 5c90d1e88e3c714f4ac45c1296079927f1875377 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 5 Nov 2022 15:27:59 -0700 Subject: [PATCH 1/6] Fix incompatible overrides of overloaded methods in concrete subclasses Fixes #14002 --- mypy/checker.py | 13 ++++++++ test-data/unit/check-selftype.test | 51 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 8973ade98228..137ae2d69423 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1868,6 +1868,19 @@ def check_method_override_for_base_with_name( original_class_or_static = False # a variable can't be class or static if isinstance(original_type, FunctionLike): + active_self_type = self.scope.active_self_type() + if isinstance(original_type, Overloaded) and active_self_type: + # If we have an overload, filter to overloads that match the self type. + # This avoids false positives for concrete subclasses of generic classes, + # see testSelfTypeOverrideCompatibility for an example. + # It's possible we might want to do this as part of bind_and_map_method + filtered_items = [ + item + for item in original_type.items + if not item.arg_types or is_subtype(active_self_type, item.arg_types[0]) + ] + if filtered_items: + original_type = Overloaded(filtered_items) original_type = self.bind_and_map_method(base_attr, original_type, defn.info, base) if original_node and is_property(original_node): original_type = get_property_type(original_type) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 506e8bfe8ab1..5adf30150352 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -128,6 +128,57 @@ reveal_type(cast(A, C()).copy()) # N: Revealed type is "__main__.A" [builtins fixtures/bool.pyi] +[case testSelfTypeOverrideCompatibility] +from typing import overload, TypeVar, Generic + +T = TypeVar("T", str, int) + +class A(Generic[T]): + @overload + def f(self: A[int]) -> int: ... + @overload + def f(self: A[str]) -> str: ... + def f(self): ... + +class B(A[T]): + @overload + def f(self: A[int]) -> int: ... + @overload + def f(self: A[str]) -> str: ... + def f(self): ... + +class C(A[int]): + def f(self) -> int: ... + +class D(A[str]): + def f(self) -> int: ... # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: @overload \ + # N: def f(self) -> str \ + # N: Subclass: \ + # N: def f(self) -> int + +class E(A[T]): + def f(self) -> int: ... # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: @overload \ + # N: def f(self) -> int \ + # N: @overload \ + # N: def f(self) -> str \ + # N: Subclass: \ + # N: def f(self) -> int + + +class F(A[bytes]): # E: Value of type variable "T" of "A" cannot be "bytes" + def f(self) -> bytes: ... # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: @overload \ + # N: def f(self) -> int \ + # N: @overload \ + # N: def f(self) -> str \ + # N: Subclass: \ + # N: def f(self) -> bytes + [case testSelfTypeSuper] from typing import TypeVar, cast From d62b1c74d034b79c44ccea7e075213756b490760 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 5 Nov 2022 16:07:17 -0700 Subject: [PATCH 2/6] more tests --- test-data/unit/check-selftype.test | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 5adf30150352..08993de5a4e5 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -131,7 +131,7 @@ reveal_type(cast(A, C()).copy()) # N: Revealed type is "__main__.A" [case testSelfTypeOverrideCompatibility] from typing import overload, TypeVar, Generic -T = TypeVar("T", str, int) +T = TypeVar("T") class A(Generic[T]): @overload @@ -169,7 +169,7 @@ class E(A[T]): # N: def f(self) -> int -class F(A[bytes]): # E: Value of type variable "T" of "A" cannot be "bytes" +class F(A[bytes]): def f(self) -> bytes: ... # E: Signature of "f" incompatible with supertype "A" \ # N: Superclass: \ # N: @overload \ @@ -179,6 +179,25 @@ class F(A[bytes]): # E: Value of type variable "T" of "A" cannot be "bytes" # N: Subclass: \ # N: def f(self) -> bytes +class G(A): + def f(self): ... + +class H(A[int]): + def f(self): ... + +class I(A[int]): + def f(*args): ... + +class J(A[int]): + def f(self, arg) -> int: ... # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: @overload \ + # N: def f(self) -> int \ + # N: Subclass: \ + # N: def f(self, arg: Any) -> int + +[builtins fixtures/tuple.pyi] + [case testSelfTypeSuper] from typing import TypeVar, cast From 8e575a5a3bc3918c02b100effdbca1d3403876a0 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 5 Nov 2022 16:24:42 -0700 Subject: [PATCH 3/6] failing self test --- test-data/unit/check-selftype.test | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 08993de5a4e5..e47c3fcec822 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -147,6 +147,15 @@ class B(A[T]): def f(self: A[str]) -> str: ... def f(self): ... +class B2(A[T]): + @overload + def f(self: A[int]) -> int: ... + @overload + def f(self: A[str]) -> str: ... + @overload + def f(self: A[bytes]) -> bytes: ... + def f(self): ... + class C(A[int]): def f(self) -> int: ... From 992e252b65e5b09877464f9ed5b242392e3d6240 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 5 Nov 2022 19:00:49 -0700 Subject: [PATCH 4/6] add comment --- mypy/checker.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 137ae2d69423..b5aff1051645 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1879,6 +1879,10 @@ def check_method_override_for_base_with_name( for item in original_type.items if not item.arg_types or is_subtype(active_self_type, item.arg_types[0]) ] + # If we don't have any filtered_items, maybe it's always a valid override + # of the superclass? However if you get to that point you're in murky type + # territory anyway, so we just preserve the type and have the behaviour match + # that of older versions of mypy. if filtered_items: original_type = Overloaded(filtered_items) original_type = self.bind_and_map_method(base_attr, original_type, defn.info, base) From 7606d50ec9cdfbb93961a41e43643554b356cc4d Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 5 Nov 2022 19:02:07 -0700 Subject: [PATCH 5/6] more comment --- test-data/unit/check-selftype.test | 1 + 1 file changed, 1 insertion(+) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index e47c3fcec822..d64a0f103b83 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -179,6 +179,7 @@ class E(A[T]): class F(A[bytes]): + # Note there's an argument to be made that this is actually compatible with the supertype def f(self) -> bytes: ... # E: Signature of "f" incompatible with supertype "A" \ # N: Superclass: \ # N: @overload \ From 635e5753a2516ef56142da384c665fae97c6dc9b Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 5 Nov 2022 20:13:08 -0700 Subject: [PATCH 6/6] add xfail test --- test-data/unit/check-selftype.test | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index d64a0f103b83..3d801d23a642 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -208,6 +208,40 @@ class J(A[int]): [builtins fixtures/tuple.pyi] +[case testSelfTypeOverrideCompatibilityTypeVar-xfail] +from typing import overload, TypeVar, Union + +AT = TypeVar("AT", bound="A") + +class A: + @overload + def f(self: AT, x: int) -> AT: ... + @overload + def f(self, x: str) -> None: ... + @overload + def f(self: AT) -> bytes: ... + def f(*a, **kw): ... + +class B(A): + @overload # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: @overload \ + # N: def f(self, x: int) -> B \ + # N: @overload \ + # N: def f(self, x: str) -> None \ + # N: @overload \ + # N: def f(self) -> bytes \ + # N: Subclass: \ + # N: @overload \ + # N: def f(self, x: int) -> B \ + # N: @overload \ + # N: def f(self, x: str) -> None + def f(self, x: int) -> B: ... + @overload + def f(self, x: str) -> None: ... + def f(*a, **kw): ... +[builtins fixtures/dict.pyi] + [case testSelfTypeSuper] from typing import TypeVar, cast