2525from mypy import nodes
2626from mypy .config_parser import parse_config_file
2727from 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
2929
3030
3131class Missing :
@@ -243,6 +243,60 @@ def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool:
243243 )
244244
245245
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+
246300@verify .register (nodes .TypeInfo )
247301def verify_typeinfo (
248302 stub : nodes .TypeInfo , runtime : MaybeMissing [Type [Any ]], object_path : List [str ]
@@ -274,11 +328,9 @@ class SubClass(runtime): # type: ignore
274328
275329 # Check everything already defined in the stub
276330 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.
279331 to_check .update (
280332 # 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
282334 )
283335
284336 for entry in sorted (to_check ):
@@ -292,8 +344,16 @@ class SubClass(runtime): # type: ignore
292344 except Exception :
293345 # Catch all exceptions in case the runtime raises an unexpected exception
294346 # 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+ ):
297357 yield from verify (stub_to_verify , runtime_attr , object_path + [entry ])
298358
299359
0 commit comments