diff --git a/mypy/checker.py b/mypy/checker.py index 109fc24043bd..7b0180f79764 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -40,7 +40,7 @@ from mypy.sametypes import is_same_type, is_same_types from mypy.messages import MessageBuilder, make_inferred_type_note import mypy.checkexpr -from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound +from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound, type_object_type from mypy import messages from mypy.subtypes import ( is_subtype, is_equivalent, is_proper_subtype, is_more_precise, @@ -1255,6 +1255,29 @@ def visit_class_def(self, defn: ClassDef) -> None: # Otherwise we've already found errors; more errors are not useful self.check_multiple_inheritance(typ) + if defn.decorators: + sig = type_object_type(defn.info, self.named_type) + # Decorators are applied in reverse order. + for decorator in reversed(defn.decorators): + if (isinstance(decorator, CallExpr) + and isinstance(decorator.analyzed, PromoteExpr)): + # _promote is a special type checking related construct. + continue + + dec = self.expr_checker.accept(decorator) + temp = self.temp_node(sig) + fullname = None + if isinstance(decorator, RefExpr): + fullname = decorator.fullname + + # TODO: Figure out how to have clearer error messages. + # (e.g. "class decorator must be a function that accepts a type." + sig, _ = self.expr_checker.check_call(dec, [temp], + [nodes.ARG_POS], defn, + callable_name=fullname) + # TODO: Apply the sig to the actual TypeInfo so we can handle decorators + # that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]]) + def check_protocol_variance(self, defn: ClassDef) -> None: """Check that protocol definition is compatible with declared variances of type variables. diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 0d6cd0102af7..9137345d141e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4115,7 +4115,8 @@ def f() -> type: return M class C1(six.with_metaclass(M), object): pass # E: Invalid base class class C2(C1, six.with_metaclass(M)): pass # E: Invalid base class class C3(six.with_metaclass(A)): pass # E: Metaclasses not inheriting from 'type' are not supported -@six.add_metaclass(A) # E: Metaclasses not inheriting from 'type' are not supported +@six.add_metaclass(A) # E: Argument 1 to "add_metaclass" has incompatible type "Type[A]"; expected "Type[type]" \ + # E: Metaclasses not inheriting from 'type' are not supported class D3(A): pass class C4(six.with_metaclass(M), metaclass=M): pass # E: Multiple metaclass definitions @six.add_metaclass(M) # E: Multiple metaclass definitions @@ -4223,3 +4224,38 @@ class C(Any): reveal_type(self.bar().__name__) # E: Revealed type is 'builtins.str' [builtins fixtures/type.pyi] [out] + +[case testClassDecoratorIsTypeChecked] +from typing import Callable, Type +def decorate(x: int) -> Callable[[type], type]: # N: "decorate" defined here + ... +def decorate_forward_ref() -> Callable[[Type[A]], Type[A]]: + ... +@decorate(y=17) # E: Unexpected keyword argument "y" for "decorate" +@decorate() # E: Too few arguments for "decorate" +@decorate(22, 25) # E: Too many arguments for "decorate" +@decorate_forward_ref() +@decorate(11) +class A: pass + +@decorate # E: Argument 1 to "decorate" has incompatible type "Type[A2]"; expected "int" +class A2: pass + +[case testClassDecoratorIncorrect] +def not_a_class_decorator(x: int) -> int: ... +@not_a_class_decorator(7) # E: "int" not callable +class A3: pass + +not_a_function = 17 +@not_a_function() # E: "int" not callable +class B: pass + +@not_a_function # E: "int" not callable +class B2: pass + +b = object() +@b.nothing # E: "object" has no attribute "nothing" +class C: pass + +@undefined # E: Name 'undefined' is not defined +class D: pass