88from textwrap import dedent
99from weakref import WeakKeyDictionary
1010
11+ from .functional import complement , keyfilter
1112from .typecheck import compatible
13+ from .typed_signature import TypedSignature
1214from .utils import is_a , unique
1315
1416first = itemgetter (0 )
@@ -21,19 +23,50 @@ class IncompleteImplementation(TypeError):
2123 """
2224
2325
26+ CLASS_ATTRIBUTE_WHITELIST = frozenset ([
27+ '__doc__' ,
28+ '__module__' ,
29+ '__name__' ,
30+ '__qualname__' ,
31+ '__weakref__' ,
32+ ])
33+
34+ is_interface_field_name = complement (CLASS_ATTRIBUTE_WHITELIST .__contains__ )
35+
36+
37+ def static_get_type_attr (t , name ):
38+ """
39+ Get a type attribute statically, circumventing the descriptor protocol.
40+ """
41+ for type_ in t .mro ():
42+ try :
43+ return vars (type_ )[name ]
44+ except KeyError :
45+ pass
46+ raise AttributeError (name )
47+
48+
2449class InterfaceMeta (type ):
2550 """
2651 Metaclass for interfaces.
2752
28- Supplies a ``_signatures`` attribute and a ``check_implementation`` method .
53+ Supplies a ``_signatures`` attribute.
2954 """
3055 def __new__ (mcls , name , bases , clsdict ):
3156 signatures = {}
32- for k , v in clsdict .items ():
57+ for k , v in keyfilter ( is_interface_field_name , clsdict ) .items ():
3358 try :
34- signatures [k ] = inspect .signature (v )
35- except TypeError :
36- pass
59+ signatures [k ] = TypedSignature (v )
60+ except TypeError as e :
61+ errmsg = (
62+ "Couldn't parse signature for field "
63+ "{iface_name}.{fieldname} of type {attrtype}." .format (
64+ iface_name = name ,
65+ fieldname = k ,
66+ attrtype = getname (type (v )),
67+ )
68+ )
69+ raise TypeError (errmsg ) from e
3770
3871 clsdict ['_signatures' ] = signatures
3972 return super ().__new__ (mcls , name , bases , clsdict )
@@ -49,23 +82,33 @@ def _diff_signatures(self, type_):
4982
5083 Returns
5184 -------
52- missing, mismatched : list[str], dict[str -> signature]
53- ``missing`` is a list of missing method names.
54- ``mismatched `` is a dict mapping method names to incorrect
55- signatures.
85+ missing, mistyped, mismatched : list[str], dict[str -> type], dict[str -> signature] # noqa
86+ ``missing`` is a list of missing interface names.
87+ ``mistyped `` is a list mapping names to incorrect types.
88+ ``mismatched`` is a dict mapping names to incorrect signatures.
5689 """
5790 missing = []
91+ mistyped = {}
5892 mismatched = {}
5993 for name , iface_sig in self ._signatures .items ():
6094 try :
61- f = getattr (type_ , name )
95+ # Don't invoke the descriptor protocol here so that we get
96+ # staticmethod/classmethod/property objects instead of the
97+ # functions they wrap.
98+ f = static_get_type_attr (type_ , name )
6299 except AttributeError :
63100 missing .append (name )
64101 continue
65- impl_sig = inspect .signature (f )
66- if not compatible (impl_sig , iface_sig ):
102+
103+ impl_sig = TypedSignature (f )
104+
105+ if not issubclass (impl_sig .type , iface_sig .type ):
106+ mistyped [name ] = impl_sig .type
107+
108+ if not compatible (impl_sig .signature , iface_sig .signature ):
67109 mismatched [name ] = impl_sig
68- return missing , mismatched
110+
111+ return missing , mistyped , mismatched
69112
70113 def verify (self , type_ ):
71114 """
@@ -85,16 +128,16 @@ def verify(self, type_):
85128 -------
86129 None
87130 """
88- missing , mismatched = self ._diff_signatures (type_ )
89- if not missing and not mismatched :
131+ missing , mistyped , mismatched = self ._diff_signatures (type_ )
132+ if not any (( missing , mistyped , mismatched )) :
90133 return
91- raise self ._invalid_implementation (type_ , missing , mismatched )
134+ raise self ._invalid_implementation (type_ , missing , mistyped , mismatched )
92135
93- def _invalid_implementation (self , t , missing , mismatched ):
136+ def _invalid_implementation (self , t , missing , mistyped , mismatched ):
94137 """
95138 Make a TypeError explaining why ``t`` doesn't implement our interface.
96139 """
97- assert missing or mismatched , "Implementation wasn't invalid."
140+ assert missing or mistyped or mismatched , "Implementation wasn't invalid."
98141
99142 message = "\n class {C} failed to implement interface {I}:" .format (
100143 C = getname (t ),
@@ -111,6 +154,17 @@ def _invalid_implementation(self, t, missing, mismatched):
111154 missing_methods = self ._format_missing_methods (missing )
112155 )
113156
157+ if mistyped :
158+ message += dedent (
159+ """
160+
161+ The following methods of {I} were implemented with incorrect types:
162+ {mismatched_types}"""
163+ ).format (
164+ I = getname (self ),
165+ mismatched_types = self ._format_mismatched_types (mistyped ),
166+ )
167+
114168 if mismatched :
115169 message += dedent (
116170 """
@@ -129,6 +183,17 @@ def _format_missing_methods(self, missing):
129183 for name in missing
130184 ]))
131185
186+ def _format_mismatched_types (self , mistyped ):
187+ return "\n " .join (sorted ([
188+ " - {name}: {actual!r} is not a subtype "
189+ "of expected type {expected!r}" .format (
190+ name = name ,
191+ actual = getname (bad_type ),
192+ expected = getname (self ._signatures [name ].type ),
193+ )
194+ for name , bad_type in mistyped .items ()
195+ ]))
196+
132197 def _format_mismatched_methods (self , mismatched ):
133198 return "\n " .join (sorted ([
134199 " - {name}{actual} != {name}{expected}" .format (
0 commit comments