From 262e6e0e56cd79871f9cf97b58953cc986f82231 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 11 Mar 2023 11:31:22 -0800 Subject: [PATCH 1/2] Fix incompatible overrides of overloaded generics with self types Fixes #14866 Basically does the potential todo I'd mentioned in #14017 --- mypy/checker.py | 41 +++++++++++++++--------------- test-data/unit/check-selftype.test | 26 +++++++++++++++++++ 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index bd762942da48..f774ae88a63d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1876,23 +1876,6 @@ 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 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) if original_node and is_property(original_node): original_type = get_property_type(original_type) @@ -1964,10 +1947,28 @@ def bind_and_map_method( is_class_method = sym.node.func.is_class else: is_class_method = sym.node.is_class - bound = bind_self(typ, self.scope.active_self_type(), is_class_method) + + mapped_typ = cast(FunctionLike, map_type_from_supertype(typ, sub_info, super_info)) + active_self_type = self.scope.active_self_type() + if isinstance(mapped_typ, 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. + filtered_items = [ + item + for item in mapped_typ.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: + mapped_typ = Overloaded(filtered_items) + + return bind_self(mapped_typ, active_self_type, is_class_method) else: - bound = typ - return cast(FunctionLike, map_type_from_supertype(bound, sub_info, super_info)) + return cast(FunctionLike, map_type_from_supertype(typ, sub_info, super_info)) def get_op_other_domain(self, tp: FunctionLike) -> Type | None: if isinstance(tp, CallableType): diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 555cef3641f8..0aeea7eb9991 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -208,6 +208,32 @@ class J(A[int]): [builtins fixtures/tuple.pyi] +[case testSelfTypeOverrideCompatibilityGeneric] +from typing import TypeVar, Generic, overload + +T = TypeVar("T", str, int) + +class A(Generic[T]): + @overload + def f(self, s: T) -> T: ... + @overload + def f(self: A[str], s: bytes) -> str: ... + def f(self, s: object): ... + +class B(A[int]): + def f(self, s: int) -> int: ... + +class C(A[str]): + def f(self, s: int) -> int: ... # E: Signature of "f" incompatible with supertype "A" \ + # N: Superclass: \ + # N: @overload \ + # N: def f(self, s: str) -> str \ + # N: @overload \ + # N: def f(self, s: bytes) -> str \ + # N: Subclass: \ + # N: def f(self, s: int) -> int +[builtins fixtures/tuple.pyi] + [case testSelfTypeOverrideCompatibilityTypeVar-xfail] from typing import overload, TypeVar, Union From 3f09d45f4fcb621ab2f93a2aa8692a9fe211973e Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 11 Mar 2023 11:41:35 -0800 Subject: [PATCH 2/2] better test --- test-data/unit/check-selftype.test | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 0aeea7eb9991..752de3741456 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -211,7 +211,7 @@ class J(A[int]): [case testSelfTypeOverrideCompatibilityGeneric] from typing import TypeVar, Generic, overload -T = TypeVar("T", str, int) +T = TypeVar("T", str, int, None) class A(Generic[T]): @overload @@ -223,13 +223,11 @@ class A(Generic[T]): class B(A[int]): def f(self, s: int) -> int: ... -class C(A[str]): +class C(A[None]): def f(self, s: int) -> int: ... # E: Signature of "f" incompatible with supertype "A" \ # N: Superclass: \ # N: @overload \ - # N: def f(self, s: str) -> str \ - # N: @overload \ - # N: def f(self, s: bytes) -> str \ + # N: def f(self, s: None) -> None \ # N: Subclass: \ # N: def f(self, s: int) -> int [builtins fixtures/tuple.pyi]