99import enum
1010import importlib
1111import inspect
12+ import re
1213import sys
1314import types
1415import warnings
@@ -240,9 +241,12 @@ def verify_typeinfo(
240241 to_check .update (m for m in cast (Any , vars )(runtime ) if not m .startswith ("_" ))
241242
242243 for entry in sorted (to_check ):
244+ mangled_entry = entry
245+ if entry .startswith ("__" ) and not entry .endswith ("__" ):
246+ mangled_entry = "_{}{}" .format (stub .name , entry )
243247 yield from verify (
244248 next ((t .names [entry ].node for t in stub .mro if entry in t .names ), MISSING ),
245- getattr (runtime , entry , MISSING ),
249+ getattr (runtime , mangled_entry , MISSING ),
246250 object_path + [entry ],
247251 )
248252
@@ -266,7 +270,13 @@ def _verify_static_class_methods(
266270 # Look the object up statically, to avoid binding by the descriptor protocol
267271 static_runtime = importlib .import_module (object_path [0 ])
268272 for entry in object_path [1 :]:
269- static_runtime = inspect .getattr_static (static_runtime , entry )
273+ try :
274+ static_runtime = inspect .getattr_static (static_runtime , entry )
275+ except AttributeError :
276+ # This can happen with mangled names, ignore for now.
277+ # TODO: pass more information about ancestors of nodes/objects to verify, so we don't
278+ # have to do this hacky lookup. Would be useful in a couple other places too.
279+ return
270280
271281 if isinstance (static_runtime , classmethod ) and not stub .is_class :
272282 yield "runtime is a classmethod but stub is not"
@@ -582,21 +592,24 @@ def _verify_signature(
582592
583593 # Check unmatched keyword-only args
584594 if runtime .varkw is None or not set (runtime .kwonly ).issubset (set (stub .kwonly )):
595+ # There are cases where the stub exhaustively lists out the extra parameters the function
596+ # would take through *kwargs. Hence, a) we only check if the runtime actually takes those
597+ # parameters when the above condition holds and b) below, we don't enforce that the stub
598+ # takes *kwargs, since runtime logic may prevent additional arguments from actually being
599+ # accepted.
585600 for arg in sorted (set (stub .kwonly ) - set (runtime .kwonly )):
586601 yield 'runtime does not have argument "{}"' .format (arg )
587- if stub .varkw is None or not set (stub .kwonly ).issubset (set (runtime .kwonly )):
588- for arg in sorted (set (runtime .kwonly ) - set (stub .kwonly )):
589- if arg in set (stub_arg .variable .name for stub_arg in stub .pos ):
590- # Don't report this if we've reported it before
591- if len (stub .pos ) > len (runtime .pos ) and runtime .varpos is not None :
592- yield 'stub argument "{}" is not keyword-only' .format (arg )
593- else :
594- yield 'stub does not have argument "{}"' .format (arg )
602+ for arg in sorted (set (runtime .kwonly ) - set (stub .kwonly )):
603+ if arg in set (stub_arg .variable .name for stub_arg in stub .pos ):
604+ # Don't report this if we've reported it before
605+ if len (stub .pos ) > len (runtime .pos ) and runtime .varpos is not None :
606+ yield 'stub argument "{}" is not keyword-only' .format (arg )
607+ else :
608+ yield 'stub does not have argument "{}"' .format (arg )
595609
596610 # Checks involving **kwargs
597611 if stub .varkw is None and runtime .varkw is not None :
598- # There are cases where the stub exhaustively lists out the extra parameters the function
599- # would take through **kwargs, so we don't enforce that the stub takes **kwargs.
612+ # As mentioned above, don't enforce that the stub takes **kwargs.
600613 # Also check against positional parameters, to avoid a nitpicky message when an argument
601614 # isn't marked as keyword-only
602615 stub_pos_names = set (stub_arg .variable .name for stub_arg in stub .pos )
@@ -1016,6 +1029,7 @@ def test_stubs(args: argparse.Namespace) -> int:
10161029 for whitelist_file in args .whitelist
10171030 for entry in get_whitelist_entries (whitelist_file )
10181031 }
1032+ whitelist_regexes = {entry : re .compile (entry ) for entry in whitelist }
10191033
10201034 # If we need to generate a whitelist, we store Error.object_desc for each error here.
10211035 generated_whitelist = set ()
@@ -1024,7 +1038,8 @@ def test_stubs(args: argparse.Namespace) -> int:
10241038 if args .check_typeshed :
10251039 assert not args .modules , "Cannot pass both --check-typeshed and a list of modules"
10261040 modules = get_typeshed_stdlib_modules (args .custom_typeshed_dir )
1027- modules .remove ("antigravity" ) # it's super annoying
1041+ annoying_modules = {"antigravity" , "this" }
1042+ modules = [m for m in modules if m not in annoying_modules ]
10281043
10291044 assert modules , "No modules to check"
10301045
@@ -1048,6 +1063,14 @@ def test_stubs(args: argparse.Namespace) -> int:
10481063 if error .object_desc in whitelist :
10491064 whitelist [error .object_desc ] = True
10501065 continue
1066+ is_whitelisted = False
1067+ for w in whitelist :
1068+ if whitelist_regexes [w ].fullmatch (error .object_desc ):
1069+ whitelist [w ] = True
1070+ is_whitelisted = True
1071+ break
1072+ if is_whitelisted :
1073+ continue
10511074
10521075 # We have errors, so change exit code, and output whatever necessary
10531076 exit_code = 1
@@ -1057,10 +1080,13 @@ def test_stubs(args: argparse.Namespace) -> int:
10571080 print (error .get_description (concise = args .concise ))
10581081
10591082 # Print unused whitelist entries
1060- for w in whitelist :
1061- if not whitelist [w ]:
1062- exit_code = 1
1063- print ("note: unused whitelist entry {}" .format (w ))
1083+ if not args .ignore_unused_whitelist :
1084+ for w in whitelist :
1085+ # Don't consider an entry unused if it regex-matches the empty string
1086+ # This allows us to whitelist errors that don't manifest at all on some systems
1087+ if not whitelist [w ] and not whitelist_regexes [w ].fullmatch ("" ):
1088+ exit_code = 1
1089+ print ("note: unused whitelist entry {}" .format (w ))
10641090
10651091 # Print the generated whitelist
10661092 if args .generate_whitelist :
@@ -1100,14 +1126,20 @@ def parse_options(args: List[str]) -> argparse.Namespace:
11001126 default = [],
11011127 help = (
11021128 "Use file as a whitelist. Can be passed multiple times to combine multiple "
1103- "whitelists. Whitelist can be created with --generate-whitelist"
1129+ "whitelists. Whitelists can be created with --generate-whitelist"
11041130 ),
11051131 )
11061132 parser .add_argument (
11071133 "--generate-whitelist" ,
11081134 action = "store_true" ,
11091135 help = "Print a whitelist (to stdout) to be used with --whitelist" ,
11101136 )
1137+ parser .add_argument (
1138+ "--ignore-unused-whitelist" ,
1139+ action = "store_true" ,
1140+ help = "Ignore unused whitelist entries" ,
1141+ )
1142+
11111143 return parser .parse_args (args )
11121144
11131145
0 commit comments