Repository: python/mypy

Issue:
Mypy doesn't recognize metaclass implementing __getitem__
Suppose we have an `Enum` called `MyEnum`, and we try something like `MyEnum["foo"]`, mypy will give us an error `Value of type "MyEnum" is not indexable`. We believe this is because mypy has trouble recognizing when a metaclass implements `__getitem__`.

A workaround is to do `getattr(MyEnum, "foo")`, but it'd be nice to have an actual fix.



Discussion:
Oh, it looks like this already exists in the tracker: #741. I'll have to close this as a duplicate.

Reopening this, since there's an extra wrinkle specifically for `__getitem__` -- because `X[Y]` is used as generic type parameterization, even with @elazarg's fix (#2475) the metaclass's `__getitem__` is not honored.

Resolving patch:
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -392,8 +392,13 @@ def has_no_attr(self, typ: Type, member: str, context: Context) -> Type:
                 self.format(typ)), context)
         elif member == '__getitem__':
             # Indexed get.
-            self.fail('Value of type {} is not indexable'.format(
-                self.format(typ)), context)
+            # TODO: Fix this consistently in self.format
+            if isinstance(typ, CallableType) and typ.is_type_obj():
+                self.fail('The type {} is not generic and not indexable'.format(
+                    self.format(typ)), context)
+            else:
+                self.fail('Value of type {} is not indexable'.format(
+                    self.format(typ)), context)
         elif member == '__setitem__':
             # Indexed set.
             self.fail('Unsupported target for indexed assignment', context)
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -2623,7 +2623,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None:
         expr.base.accept(self)
         if (isinstance(expr.base, RefExpr)
                 and isinstance(expr.base.node, TypeInfo)
-                and expr.base.node.is_enum):
+                and not expr.base.node.is_generic()):
             expr.index.accept(self)
         elif isinstance(expr.base, RefExpr) and expr.base.kind == TYPE_ALIAS:
             # Special form -- subscripting a generic type alias.


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -2883,6 +2883,14 @@ class Concrete(metaclass=Meta):
 reveal_type(Concrete + X())  # E: Revealed type is 'builtins.str'
 Concrete + "hello"  # E: Unsupported operand types for + ("Meta" and "str")
 
+[case testMetaclassGetitem]
+class M(type):
+    def __getitem__(self, key) -> int: return 1
+
+class A(metaclass=M): pass
+
+reveal_type(A[M])  # E: Revealed type is 'builtins.int'
+
 [case testMetaclassSelftype]
 from typing import TypeVar, Type
 
diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test
--- a/test-data/unit/check-generics.test
+++ b/test-data/unit/check-generics.test
@@ -449,7 +449,7 @@ A[int, str, int]() # E: Type application has too many types (2 expected)
 a = None # type: A
 class A: pass
 a[A]()  # E: Value of type "A" is not indexable
-A[A]()  # E: Type application targets a non-generic function or class
+A[A]()  # E: The type "A" is not generic and not indexable
 [out]
 
 [case testTypeApplicationArgTypes]
@@ -505,7 +505,7 @@ Alias[int]("a")  # E: Argument 1 to "Node" has incompatible type "str"; expected
 [out]
 
 [case testTypeApplicationCrash]
-type[int] # this was crashing, see #2302 (comment)  # E: Type application targets a non-generic function or class
+type[int] # this was crashing, see #2302 (comment)  # E: The type "type" is not generic and not indexable
 [out]
 
 
diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test
--- a/test-data/unit/semanal-errors.test
+++ b/test-data/unit/semanal-errors.test
@@ -103,13 +103,6 @@ class A: pass
 x = cast(A[int], 1) # E: "A" expects no type arguments, but 1 given
 [out]
 
-[case testInvalidNumberOfGenericArgsInTypeApplication]
-import typing
-class A: pass
-class B: pass
-x = A[B[int]]() # E: "B" expects no type arguments, but 1 given
-[out]
-
 [case testInvalidNumberOfGenericArgsInNestedGenericType]
 from typing import TypeVar, Generic
 T = TypeVar('T')