@@ -244,11 +244,13 @@ def verify_typeinfo(
244
244
yield Error (object_path , "is not a type" , stub , runtime , stub_desc = repr (stub ))
245
245
return
246
246
247
+ # Check everything already defined in the stub
247
248
to_check = set (stub .names )
248
- dunders_to_check = ( "__init__" , "__new__" , "__call__" , "__class_getitem__" )
249
- # cast to workaround mypyc complaints
249
+ # There's a reasonable case to be made that we should always check all dunders, but it's
250
+ # currently quite noisy. We could turn this into a denylist instead of an allowlist.
250
251
to_check .update (
251
- m for m in cast (Any , vars )(runtime ) if m in dunders_to_check or not m .startswith ("_" )
252
+ # cast to workaround mypyc complaints
253
+ m for m in cast (Any , vars )(runtime ) if not m .startswith ("_" ) or m in SPECIAL_DUNDERS
252
254
)
253
255
254
256
for entry in sorted (to_check ):
@@ -265,8 +267,8 @@ def verify_typeinfo(
265
267
def _verify_static_class_methods (
266
268
stub : nodes .FuncItem , runtime : types .FunctionType , object_path : List [str ]
267
269
) -> Iterator [str ]:
268
- if stub .name == "__new__" :
269
- # Special cased by Python, so never declared as staticmethod
270
+ if stub .name in ( "__new__" , "__init_subclass__" , "__class_getitem__" ) :
271
+ # Special cased by Python, so don't bother checking
270
272
return
271
273
if inspect .isbuiltin (runtime ):
272
274
# The isinstance checks don't work reliably for builtins, e.g. datetime.datetime.now, so do
@@ -303,8 +305,8 @@ def _verify_arg_name(
303
305
stub_arg : nodes .Argument , runtime_arg : inspect .Parameter , function_name : str
304
306
) -> Iterator [str ]:
305
307
"""Checks whether argument names match."""
306
- # Ignore exact names for all dunder methods other than __init__
307
- if is_dunder (function_name , exclude_init = True ):
308
+ # Ignore exact names for most dunder methods
309
+ if is_dunder (function_name , exclude_special = True ):
308
310
return
309
311
310
312
def strip_prefix (s : str , prefix : str ) -> str :
@@ -468,8 +470,8 @@ def from_overloadedfuncdef(stub: nodes.OverloadedFuncDef) -> "Signature[nodes.Ar
468
470
lies it might try to tell.
469
471
470
472
"""
471
- # For all dunder methods other than __init__ , just assume all args are positional-only
472
- assume_positional_only = is_dunder (stub .name , exclude_init = True )
473
+ # For most dunder methods, just assume all args are positional-only
474
+ assume_positional_only = is_dunder (stub .name , exclude_special = True )
473
475
474
476
all_args = {} # type: Dict[str, List[Tuple[nodes.Argument, int]]]
475
477
for func in map (_resolve_funcitem_from_decorator , stub .items ):
@@ -548,7 +550,7 @@ def _verify_signature(
548
550
runtime_arg .kind == inspect .Parameter .POSITIONAL_ONLY
549
551
and not stub_arg .variable .name .startswith ("__" )
550
552
and not stub_arg .variable .name .strip ("_" ) == "self"
551
- and not is_dunder (function_name ) # noisy for dunder methods
553
+ and not is_dunder (function_name , exclude_special = True ) # noisy for dunder methods
552
554
):
553
555
yield (
554
556
'stub argument "{}" should be positional-only '
@@ -656,6 +658,13 @@ def verify_funcitem(
656
658
# catch RuntimeError because of https://bugs.python.org/issue39504
657
659
return
658
660
661
+ if stub .name in ("__init_subclass__" , "__class_getitem__" ):
662
+ # These are implicitly classmethods. If the stub chooses not to have @classmethod, we
663
+ # should remove the cls argument
664
+ if stub .arguments [0 ].variable .name == "cls" :
665
+ stub = copy .copy (stub )
666
+ stub .arguments = stub .arguments [1 :]
667
+
659
668
stub_sig = Signature .from_funcitem (stub )
660
669
runtime_sig = Signature .from_inspect_signature (signature )
661
670
@@ -846,13 +855,16 @@ def verify_typealias(
846
855
yield None
847
856
848
857
849
- def is_dunder (name : str , exclude_init : bool = False ) -> bool :
858
+ SPECIAL_DUNDERS = ("__init__" , "__new__" , "__call__" , "__init_subclass__" , "__class_getitem__" )
859
+
860
+
861
+ def is_dunder (name : str , exclude_special : bool = False ) -> bool :
850
862
"""Returns whether name is a dunder name.
851
863
852
- :param exclude_init : Whether to return False for __init__
864
+ :param exclude_special : Whether to return False for a couple special dunder methods.
853
865
854
866
"""
855
- if exclude_init and name == "__init__" :
867
+ if exclude_special and name in SPECIAL_DUNDERS :
856
868
return False
857
869
return name .startswith ("__" ) and name .endswith ("__" )
858
870
0 commit comments