Repository: python/mypy

Issue:
SupportsDivmod
There doesn't seem to be such a thing right now, so when I add __divmod__ to my class, mypy still complains:

    error: Type argument 1 of "divmod" has incompatible value "MyClassHere"

    error: Type argument 1 of "divmod" has incompatible value "SupportsInt"

For the same line (my class does SupportsInt).


Discussion:
This problem will be fixed by protocols (structural subtyping) proposed by PEP 544 (and corresponding improvements to typeshed stubs). 
Good - is there a temporary workaround though?
Right now I just place `# type: ignore` on any line that uses `divmod` on my class (mercifully, it is very rare).
I don't think anything could be done now from mypy side. typeshed stubs allow only arguments that explicitly inherit from ``int`` or ``float``. If you have an idea about how to improve stubs, then you could make a PR to [typeshed](https://github.com/python/typeshed).
Possibly related issue: if my class impements `__index__`, there seems to be no way to tell `mypy` that it is allowed inside `[...]` of unrelated types (there is no `SupportsIndex`). Again, `# type: ignore` to the rescue...
The full list of operations that `mypy` can't be convinced not to complain about is:

* `__divmod__`, which triggered opening this issue.

* `__index__`, which may be an unrelated issue?

* `__complex__`, which is strange, since my class derives from `typing.SupportsComplex`.

