@@ -506,28 +506,57 @@ def _is_disjoint_base(typ: type[object]) -> bool:
506
506
def _verify_disjoint_base (
507
507
stub : nodes .TypeInfo , runtime : type [object ], object_path : list [str ]
508
508
) -> Iterator [Error ]:
509
- # If it's final, doesn't matter whether it's a disjoint base or not
510
- if stub .is_final :
511
- return
512
509
is_disjoint_runtime = _is_disjoint_base (runtime )
513
510
# Don't complain about missing @disjoint_base if there are __slots__, because
514
511
# in that case we can infer that it's a disjoint base.
515
- if is_disjoint_runtime and not stub .is_disjoint_base and not runtime .__dict__ .get ("__slots__" ):
512
+ if (
513
+ is_disjoint_runtime
514
+ and not stub .is_disjoint_base
515
+ and not runtime .__dict__ .get ("__slots__" )
516
+ and not stub .is_final
517
+ and not (stub .is_enum and stub .enum_members )
518
+ ):
516
519
yield Error (
517
520
object_path ,
518
521
"is a disjoint base at runtime, but isn't marked with @disjoint_base in the stub" ,
519
522
stub ,
520
523
runtime ,
521
524
stub_desc = repr (stub ),
522
525
)
523
- elif not is_disjoint_runtime and stub .is_disjoint_base :
524
- yield Error (
525
- object_path ,
526
- "is marked with @disjoint_base in the stub, but isn't a disjoint base at runtime" ,
527
- stub ,
528
- runtime ,
529
- stub_desc = repr (stub ),
530
- )
526
+ elif stub .is_disjoint_base :
527
+ if not is_disjoint_runtime :
528
+ yield Error (
529
+ object_path ,
530
+ "is marked with @disjoint_base in the stub, but isn't a disjoint base at runtime" ,
531
+ stub ,
532
+ runtime ,
533
+ stub_desc = repr (stub ),
534
+ )
535
+ if runtime .__dict__ .get ("__slots__" ):
536
+ yield Error (
537
+ object_path ,
538
+ "is marked as @disjoint_base, but also has slots; add __slots__ instead" ,
539
+ stub ,
540
+ runtime ,
541
+ stub_desc = repr (stub ),
542
+ )
543
+ elif stub .is_final :
544
+ yield Error (
545
+ object_path ,
546
+ "is marked as @disjoint_base, but also marked as @final; remove @disjoint_base" ,
547
+ stub ,
548
+ runtime ,
549
+ stub_desc = repr (stub ),
550
+ )
551
+ elif stub .is_enum and stub .enum_members :
552
+ yield Error (
553
+ object_path ,
554
+ "is marked as @disjoint_base, but is an enum with members, which is implicitly final; "
555
+ "remove @disjoint_base" ,
556
+ stub ,
557
+ runtime ,
558
+ stub_desc = repr (stub ),
559
+ )
531
560
532
561
533
562
def _verify_metaclass (
@@ -747,8 +776,8 @@ def names_approx_match(a: str, b: str) -> bool:
747
776
if stub_arg .variable .name == "_self" :
748
777
return
749
778
yield (
750
- f'stub argument "{ stub_arg .variable .name } " '
751
- f'differs from runtime argument "{ runtime_arg .name } "'
779
+ f'stub parameter "{ stub_arg .variable .name } " '
780
+ f'differs from runtime parameter "{ runtime_arg .name } "'
752
781
)
753
782
754
783
@@ -759,8 +788,8 @@ def _verify_arg_default_value(
759
788
if runtime_arg .default is not inspect .Parameter .empty :
760
789
if stub_arg .kind .is_required ():
761
790
yield (
762
- f'runtime argument "{ runtime_arg .name } " '
763
- "has a default value but stub argument does not"
791
+ f'runtime parameter "{ runtime_arg .name } " '
792
+ "has a default value but stub parameter does not"
764
793
)
765
794
else :
766
795
runtime_type = get_mypy_type_of_runtime_value (runtime_arg .default )
@@ -781,9 +810,9 @@ def _verify_arg_default_value(
781
810
and not is_subtype_helper (runtime_type , stub_type )
782
811
):
783
812
yield (
784
- f'runtime argument "{ runtime_arg .name } " '
813
+ f'runtime parameter "{ runtime_arg .name } " '
785
814
f"has a default value of type { runtime_type } , "
786
- f"which is incompatible with stub argument type { stub_type } "
815
+ f"which is incompatible with stub parameter type { stub_type } "
787
816
)
788
817
if stub_arg .initializer is not None :
789
818
stub_default = evaluate_expression (stub_arg .initializer )
@@ -807,15 +836,15 @@ def _verify_arg_default_value(
807
836
defaults_match = False
808
837
if not defaults_match :
809
838
yield (
810
- f'runtime argument "{ runtime_arg .name } " '
839
+ f'runtime parameter "{ runtime_arg .name } " '
811
840
f"has a default value of { runtime_arg .default !r} , "
812
- f"which is different from stub argument default { stub_default !r} "
841
+ f"which is different from stub parameter default { stub_default !r} "
813
842
)
814
843
else :
815
844
if stub_arg .kind .is_optional ():
816
845
yield (
817
- f'stub argument "{ stub_arg .variable .name } " has a default value '
818
- f"but runtime argument does not"
846
+ f'stub parameter "{ stub_arg .variable .name } " has a default value '
847
+ f"but runtime parameter does not"
819
848
)
820
849
821
850
@@ -925,21 +954,36 @@ def from_overloadedfuncdef(stub: nodes.OverloadedFuncDef) -> Signature[nodes.Arg
925
954
# For most dunder methods, just assume all args are positional-only
926
955
assume_positional_only = is_dunder (stub .name , exclude_special = True )
927
956
928
- all_args : dict [str , list [ tuple [ nodes . Argument , int ]]] = {}
957
+ is_arg_pos_only : defaultdict [str , set [ bool ]] = defaultdict ( set )
929
958
for func in map (_resolve_funcitem_from_decorator , stub .items ):
930
959
assert func is not None , "Failed to resolve decorated overload"
931
960
args = maybe_strip_cls (stub .name , func .arguments )
932
961
for index , arg in enumerate (args ):
933
- # For positional-only args, we allow overloads to have different names for the same
934
- # argument. To accomplish this, we just make up a fake index-based name.
935
- name = (
936
- f"__{ index } "
937
- if arg .variable .name .startswith ("__" )
962
+ if (
963
+ arg .variable .name .startswith ("__" )
938
964
or arg .pos_only
939
965
or assume_positional_only
940
966
or arg .variable .name .strip ("_" ) == "self"
941
- else arg .variable .name
942
- )
967
+ or (index == 0 and arg .variable .name .strip ("_" ) == "cls" )
968
+ ):
969
+ is_arg_pos_only [arg .variable .name ].add (True )
970
+ else :
971
+ is_arg_pos_only [arg .variable .name ].add (False )
972
+
973
+ all_args : dict [str , list [tuple [nodes .Argument , int ]]] = {}
974
+ for func in map (_resolve_funcitem_from_decorator , stub .items ):
975
+ assert func is not None , "Failed to resolve decorated overload"
976
+ args = maybe_strip_cls (stub .name , func .arguments )
977
+ for index , arg in enumerate (args ):
978
+ # For positional-only args, we allow overloads to have different names for the same
979
+ # argument. To accomplish this, we just make up a fake index-based name.
980
+ # We can only use the index-based name if the argument is always
981
+ # positional only. Sometimes overloads have an arg as positional-only
982
+ # in some but not all branches of the overload.
983
+ name = arg .variable .name
984
+ if is_arg_pos_only [name ] == {True }:
985
+ name = f"__{ index } "
986
+
943
987
all_args .setdefault (name , []).append ((arg , index ))
944
988
945
989
def get_position (arg_name : str ) -> int :
@@ -1008,21 +1052,23 @@ def _verify_signature(
1008
1052
and not stub_arg .pos_only
1009
1053
and not stub_arg .variable .name .startswith ("__" )
1010
1054
and stub_arg .variable .name .strip ("_" ) != "self"
1055
+ and stub_arg .variable .name .strip ("_" ) != "cls"
1011
1056
and not is_dunder (function_name , exclude_special = True ) # noisy for dunder methods
1012
1057
):
1013
1058
yield (
1014
- f'stub argument "{ stub_arg .variable .name } " should be positional-only '
1059
+ f'stub parameter "{ stub_arg .variable .name } " should be positional-only '
1015
1060
f'(add "/", e.g. "{ runtime_arg .name } , /")'
1016
1061
)
1017
1062
if (
1018
1063
runtime_arg .kind != inspect .Parameter .POSITIONAL_ONLY
1019
1064
and (stub_arg .pos_only or stub_arg .variable .name .startswith ("__" ))
1020
1065
and not runtime_arg .name .startswith ("__" )
1021
1066
and stub_arg .variable .name .strip ("_" ) != "self"
1067
+ and stub_arg .variable .name .strip ("_" ) != "cls"
1022
1068
and not is_dunder (function_name , exclude_special = True ) # noisy for dunder methods
1023
1069
):
1024
1070
yield (
1025
- f'stub argument "{ stub_arg .variable .name } " should be positional or keyword '
1071
+ f'stub parameter "{ stub_arg .variable .name } " should be positional or keyword '
1026
1072
'(remove "/")'
1027
1073
)
1028
1074
@@ -1037,28 +1083,28 @@ def _verify_signature(
1037
1083
# If the variable is in runtime.kwonly, it's just mislabelled as not a
1038
1084
# keyword-only argument
1039
1085
if stub_arg .variable .name not in runtime .kwonly :
1040
- msg = f'runtime does not have argument "{ stub_arg .variable .name } "'
1086
+ msg = f'runtime does not have parameter "{ stub_arg .variable .name } "'
1041
1087
if runtime .varkw is not None :
1042
1088
msg += ". Maybe you forgot to make it keyword-only in the stub?"
1043
1089
yield msg
1044
1090
else :
1045
- yield f'stub argument "{ stub_arg .variable .name } " is not keyword-only'
1091
+ yield f'stub parameter "{ stub_arg .variable .name } " is not keyword-only'
1046
1092
if stub .varpos is not None :
1047
- yield f'runtime does not have *args argument "{ stub .varpos .variable .name } "'
1093
+ yield f'runtime does not have *args parameter "{ stub .varpos .variable .name } "'
1048
1094
elif len (stub .pos ) < len (runtime .pos ):
1049
1095
for runtime_arg in runtime .pos [len (stub .pos ) :]:
1050
1096
if runtime_arg .name not in stub .kwonly :
1051
1097
if not _is_private_parameter (runtime_arg ):
1052
- yield f'stub does not have argument "{ runtime_arg .name } "'
1098
+ yield f'stub does not have parameter "{ runtime_arg .name } "'
1053
1099
else :
1054
- yield f'runtime argument "{ runtime_arg .name } " is not keyword-only'
1100
+ yield f'runtime parameter "{ runtime_arg .name } " is not keyword-only'
1055
1101
1056
1102
# Checks involving *args
1057
1103
if len (stub .pos ) <= len (runtime .pos ) or runtime .varpos is None :
1058
1104
if stub .varpos is None and runtime .varpos is not None :
1059
- yield f'stub does not have *args argument "{ runtime .varpos .name } "'
1105
+ yield f'stub does not have *args parameter "{ runtime .varpos .name } "'
1060
1106
if stub .varpos is not None and runtime .varpos is None :
1061
- yield f'runtime does not have *args argument "{ stub .varpos .variable .name } "'
1107
+ yield f'runtime does not have *args parameter "{ stub .varpos .variable .name } "'
1062
1108
1063
1109
# Check keyword-only args
1064
1110
for arg in sorted (set (stub .kwonly ) & set (runtime .kwonly )):
@@ -1077,20 +1123,20 @@ def _verify_signature(
1077
1123
if arg in {runtime_arg .name for runtime_arg in runtime .pos }:
1078
1124
# Don't report this if we've reported it before
1079
1125
if arg not in {runtime_arg .name for runtime_arg in runtime .pos [len (stub .pos ) :]}:
1080
- yield f'runtime argument "{ arg } " is not keyword-only'
1126
+ yield f'runtime parameter "{ arg } " is not keyword-only'
1081
1127
else :
1082
- yield f'runtime does not have argument "{ arg } "'
1128
+ yield f'runtime does not have parameter "{ arg } "'
1083
1129
for arg in sorted (set (runtime .kwonly ) - set (stub .kwonly )):
1084
1130
if arg in {stub_arg .variable .name for stub_arg in stub .pos }:
1085
1131
# Don't report this if we've reported it before
1086
1132
if not (
1087
1133
runtime .varpos is None
1088
1134
and arg in {stub_arg .variable .name for stub_arg in stub .pos [len (runtime .pos ) :]}
1089
1135
):
1090
- yield f'stub argument "{ arg } " is not keyword-only'
1136
+ yield f'stub parameter "{ arg } " is not keyword-only'
1091
1137
else :
1092
1138
if not _is_private_parameter (runtime .kwonly [arg ]):
1093
- yield f'stub does not have argument "{ arg } "'
1139
+ yield f'stub does not have parameter "{ arg } "'
1094
1140
1095
1141
# Checks involving **kwargs
1096
1142
if stub .varkw is None and runtime .varkw is not None :
@@ -1100,9 +1146,9 @@ def _verify_signature(
1100
1146
stub_pos_names = {stub_arg .variable .name for stub_arg in stub .pos }
1101
1147
# Ideally we'd do a strict subset check, but in practice the errors from that aren't useful
1102
1148
if not set (runtime .kwonly ).issubset (set (stub .kwonly ) | stub_pos_names ):
1103
- yield f'stub does not have **kwargs argument "{ runtime .varkw .name } "'
1149
+ yield f'stub does not have **kwargs parameter "{ runtime .varkw .name } "'
1104
1150
if stub .varkw is not None and runtime .varkw is None :
1105
- yield f'runtime does not have **kwargs argument "{ stub .varkw .variable .name } "'
1151
+ yield f'runtime does not have **kwargs parameter "{ stub .varkw .variable .name } "'
1106
1152
1107
1153
1108
1154
def _is_private_parameter (arg : inspect .Parameter ) -> bool :
@@ -1422,7 +1468,7 @@ def apply_decorator_to_funcitem(
1422
1468
if decorator .fullname == "builtins.classmethod" :
1423
1469
if func .arguments [0 ].variable .name not in ("cls" , "mcs" , "metacls" ):
1424
1470
raise StubtestFailure (
1425
- f"unexpected class argument name { func .arguments [0 ].variable .name !r} "
1471
+ f"unexpected class parameter name { func .arguments [0 ].variable .name !r} "
1426
1472
f"in { dec .fullname } "
1427
1473
)
1428
1474
# FuncItem is written so that copy.copy() actually works, even when compiled
@@ -1662,6 +1708,71 @@ def is_read_only_property(runtime: object) -> bool:
1662
1708
1663
1709
1664
1710
def safe_inspect_signature (runtime : Any ) -> inspect .Signature | None :
1711
+ if (
1712
+ hasattr (runtime , "__name__" )
1713
+ and runtime .__name__ == "__init__"
1714
+ and hasattr (runtime , "__text_signature__" )
1715
+ and runtime .__text_signature__ == "($self, /, *args, **kwargs)"
1716
+ and hasattr (runtime , "__objclass__" )
1717
+ and hasattr (runtime .__objclass__ , "__text_signature__" )
1718
+ and runtime .__objclass__ .__text_signature__ is not None
1719
+ ):
1720
+ # This is an __init__ method with the generic C-class signature.
1721
+ # In this case, the underlying class often has a better signature,
1722
+ # which we can convert into an __init__ signature by adding in the
1723
+ # self parameter.
1724
+ try :
1725
+ s = inspect .signature (runtime .__objclass__ )
1726
+
1727
+ parameter_kind : inspect ._ParameterKind = inspect .Parameter .POSITIONAL_OR_KEYWORD
1728
+ if s .parameters :
1729
+ first_parameter = next (iter (s .parameters .values ()))
1730
+ if first_parameter .kind == inspect .Parameter .POSITIONAL_ONLY :
1731
+ parameter_kind = inspect .Parameter .POSITIONAL_ONLY
1732
+ return s .replace (
1733
+ parameters = [inspect .Parameter ("self" , parameter_kind ), * s .parameters .values ()]
1734
+ )
1735
+ except Exception :
1736
+ pass
1737
+
1738
+ if (
1739
+ hasattr (runtime , "__name__" )
1740
+ and runtime .__name__ == "__new__"
1741
+ and hasattr (runtime , "__text_signature__" )
1742
+ and runtime .__text_signature__ == "($type, *args, **kwargs)"
1743
+ and hasattr (runtime , "__self__" )
1744
+ and hasattr (runtime .__self__ , "__text_signature__" )
1745
+ and runtime .__self__ .__text_signature__ is not None
1746
+ ):
1747
+ # This is a __new__ method with the generic C-class signature.
1748
+ # In this case, the underlying class often has a better signature,
1749
+ # which we can convert into a __new__ signature by adding in the
1750
+ # cls parameter.
1751
+
1752
+ # If the attached class has a valid __init__, skip recovering a
1753
+ # signature for this __new__ method.
1754
+ has_init = False
1755
+ if (
1756
+ hasattr (runtime .__self__ , "__init__" )
1757
+ and hasattr (runtime .__self__ .__init__ , "__objclass__" )
1758
+ and runtime .__self__ .__init__ .__objclass__ is runtime .__self__
1759
+ ):
1760
+ has_init = True
1761
+
1762
+ if not has_init :
1763
+ try :
1764
+ s = inspect .signature (runtime .__self__ )
1765
+ parameter_kind = inspect .Parameter .POSITIONAL_OR_KEYWORD
1766
+ if s .parameters :
1767
+ first_parameter = next (iter (s .parameters .values ()))
1768
+ if first_parameter .kind == inspect .Parameter .POSITIONAL_ONLY :
1769
+ parameter_kind = inspect .Parameter .POSITIONAL_ONLY
1770
+ return s .replace (
1771
+ parameters = [inspect .Parameter ("cls" , parameter_kind ), * s .parameters .values ()]
1772
+ )
1773
+ except Exception :
1774
+ pass
1775
+
1665
1776
try :
1666
1777
try :
1667
1778
return inspect .signature (runtime )
0 commit comments