diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index e850744b5c71..5092e5860947 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2932,6 +2932,9 @@ def visit_index_with_type(self, left_type: Type, e: IndexExpr, # Visit the index, just to make sure we have a type for it available self.accept(index) + # Base method name for instance-based item access, might be changed for types: + method_name = '__getitem__' + if isinstance(left_type, UnionType): original_type = original_type or left_type # Don't combine literal types, since we may need them for type narrowing. @@ -2967,12 +2970,22 @@ def visit_index_with_type(self, left_type: Type, e: IndexExpr, elif (isinstance(left_type, TypeVarType) and not self.has_member(left_type.upper_bound, "__getitem__")): return self.visit_index_with_type(left_type.upper_bound, e, original_type) - else: - result, method_type = self.check_method_call_by_name( - '__getitem__', left_type, [e.index], [ARG_POS], e, - original_type=original_type) - e.method_type = method_type - return result + elif (isinstance(left_type, TypeType) + or (isinstance(left_type, CallableType) and left_type.is_type_obj())): + # When we do `SomeClass[1]`, we actually call `__class_getitem__`, + # not just `__getitem__`. + method_name = '__class_getitem__' + + result, method_type = self.check_method_call_by_name( + method=method_name, + base_type=left_type, + args=[e.index], + arg_kinds=[ARG_POS], + context=e, + original_type=original_type, + ) + e.method_type = method_type + return result def visit_tuple_slice_helper(self, left_type: TupleType, slic: SliceExpr) -> Type: begin: Sequence[Optional[int]] = [None] diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 3efc39753627..9e4edf2f5989 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -235,7 +235,7 @@ def analyze_type_callable_member_access(name: str, if isinstance(ret_type, TupleType): ret_type = tuple_fallback(ret_type) if isinstance(ret_type, Instance): - if not mx.is_operator: + if not mx.is_operator or name == '__class_getitem__': # When Python sees an operator (eg `3 == 4`), it automatically translates that # into something like `int.__eq__(3, 4)` instead of `(3).__eq__(4)` as an # optimization. @@ -250,6 +250,9 @@ def analyze_type_callable_member_access(name: str, # the corresponding method in the current instance to avoid this edge case. # See https://github.com/python/mypy/pull/1787 for more info. # TODO: do not rely on same type variables being present in all constructor overloads. + + # We also allow `SomeClass[1]` acccess via `__class_getitem__`, + # it is very special. It only works this way for non-generic types. result = analyze_class_attribute_access(ret_type, name, mx, original_vars=typ.items[0].variables) if result: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index d400c7e1ca69..6dc9f6ae0b61 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1072,6 +1072,12 @@ def fix_instance(t: Instance, fail: MsgCallback, note: MsgCallback, Also emit a suitable error if this is not due to implicit Any's. """ + if not t.type.is_generic() and t.type.has_readable_member('__class_getitem__'): + # Corner case: we might have a non-generic class with `__class_getitem__` + # which is used for something else: not type application. + # So, in this case: we allow using this type without type arguments. + return + if len(t.args) == 0: if use_generic_error: fullname: Optional[str] = None @@ -1081,6 +1087,7 @@ def fix_instance(t: Instance, fail: MsgCallback, note: MsgCallback, unexpanded_type) t.args = (any_type,) * len(t.type.type_vars) return + # Invalid number of type parameters. n = len(t.type.type_vars) s = '{} type arguments'.format(n)