Skip to content

Commit 46ecea5

Browse files
eurestiilevkivskyi
authored andcommitted
Type checking of class decorators (#4544)
It checks that the calls are well-type and that applying the decorators works. It does not check/apply the end result. Helps with #3135
1 parent dcb85ea commit 46ecea5

File tree

2 files changed

+61
-2
lines changed

2 files changed

+61
-2
lines changed

mypy/checker.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from mypy.sametypes import is_same_type, is_same_types
4141
from mypy.messages import MessageBuilder, make_inferred_type_note
4242
import mypy.checkexpr
43-
from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound
43+
from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound, type_object_type
4444
from mypy import messages
4545
from mypy.subtypes import (
4646
is_subtype, is_equivalent, is_proper_subtype, is_more_precise,
@@ -1255,6 +1255,29 @@ def visit_class_def(self, defn: ClassDef) -> None:
12551255
# Otherwise we've already found errors; more errors are not useful
12561256
self.check_multiple_inheritance(typ)
12571257

1258+
if defn.decorators:
1259+
sig = type_object_type(defn.info, self.named_type)
1260+
# Decorators are applied in reverse order.
1261+
for decorator in reversed(defn.decorators):
1262+
if (isinstance(decorator, CallExpr)
1263+
and isinstance(decorator.analyzed, PromoteExpr)):
1264+
# _promote is a special type checking related construct.
1265+
continue
1266+
1267+
dec = self.expr_checker.accept(decorator)
1268+
temp = self.temp_node(sig)
1269+
fullname = None
1270+
if isinstance(decorator, RefExpr):
1271+
fullname = decorator.fullname
1272+
1273+
# TODO: Figure out how to have clearer error messages.
1274+
# (e.g. "class decorator must be a function that accepts a type."
1275+
sig, _ = self.expr_checker.check_call(dec, [temp],
1276+
[nodes.ARG_POS], defn,
1277+
callable_name=fullname)
1278+
# TODO: Apply the sig to the actual TypeInfo so we can handle decorators
1279+
# that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]])
1280+
12581281
def check_protocol_variance(self, defn: ClassDef) -> None:
12591282
"""Check that protocol definition is compatible with declared
12601283
variances of type variables.

test-data/unit/check-classes.test

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4115,7 +4115,8 @@ def f() -> type: return M
41154115
class C1(six.with_metaclass(M), object): pass # E: Invalid base class
41164116
class C2(C1, six.with_metaclass(M)): pass # E: Invalid base class
41174117
class C3(six.with_metaclass(A)): pass # E: Metaclasses not inheriting from 'type' are not supported
4118-
@six.add_metaclass(A) # E: Metaclasses not inheriting from 'type' are not supported
4118+
@six.add_metaclass(A) # E: Argument 1 to "add_metaclass" has incompatible type "Type[A]"; expected "Type[type]" \
4119+
# E: Metaclasses not inheriting from 'type' are not supported
41194120
class D3(A): pass
41204121
class C4(six.with_metaclass(M), metaclass=M): pass # E: Multiple metaclass definitions
41214122
@six.add_metaclass(M) # E: Multiple metaclass definitions
@@ -4223,3 +4224,38 @@ class C(Any):
42234224
reveal_type(self.bar().__name__) # E: Revealed type is 'builtins.str'
42244225
[builtins fixtures/type.pyi]
42254226
[out]
4227+
4228+
[case testClassDecoratorIsTypeChecked]
4229+
from typing import Callable, Type
4230+
def decorate(x: int) -> Callable[[type], type]: # N: "decorate" defined here
4231+
...
4232+
def decorate_forward_ref() -> Callable[[Type[A]], Type[A]]:
4233+
...
4234+
@decorate(y=17) # E: Unexpected keyword argument "y" for "decorate"
4235+
@decorate() # E: Too few arguments for "decorate"
4236+
@decorate(22, 25) # E: Too many arguments for "decorate"
4237+
@decorate_forward_ref()
4238+
@decorate(11)
4239+
class A: pass
4240+
4241+
@decorate # E: Argument 1 to "decorate" has incompatible type "Type[A2]"; expected "int"
4242+
class A2: pass
4243+
4244+
[case testClassDecoratorIncorrect]
4245+
def not_a_class_decorator(x: int) -> int: ...
4246+
@not_a_class_decorator(7) # E: "int" not callable
4247+
class A3: pass
4248+
4249+
not_a_function = 17
4250+
@not_a_function() # E: "int" not callable
4251+
class B: pass
4252+
4253+
@not_a_function # E: "int" not callable
4254+
class B2: pass
4255+
4256+
b = object()
4257+
@b.nothing # E: "object" has no attribute "nothing"
4258+
class C: pass
4259+
4260+
@undefined # E: Name 'undefined' is not defined
4261+
class D: pass

0 commit comments

Comments
 (0)