25
25
from mypy import nodes
26
26
from mypy .config_parser import parse_config_file
27
27
from mypy .options import Options
28
- from mypy .util import FancyFormatter , bytes_to_human_readable_repr , is_dunder , SPECIAL_DUNDERS
28
+ from mypy .util import FancyFormatter , bytes_to_human_readable_repr , is_dunder
29
29
30
30
31
31
class Missing :
@@ -243,6 +243,60 @@ def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool:
243
243
)
244
244
245
245
246
+ IGNORED_DUNDERS = frozenset ({
247
+ # Very special attributes
248
+ "__weakref__" ,
249
+ "__slots__" ,
250
+ "__dict__" ,
251
+ "__text_signature__" ,
252
+ # Pickle methods
253
+ "__setstate__" ,
254
+ "__getstate__" ,
255
+ "__getnewargs__" ,
256
+ "__getinitargs__" ,
257
+ "__reduce_ex__" ,
258
+ "__reduce__" ,
259
+ # typing implementation details
260
+ "__parameters__" ,
261
+ "__origin__" ,
262
+ "__args__" ,
263
+ "__orig_bases__" ,
264
+ "__final__" ,
265
+ # isinstance/issubclass hooks that type-checkers don't usually care about
266
+ "__instancecheck__" ,
267
+ "__subclasshook__" ,
268
+ "__subclasscheck__" ,
269
+ # Dataclasses implementation details
270
+ "__dataclass_fields__" ,
271
+ "__dataclass_params__" ,
272
+ # ctypes weirdness
273
+ "__ctype_be__" ,
274
+ "__ctype_le__" ,
275
+ "__ctypes_from_outparam__" ,
276
+ # These two are basically useless for type checkers
277
+ "__hash__" ,
278
+ "__getattr__" ,
279
+ # For some reason, mypy doesn't infer classes with metaclass=ABCMeta inherit this attribute
280
+ "__abstractmethods__" ,
281
+ # Ideally we'd include __match_args__ in stubs,
282
+ # but this currently has issues
283
+ "__match_args__" ,
284
+ "__doc__" , # Can only ever be str | None, who cares?
285
+ "__del__" , # Only ever called when an object is being deleted, who cares?
286
+ "__new_member__" , # If an enum defines __new__, the method is renamed as __new_member__
287
+ })
288
+
289
+
290
+ if sys .version_info >= (3 , 7 ):
291
+ _WrapperDescriptorType = types .WrapperDescriptorType
292
+ else :
293
+ _WrapperDescriptorType = type (object .__init__ )
294
+
295
+
296
+ def is_private (name : str ) -> bool :
297
+ return name .startswith ("_" ) and not is_dunder (name )
298
+
299
+
246
300
@verify .register (nodes .TypeInfo )
247
301
def verify_typeinfo (
248
302
stub : nodes .TypeInfo , runtime : MaybeMissing [Type [Any ]], object_path : List [str ]
@@ -274,11 +328,9 @@ class SubClass(runtime): # type: ignore
274
328
275
329
# Check everything already defined in the stub
276
330
to_check = set (stub .names )
277
- # There's a reasonable case to be made that we should always check all dunders, but it's
278
- # currently quite noisy. We could turn this into a denylist instead of an allowlist.
279
331
to_check .update (
280
332
# cast to workaround mypyc complaints
281
- m for m in cast (Any , vars )(runtime ) if not m . startswith ( "_" ) or m in SPECIAL_DUNDERS
333
+ m for m in cast (Any , vars )(runtime ) if not is_private ( m ) and m not in IGNORED_DUNDERS
282
334
)
283
335
284
336
for entry in sorted (to_check ):
@@ -292,8 +344,16 @@ class SubClass(runtime): # type: ignore
292
344
except Exception :
293
345
# Catch all exceptions in case the runtime raises an unexpected exception
294
346
# from __getattr__ or similar.
295
- pass
296
- else :
347
+ continue
348
+ # Do not error for an object missing from the stub
349
+ # If the runtime object is a types.WrapperDescriptorType object
350
+ # and has a non-special dunder name.
351
+ # The vast majority of these are false positives.
352
+ if not (
353
+ isinstance (stub_to_verify , Missing )
354
+ and isinstance (runtime_attr , _WrapperDescriptorType )
355
+ and is_dunder (mangled_entry , exclude_special = True )
356
+ ):
297
357
yield from verify (stub_to_verify , runtime_attr , object_path + [entry ])
298
358
299
359
0 commit comments