I can live with `# type: ignore` these, as there is a trivial workaround (just wrap the value with `int(...)`.

Still, it isn't "nice" that there's no way to define a type-checked class that behaves "like an int" but doesn't derive from `int`.

(Edit: I complained about `__reversed__`, which actually works. My bad.)
> * __index__, which may be an unrelated issue?

Here is a code sample showing the issue. The following code runs fine:
```
class MyInt:
    def __init__(self, value: int) -> None:
        self.value = value

    def __index__(self) -> int:
        return self.value

l = ['foo', 'bar']
t = ('foo', 'bar')

print(hex(MyInt(7)))
print(l[MyInt(0)])
print(t[MyInt(1)])
```
mypy does not complain about the `hex` line, but it does complain about the next two lines:
```
t.py:11: error: No overload variant of "__getitem__" of "list" matches argument type "MyInt"  [call-overload]
t.py:12: error: Invalid tuple index type (actual type "MyInt", expected type "Union[int, slice]")  [misc]
```


Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -3480,25 +3480,12 @@ def try_getting_int_literals(self, index: Expression) -> Optional[List[int]]:
         return None
 
     def nonliteral_tuple_index_helper(self, left_type: TupleType, index: Expression) -> Type:
-        index_type = self.accept(index)
-        expected_type = UnionType.make_union(
-            [self.named_type("builtins.int"), self.named_type("builtins.slice")]
-        )
-        if not self.chk.check_subtype(
-            index_type,
-            expected_type,
-            index,
-            message_registry.INVALID_TUPLE_INDEX_TYPE,
-            "actual type",
-            "expected type",
-        ):
-            return AnyType(TypeOfAny.from_error)
-        else:
-            union = make_simplified_union(left_type.items)
-            if isinstance(index, SliceExpr):
-                return self.chk.named_generic_type("builtins.tuple", [union])
-            else:
-                return union
+        self.check_method_call_by_name("__getitem__", left_type, [index], [ARG_POS], context=index)
+        # We could return the return type from above, but unions are often better than the join
+        union = make_simplified_union(left_type.items)
+        if isinstance(index, SliceExpr):
+            return self.chk.named_generic_type("builtins.tuple", [union])
+        return union
 
     def visit_typeddict_index_expr(self, td_type: TypedDictType, index: Expression) -> Type:
         if isinstance(index, StrExpr):
diff --git a/mypy/message_registry.py b/mypy/message_registry.py
--- a/mypy/message_registry.py
+++ b/mypy/message_registry.py
@@ -67,7 +67,6 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage":
 INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION: Final = "Incompatible types in string interpolation"
 INCOMPATIBLE_TYPES_IN_CAPTURE: Final = ErrorMessage("Incompatible types in capture pattern")
 MUST_HAVE_NONE_RETURN_TYPE: Final = ErrorMessage('The return type of "{}" must be None')
-INVALID_TUPLE_INDEX_TYPE: Final = ErrorMessage("Invalid tuple index type")
 TUPLE_INDEX_OUT_OF_RANGE: Final = ErrorMessage("Tuple index out of range")
 INVALID_SLICE_INDEX: Final = ErrorMessage("Slice index must be an integer or None")
 CANNOT_INFER_LAMBDA_TYPE: Final = ErrorMessage("Cannot infer type of lambda")


Test patch:
diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test
--- a/test-data/unit/check-class-namedtuple.test
+++ b/test-data/unit/check-class-namedtuple.test
@@ -582,8 +582,11 @@ class Base(NamedTuple):
         reveal_type(self.x)  # N: Revealed type is "builtins.int"
         self.x = 3  # E: Property "x" defined in "Base" is read-only
         self[1]  # E: Tuple index out of range
-        reveal_type(self[T])  # N: Revealed type is "Any" \
-                              # E: Invalid tuple index type (actual type "object", expected type "Union[int, slice]")
+        reveal_type(self[T])  # N: Revealed type is "builtins.int" \
+                              # E: No overload variant of "__getitem__" of "tuple" matches argument type "object" \
+                              # N: Possible overload variants: \
+                              # N:     def __getitem__(self, int) -> int \
+                              # N:     def __getitem__(self, slice) -> Tuple[int, ...]
         return self.x
     def bad_override(self) -> int:
         return self.x
diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test
--- a/test-data/unit/check-narrowing.test
+++ b/test-data/unit/check-narrowing.test
@@ -434,7 +434,10 @@ else:
     reveal_type(impossible_mixture)     # N: Revealed type is "Union[__main__.KeyedObject, TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]})]"
 
 weird_mixture: Union[KeyedTypedDict, KeyedNamedTuple]
-if weird_mixture["key"] is Key.B:       # E: Invalid tuple index type (actual type "str", expected type "Union[int, slice]")
+if weird_mixture["key"] is Key.B:       # E: No overload variant of "__getitem__" of "tuple" matches argument type "str" \
+                                        # N: Possible overload variants: \
+                                        # N:     def __getitem__(self, int) -> Literal[Key.C] \
+                                        # N:     def __getitem__(self, slice) -> Tuple[Literal[Key.C], ...]
     reveal_type(weird_mixture)          # N: Revealed type is "Union[TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}), Tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]]"
 else:
     reveal_type(weird_mixture)          # N: Revealed type is "Union[TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}), Tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]]"
@@ -443,7 +446,8 @@ if weird_mixture[0] is Key.B:           # E: TypedDict key must be a string lite
     reveal_type(weird_mixture)          # N: Revealed type is "Union[TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}), Tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]]"
 else:
     reveal_type(weird_mixture)          # N: Revealed type is "Union[TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}), Tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]]"
-[builtins fixtures/slice.pyi]
+[builtins fixtures/tuple.pyi]
+[typing fixtures/typing-full.pyi]
 
 [case testNarrowingParentWithProperties]
 from enum import Enum
diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test
--- a/test-data/unit/check-protocols.test
+++ b/test-data/unit/check-protocols.test
@@ -2630,6 +2630,7 @@ class P(Protocol): ...
 class C(P): ...
 
 reveal_type(C.register(int))  # N: Revealed type is "def () -> builtins.int"
+[builtins fixtures/tuple.pyi]
 [typing fixtures/typing-full.pyi]
 [out]
 
diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test
--- a/test-data/unit/check-tuples.test
+++ b/test-data/unit/check-tuples.test
@@ -1226,7 +1226,11 @@ t = (0, "")
 x = 0
 y = ""
 reveal_type(t[x])  # N: Revealed type is "Union[builtins.int, builtins.str]"
-t[y]  # E: Invalid tuple index type (actual type "str", expected type "Union[int, slice]")
+t[y]  # E: No overload variant of "__getitem__" of "tuple" matches argument type "str" \
+      # N: Possible overload variants: \
+      # N:     def __getitem__(self, int) -> object \
+      # N:     def __getitem__(self, slice) -> Tuple[object, ...]
+
 [builtins fixtures/tuple.pyi]
 
 [case testNonliteralTupleSlice]