bindgen: Fix dropped constructors and out params

A static function that makes the class it sits on was not read at all,
so every named constructor in the API was missing. One that throws was
then missing twice over: skipped for __init__ because it throws, and
skipped as a factory for being the primary, leaving nothing to make
the class with.

An out parameter was dropped along with the value it carried, so a
`gboolean` method that answers through one now binds as Optional, and
any other out parameter is refused rather than bound as a result the
caller cannot read.
This commit is contained in:
Ole André Vadla Ravnås committed 2026-09-17 18:54:58 +02:00
1 parent 00e6231b89
commit 01fd680353
3 files changed
+67 -11

No files matched your search

+58 -10
View File
@@ -436,7 +436,7 @@ def generate_aio_async_method(method: Method, model: Model) -> Optional[str]:
if method.return_value is not None:
call = wrap_result(call, method.return_value.type, model)
ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model)
ret = facade_return_type(method, model)
return f""" async def {method.name}({signature}) -> {ret}:
return {call}"""
@@ -460,7 +460,7 @@ def generate_custom_facade_method(
body = textwrap.indent(logic.strip(), " " * 8)
keyword = "async def" if awaitable else "def"
ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model)
ret = facade_return_type(method, model)
return f""" {keyword} {method.name}({signature}) -> {ret}:
{body}
@@ -667,7 +667,7 @@ def generate_py_sync_method(method: Method, model: Model) -> Optional[str]:
if method.return_value is not None:
call = wrap_result(call, method.return_value.type, model)
ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model)
ret = facade_return_type(method, model)
return f""" def {method.name}({facade_signature(method, params)}) -> {ret}:
return {call}"""
@@ -703,7 +703,7 @@ def generate_py_async_method(method: Method, model: Model) -> Optional[str]:
if method.return_value is not None:
call = wrap_result(call, method.return_value.type, model)
ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model)
ret = facade_return_type(method, model)
return f""" def {method.name}({signature}) -> {ret}:
return {call}"""
@@ -793,8 +793,8 @@ def generate_pyi_class(otype: ObjectType, model: Model) -> List[str]:
body = []
primary = primary_constructor(otype)
if primary is not None and not primary.throws:
primary = inline_constructor(otype)
if primary is not None:
params = pyi_parameters(primary.input_parameters, model)
if params is not None:
signature = ", ".join(["self"] + params)
@@ -869,7 +869,13 @@ def pyi_method(method: Method, model: Model) -> Optional[str]:
if params is None:
return None
signature = ", ".join(["self"] + params)
ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model)
out = method.optional_out_parameter
if out is not None:
ret = f"Optional[{pyi_type(out.type, model)}]"
elif method.return_value is None:
ret = "None"
else:
ret = pyi_type(method.return_value.type, model)
return f" def {method.name}({signature}) -> {ret}: ..."
@@ -1371,6 +1377,11 @@ def generate_method(otype: ObjectType, method: Method) -> Optional[Tuple[str, st
marshal = synchronous_method_return_marshal(method)
if marshal is None:
return None
out = method.optional_out_parameter
if out is not None:
marshal = build_return_marshal(out.type, method.object_type.model)
if marshal is None:
return None
params = []
for param in method.input_parameters:
sync_param = build_sync_param(param)
@@ -1714,14 +1725,17 @@ def generate_synchronous_method(otype: ObjectType, method: Method, marshal: str,
indent = " " * (len(otype.c_symbol_prefix) + len(method.name) + 3)
handle = f"({otype.c_type} *) PY_GOBJECT_HANDLE (self)"
returns_strv = method.return_value is not None and method.return_value.type.name == "utf8[]"
out = method.optional_out_parameter
call_arg_list = [handle] + [p.call_arg for p in params]
if returns_strv:
call_arg_list.append("&retval_length")
if out is not None:
call_arg_list.append(f"&{out.name}")
if method.throws:
call_arg_list.append("&error")
call = f"{method.c_identifier} ({', '.join(call_arg_list)})"
if not params and not method.throws and not returns_strv:
if not params and not method.throws and not returns_strv and out is None:
if method.return_value is None:
body = f" {call};\n\n PyFrida_RETURN_NONE;"
else:
@@ -1740,6 +1754,8 @@ static PyObject *
decls.append(" GError * error = NULL;")
if returns_strv:
decls.append(" gint retval_length;")
if out is not None:
decls.append(f" {out_parameter_storage_type(out)} {out.name};")
for p in params:
decls += [f" {line}" for line in p.decl.splitlines()]
@@ -1754,7 +1770,15 @@ static PyObject *
pre = "".join(f"\n {p.pre}\n" for p in params if p.pre)
if method.return_value is None:
if out is not None:
invocation = f" gboolean found = {call};"
success = (
" if (found)\n"
f" result = {marshal.format(value=out.name)};\n"
" else\n"
" {\n Py_IncRef (Py_None);\n result = Py_None;\n }"
)
elif method.return_value is None:
invocation = f" {call};"
success = " Py_IncRef (Py_None);\n result = Py_None;"
elif returns_strv:
@@ -1799,6 +1823,22 @@ static PyObject *
"""
def facade_return_type(method: Method, model: Model) -> str:
out = method.optional_out_parameter
if out is not None:
return f"Optional[{pyi_type(out.type, model)}]"
if method.return_value is None:
return "None"
return pyi_type(method.return_value.type, model)
def out_parameter_storage_type(param: Parameter) -> str:
"""What the caller declares: the out parameter's own type, less the pointer."""
c = param.type.c
assert c.endswith("*"), c
return c[:-1].strip()
def synchronous_method_return_marshal(method: Method) -> Optional[str]:
if method.is_async or method.is_property_accessor:
return None
@@ -2328,8 +2368,16 @@ def primary_constructor(otype: ObjectType) -> Optional[Procedure]:
return next((c for c in otype.constructors if c.name == "new"), otype.constructors[0])
def inline_constructor(otype: ObjectType) -> Optional[Procedure]:
"""The constructor __init__ takes; one that throws is reached as a factory instead."""
ctor = primary_constructor(otype)
if ctor is None or ctor.throws:
return None
return ctor
def factory_constructors(otype: ObjectType) -> List[Procedure]:
primary = primary_constructor(otype)
primary = inline_constructor(otype)
return [
ctor
for ctor in otype.constructors
+8
View File
@@ -17,6 +17,10 @@ class Model(core.Model):
class ObjectType(core.ObjectType):
@cached_property
def methods(self) -> List["Method"]:
return [m for m in super().methods if not m.out_parameters or m.optional_out_parameter is not None]
@cached_property
def py_name(self) -> str:
custom = self.customizations
@@ -277,6 +281,7 @@ def _make_class(
type_struct,
parent,
constructors,
functions,
methods,
properties,
signals,
@@ -291,6 +296,7 @@ def _make_class(
type_struct,
parent,
constructors,
functions,
methods,
properties,
signals,
@@ -307,6 +313,7 @@ def _make_interface(
type_struct,
parent,
constructors,
functions,
methods,
properties,
signals,
@@ -320,6 +327,7 @@ def _make_interface(
type_struct,
parent,
constructors,
functions,
methods,
properties,
signals,