@@ -506,28 +506,57 @@ def _is_disjoint_base(typ: type[object]) -> bool:
506506def _verify_disjoint_base (
507507 stub : nodes .TypeInfo , runtime : type [object ], object_path : list [str ]
508508) -> 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
512509 is_disjoint_runtime = _is_disjoint_base (runtime )
513510 # Don't complain about missing @disjoint_base if there are __slots__, because
514511 # 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+ ):
516519 yield Error (
517520 object_path ,
518521 "is a disjoint base at runtime, but isn't marked with @disjoint_base in the stub" ,
519522 stub ,
520523 runtime ,
521524 stub_desc = repr (stub ),
522525 )
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+ )
531560
532561
533562def _verify_metaclass (
@@ -747,8 +776,8 @@ def names_approx_match(a: str, b: str) -> bool:
747776 if stub_arg .variable .name == "_self" :
748777 return
749778 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 } "'
752781 )
753782
754783
@@ -759,8 +788,8 @@ def _verify_arg_default_value(
759788 if runtime_arg .default is not inspect .Parameter .empty :
760789 if stub_arg .kind .is_required ():
761790 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"
764793 )
765794 else :
766795 runtime_type = get_mypy_type_of_runtime_value (runtime_arg .default )
@@ -781,9 +810,9 @@ def _verify_arg_default_value(
781810 and not is_subtype_helper (runtime_type , stub_type )
782811 ):
783812 yield (
784- f'runtime argument "{ runtime_arg .name } " '
813+ f'runtime parameter "{ runtime_arg .name } " '
785814 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 } "
787816 )
788817 if stub_arg .initializer is not None :
789818 stub_default = evaluate_expression (stub_arg .initializer )
@@ -807,15 +836,15 @@ def _verify_arg_default_value(
807836 defaults_match = False
808837 if not defaults_match :
809838 yield (
810- f'runtime argument "{ runtime_arg .name } " '
839+ f'runtime parameter "{ runtime_arg .name } " '
811840 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} "
813842 )
814843 else :
815844 if stub_arg .kind .is_optional ():
816845 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"
819848 )
820849
821850
@@ -925,21 +954,36 @@ def from_overloadedfuncdef(stub: nodes.OverloadedFuncDef) -> Signature[nodes.Arg
925954 # For most dunder methods, just assume all args are positional-only
926955 assume_positional_only = is_dunder (stub .name , exclude_special = True )
927956
928- all_args : dict [str , list [ tuple [ nodes . Argument , int ]]] = {}
957+ is_arg_pos_only : defaultdict [str , set [ bool ]] = defaultdict ( set )
929958 for func in map (_resolve_funcitem_from_decorator , stub .items ):
930959 assert func is not None , "Failed to resolve decorated overload"
931960 args = maybe_strip_cls (stub .name , func .arguments )
932961 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 ("__" )
938964 or arg .pos_only
939965 or assume_positional_only
940966 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+
943987 all_args .setdefault (name , []).append ((arg , index ))
944988
945989 def get_position (arg_name : str ) -> int :
@@ -1008,21 +1052,23 @@ def _verify_signature(
10081052 and not stub_arg .pos_only
10091053 and not stub_arg .variable .name .startswith ("__" )
10101054 and stub_arg .variable .name .strip ("_" ) != "self"
1055+ and stub_arg .variable .name .strip ("_" ) != "cls"
10111056 and not is_dunder (function_name , exclude_special = True ) # noisy for dunder methods
10121057 ):
10131058 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 '
10151060 f'(add "/", e.g. "{ runtime_arg .name } , /")'
10161061 )
10171062 if (
10181063 runtime_arg .kind != inspect .Parameter .POSITIONAL_ONLY
10191064 and (stub_arg .pos_only or stub_arg .variable .name .startswith ("__" ))
10201065 and not runtime_arg .name .startswith ("__" )
10211066 and stub_arg .variable .name .strip ("_" ) != "self"
1067+ and stub_arg .variable .name .strip ("_" ) != "cls"
10221068 and not is_dunder (function_name , exclude_special = True ) # noisy for dunder methods
10231069 ):
10241070 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 '
10261072 '(remove "/")'
10271073 )
10281074
@@ -1037,28 +1083,28 @@ def _verify_signature(
10371083 # If the variable is in runtime.kwonly, it's just mislabelled as not a
10381084 # keyword-only argument
10391085 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 } "'
10411087 if runtime .varkw is not None :
10421088 msg += ". Maybe you forgot to make it keyword-only in the stub?"
10431089 yield msg
10441090 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'
10461092 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 } "'
10481094 elif len (stub .pos ) < len (runtime .pos ):
10491095 for runtime_arg in runtime .pos [len (stub .pos ) :]:
10501096 if runtime_arg .name not in stub .kwonly :
10511097 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 } "'
10531099 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'
10551101
10561102 # Checks involving *args
10571103 if len (stub .pos ) <= len (runtime .pos ) or runtime .varpos is None :
10581104 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 } "'
10601106 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 } "'
10621108
10631109 # Check keyword-only args
10641110 for arg in sorted (set (stub .kwonly ) & set (runtime .kwonly )):
@@ -1077,20 +1123,20 @@ def _verify_signature(
10771123 if arg in {runtime_arg .name for runtime_arg in runtime .pos }:
10781124 # Don't report this if we've reported it before
10791125 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'
10811127 else :
1082- yield f'runtime does not have argument "{ arg } "'
1128+ yield f'runtime does not have parameter "{ arg } "'
10831129 for arg in sorted (set (runtime .kwonly ) - set (stub .kwonly )):
10841130 if arg in {stub_arg .variable .name for stub_arg in stub .pos }:
10851131 # Don't report this if we've reported it before
10861132 if not (
10871133 runtime .varpos is None
10881134 and arg in {stub_arg .variable .name for stub_arg in stub .pos [len (runtime .pos ) :]}
10891135 ):
1090- yield f'stub argument "{ arg } " is not keyword-only'
1136+ yield f'stub parameter "{ arg } " is not keyword-only'
10911137 else :
10921138 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 } "'
10941140
10951141 # Checks involving **kwargs
10961142 if stub .varkw is None and runtime .varkw is not None :
@@ -1100,9 +1146,9 @@ def _verify_signature(
11001146 stub_pos_names = {stub_arg .variable .name for stub_arg in stub .pos }
11011147 # Ideally we'd do a strict subset check, but in practice the errors from that aren't useful
11021148 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 } "'
11041150 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 } "'
11061152
11071153
11081154def _is_private_parameter (arg : inspect .Parameter ) -> bool :
@@ -1422,7 +1468,7 @@ def apply_decorator_to_funcitem(
14221468 if decorator .fullname == "builtins.classmethod" :
14231469 if func .arguments [0 ].variable .name not in ("cls" , "mcs" , "metacls" ):
14241470 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} "
14261472 f"in { dec .fullname } "
14271473 )
14281474 # FuncItem is written so that copy.copy() actually works, even when compiled
@@ -1662,6 +1708,71 @@ def is_read_only_property(runtime: object) -> bool:
16621708
16631709
16641710def 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+
16651776 try :
16661777 try :
16671778 return inspect .signature (runtime )
0 commit comments