9
9
import enum
10
10
import importlib
11
11
import inspect
12
+ import re
12
13
import sys
13
14
import types
14
15
import warnings
@@ -240,9 +241,12 @@ def verify_typeinfo(
240
241
to_check .update (m for m in cast (Any , vars )(runtime ) if not m .startswith ("_" ))
241
242
242
243
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 )
243
247
yield from verify (
244
248
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 ),
246
250
object_path + [entry ],
247
251
)
248
252
@@ -266,7 +270,13 @@ def _verify_static_class_methods(
266
270
# Look the object up statically, to avoid binding by the descriptor protocol
267
271
static_runtime = importlib .import_module (object_path [0 ])
268
272
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
270
280
271
281
if isinstance (static_runtime , classmethod ) and not stub .is_class :
272
282
yield "runtime is a classmethod but stub is not"
@@ -582,21 +592,24 @@ def _verify_signature(
582
592
583
593
# Check unmatched keyword-only args
584
594
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.
585
600
for arg in sorted (set (stub .kwonly ) - set (runtime .kwonly )):
586
601
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 )
595
609
596
610
# Checks involving **kwargs
597
611
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.
600
613
# Also check against positional parameters, to avoid a nitpicky message when an argument
601
614
# isn't marked as keyword-only
602
615
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:
1016
1029
for whitelist_file in args .whitelist
1017
1030
for entry in get_whitelist_entries (whitelist_file )
1018
1031
}
1032
+ whitelist_regexes = {entry : re .compile (entry ) for entry in whitelist }
1019
1033
1020
1034
# If we need to generate a whitelist, we store Error.object_desc for each error here.
1021
1035
generated_whitelist = set ()
@@ -1024,7 +1038,8 @@ def test_stubs(args: argparse.Namespace) -> int:
1024
1038
if args .check_typeshed :
1025
1039
assert not args .modules , "Cannot pass both --check-typeshed and a list of modules"
1026
1040
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 ]
1028
1043
1029
1044
assert modules , "No modules to check"
1030
1045
@@ -1048,6 +1063,14 @@ def test_stubs(args: argparse.Namespace) -> int:
1048
1063
if error .object_desc in whitelist :
1049
1064
whitelist [error .object_desc ] = True
1050
1065
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
1051
1074
1052
1075
# We have errors, so change exit code, and output whatever necessary
1053
1076
exit_code = 1
@@ -1057,10 +1080,13 @@ def test_stubs(args: argparse.Namespace) -> int:
1057
1080
print (error .get_description (concise = args .concise ))
1058
1081
1059
1082
# 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 ))
1064
1090
1065
1091
# Print the generated whitelist
1066
1092
if args .generate_whitelist :
@@ -1100,14 +1126,20 @@ def parse_options(args: List[str]) -> argparse.Namespace:
1100
1126
default = [],
1101
1127
help = (
1102
1128
"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"
1104
1130
),
1105
1131
)
1106
1132
parser .add_argument (
1107
1133
"--generate-whitelist" ,
1108
1134
action = "store_true" ,
1109
1135
help = "Print a whitelist (to stdout) to be used with --whitelist" ,
1110
1136
)
1137
+ parser .add_argument (
1138
+ "--ignore-unused-whitelist" ,
1139
+ action = "store_true" ,
1140
+ help = "Ignore unused whitelist entries" ,
1141
+ )
1142
+
1111
1143
return parser .parse_args (args )
1112
1144
1113
1145
0 commit comments