Skip to content
53 changes: 49 additions & 4 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def __init__(
self.stub_desc = stub_desc or str(getattr(stub_object, "type", stub_object))

if runtime_desc is None:
runtime_sig = safe_inspect_signature(runtime_object)
runtime_sig = safe_inspect_signature(runtime_object, object_path)
if runtime_sig is None:
self.runtime_desc = _truncate(repr(runtime_object), 100)
else:
Expand Down Expand Up @@ -1036,7 +1036,7 @@ def verify_funcitem(
for message in _verify_static_class_methods(stub, runtime, static_runtime, object_path):
yield Error(object_path, "is inconsistent, " + message, stub, runtime)

signature = safe_inspect_signature(runtime)
signature = safe_inspect_signature(runtime, object_path)
runtime_is_coroutine = inspect.iscoroutinefunction(runtime)

if signature:
Expand Down Expand Up @@ -1164,7 +1164,7 @@ def verify_overloadedfuncdef(
# TODO: Should call _verify_final_method here,
# but overloaded final methods in stubs cause a stubtest crash: see #14950

signature = safe_inspect_signature(runtime)
signature = safe_inspect_signature(runtime, object_path)
if not signature:
return

Expand Down Expand Up @@ -1526,7 +1526,52 @@ def is_read_only_property(runtime: object) -> bool:
return isinstance(runtime, property) and runtime.fset is None


def safe_inspect_signature(runtime: Any) -> inspect.Signature | None:
def safe_inspect_signature(
runtime: Any, object_path: list[str] | None = None
) -> inspect.Signature | None:
if (
hasattr(runtime, "__name__")
and runtime.__name__ == "__init__"
and hasattr(runtime, "__text_signature__")
and runtime.__text_signature__ == "($self, /, *args, **kwargs)"
and hasattr(runtime, "__objclass__")
and hasattr(runtime.__objclass__, "__text_signature__")
and runtime.__objclass__.__text_signature__ is not None
):
# This is an __init__ method with the generic C-class signature.
# In this case, the underlying class usually has a better signature,
# which we can convert into an __init__ signature by adding in the
# self parameter.
runtime_objclass = None
if runtime.__objclass__ is object:
# When __objclass__ is object, use the object_path to look up
# the actual class that this __init__ method came from.
if object_path:
module_name = ".".join(object_path[:-2])
module = silent_import_module(module_name)
if module:
runtime_objclass = getattr(module, object_path[-2], None)
if not (
runtime_objclass is not None
and hasattr(runtime_objclass, "__text_signature__")
and runtime_objclass.__text_signature__ is not None
):
runtime_objclass = None
else:
runtime_objclass = runtime.__objclass__

if runtime_objclass is not None:
try:
s = inspect.signature(runtime_objclass)
return s.replace(
parameters=[
inspect.Parameter("self", inspect.Parameter.POSITIONAL_OR_KEYWORD),
*s.parameters.values(),
]
)
except Exception:
pass

try:
try:
return inspect.signature(runtime)
Expand Down
Loading