diff --git a/introspect.py b/introspect.py index 5abdc97..f682daf 100644 --- a/introspect.py +++ b/introspect.py @@ -944,6 +944,26 @@ def _widen_mathutils_params(params: list[ParamData]) -> None: param["type"] = f"{ptype} | Sequence[float]" +def _annotation_to_type_str(ann: object) -> str: + """Convert a Python annotation object to a clean type string for stubs.""" + s = ann if isinstance(ann, str) else str(ann) + # Clean up internal module references + s = s.replace("_bpy_types.", "bpy.types.") + # typing.Union[X, Y] -> X | Y + s = re.sub(r"\bUnion\[([^\]]+)\]", lambda m: " | ".join(m.group(1).split(", ")), s) + # typing.Optional[X] -> X | None + s = re.sub(r"\bOptional\[([^\]]+)\]", r"\1 | None", s) + # -> int + s = re.sub(r"", r"\1", s) + s = s.replace("typing.", "") + # NoneType -> None + s = re.sub(r"\bNoneType\b", "None", s) + # Qualify bare mathutils types (avoid double-qualifying already-qualified ones) + for mt in ("Vector", "Matrix", "Euler", "Quaternion", "Color"): + s = re.sub(rf"(? FunctionData | None: """Introspect a callable (function or builtin) and return its metadata.""" docstring = inspect.getdoc(func) or "" @@ -1032,6 +1052,10 @@ def introspect_callable(func: Callable[..., object], name: str) -> FunctionData type_str = doc_type actual_name = doc_name + # Fall back to signature annotations (Python functions with type hints) + if type_str is None and param.annotation is not inspect.Parameter.empty: + type_str = _annotation_to_type_str(param.annotation) + if default == "None" and type_str and not re.search(r"\| None\b", type_str): type_str = type_str + " | None" @@ -1044,6 +1068,10 @@ def introspect_callable(func: Callable[..., object], name: str) -> FunctionData } ) + # Fall back to signature return annotation + if return_type is None and sig.return_annotation is not inspect.Signature.empty: + return_type = _annotation_to_type_str(sig.return_annotation) + _widen_mathutils_params(params) return { "name": name,