Repository: python/mypy

Issue:
Incorrect incompatible override report with self type + overload + TypeVar with constraints
Similar to #14002; came up in python/typeshed#9084.

```python
from typing import TypeVar, Generic, overload

T = TypeVar("T", str, int)

class Base(Generic[T]):
    @overload
    def method(self, s: T) -> T: ...
    @overload
    def method(self: Base[str], s: bytes) -> str: ...
    def method(self, s: object): ...

class Child(Base[int]):
    def method(self, s: int) -> int:
        raise NotImplementedError
```
(https://mypy-play.net/?mypy=latest&python=3.10&gist=da356a97851f7b7649d208e1359a6de4)

with mypy 1.1.1 produces:

```
/Users/jelle/bin/inerhit.py:13: error: Signature of "method" incompatible with supertype "Base"  [override]
/Users/jelle/bin/inerhit.py:13: note:      Superclass:
/Users/jelle/bin/inerhit.py:13: note:          @overload
/Users/jelle/bin/inerhit.py:13: note:          def method(self, s: int) -> int
/Users/jelle/bin/inerhit.py:13: note:          @overload
/Users/jelle/bin/inerhit.py:13: note:          def method(self, s: bytes) -> str
/Users/jelle/bin/inerhit.py:13: note:      Subclass:
/Users/jelle/bin/inerhit.py:13: note:          def method(self, s: int) -> int
Found 1 error in 1 file (checked 1 source file)
```

There should be no errors. The second overload is not applicable to a `Base[int]`, so it should be fine that the subclass lacks it.


Discussion:

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- 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):


Test patch:
diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test
--- a/test-data/unit/check-selftype.test
+++ b/test-data/unit/check-selftype.test
@@ -208,6 +208,30 @@ class J(A[int]):
 
 [builtins fixtures/tuple.pyi]
 
+[case testSelfTypeOverrideCompatibilityGeneric]
+from typing import TypeVar, Generic, overload
+
+T = TypeVar("T", str, int, None)
+
+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[None]):
+    def f(self, s: int) -> int: ...  # E: Signature of "f" incompatible with supertype "A" \
+                                     # N:      Superclass:            \
+                                     # N:          @overload          \
+                                     # N:          def f(self, s: None) -> None \
+                                     # N:      Subclass:              \
+                                     # N:          def f(self, s: int) -> int
+[builtins fixtures/tuple.pyi]
+
 [case testSelfTypeOverrideCompatibilityTypeVar-xfail]
 from typing import overload, TypeVar, Union