980 lines
37 KiB
Python
980 lines
37 KiB
Python
|
"""Generate CPython API wrapper functions for native functions.
|
||
|
|
||
|
The wrapper functions are used by the CPython runtime when calling
|
||
|
native functions from interpreted code, and when the called function
|
||
|
can't be determined statically in compiled code. They validate, match,
|
||
|
unbox and type check function arguments, and box return values as
|
||
|
needed. All wrappers accept and return 'PyObject *' (boxed) values.
|
||
|
|
||
|
The wrappers aren't used for most calls between two native functions
|
||
|
or methods in a single compilation unit.
|
||
|
"""
|
||
|
|
||
|
from __future__ import annotations
|
||
|
|
||
|
from typing import Sequence
|
||
|
|
||
|
from mypy.nodes import ARG_NAMED, ARG_NAMED_OPT, ARG_OPT, ARG_POS, ARG_STAR, ARG_STAR2, ArgKind
|
||
|
from mypy.operators import op_methods_to_symbols, reverse_op_method_names, reverse_op_methods
|
||
|
from mypyc.codegen.emit import AssignHandler, Emitter, ErrorHandler, GotoHandler, ReturnHandler
|
||
|
from mypyc.common import (
|
||
|
BITMAP_BITS,
|
||
|
BITMAP_TYPE,
|
||
|
DUNDER_PREFIX,
|
||
|
NATIVE_PREFIX,
|
||
|
PREFIX,
|
||
|
bitmap_name,
|
||
|
use_vectorcall,
|
||
|
)
|
||
|
from mypyc.ir.class_ir import ClassIR
|
||
|
from mypyc.ir.func_ir import FUNC_STATICMETHOD, FuncIR, RuntimeArg
|
||
|
from mypyc.ir.rtypes import (
|
||
|
RInstance,
|
||
|
RType,
|
||
|
is_bool_rprimitive,
|
||
|
is_int_rprimitive,
|
||
|
is_object_rprimitive,
|
||
|
object_rprimitive,
|
||
|
)
|
||
|
from mypyc.namegen import NameGenerator
|
||
|
|
||
|
# Generic vectorcall wrapper functions (Python 3.7+)
|
||
|
#
|
||
|
# A wrapper function has a signature like this:
|
||
|
#
|
||
|
# PyObject *fn(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||
|
#
|
||
|
# The function takes a self object, pointer to an array of arguments,
|
||
|
# the number of positional arguments, and a tuple of keyword argument
|
||
|
# names (that are stored starting in args[nargs]).
|
||
|
#
|
||
|
# It returns the returned object, or NULL on an exception.
|
||
|
#
|
||
|
# These are more efficient than legacy wrapper functions, since
|
||
|
# usually no tuple or dict objects need to be created for the
|
||
|
# arguments. Vectorcalls also use pre-constructed str objects for
|
||
|
# keyword argument names and other pre-computed information, instead
|
||
|
# of processing the argument format string on each call.
|
||
|
|
||
|
|
||
|
def wrapper_function_header(fn: FuncIR, names: NameGenerator) -> str:
|
||
|
"""Return header of a vectorcall wrapper function.
|
||
|
|
||
|
See comment above for a summary of the arguments.
|
||
|
"""
|
||
|
return (
|
||
|
"PyObject *{prefix}{name}("
|
||
|
"PyObject *self, PyObject *const *args, size_t nargs, PyObject *kwnames)"
|
||
|
).format(prefix=PREFIX, name=fn.cname(names))
|
||
|
|
||
|
|
||
|
def generate_traceback_code(
|
||
|
fn: FuncIR, emitter: Emitter, source_path: str, module_name: str
|
||
|
) -> str:
|
||
|
# If we hit an error while processing arguments, then we emit a
|
||
|
# traceback frame to make it possible to debug where it happened.
|
||
|
# Unlike traceback frames added for exceptions seen in IR, we do this
|
||
|
# even if there is no `traceback_name`. This is because the error will
|
||
|
# have originated here and so we need it in the traceback.
|
||
|
globals_static = emitter.static_name("globals", module_name)
|
||
|
traceback_code = 'CPy_AddTraceback("%s", "%s", %d, %s);' % (
|
||
|
source_path.replace("\\", "\\\\"),
|
||
|
fn.traceback_name or fn.name,
|
||
|
fn.line,
|
||
|
globals_static,
|
||
|
)
|
||
|
return traceback_code
|
||
|
|
||
|
|
||
|
def make_arg_groups(args: list[RuntimeArg]) -> dict[ArgKind, list[RuntimeArg]]:
|
||
|
"""Group arguments by kind."""
|
||
|
return {k: [arg for arg in args if arg.kind == k] for k in ArgKind}
|
||
|
|
||
|
|
||
|
def reorder_arg_groups(groups: dict[ArgKind, list[RuntimeArg]]) -> list[RuntimeArg]:
|
||
|
"""Reorder argument groups to match their order in a format string."""
|
||
|
return groups[ARG_POS] + groups[ARG_OPT] + groups[ARG_NAMED_OPT] + groups[ARG_NAMED]
|
||
|
|
||
|
|
||
|
def make_static_kwlist(args: list[RuntimeArg]) -> str:
|
||
|
arg_names = "".join(f'"{arg.name}", ' for arg in args)
|
||
|
return f"static const char * const kwlist[] = {{{arg_names}0}};"
|
||
|
|
||
|
|
||
|
def make_format_string(func_name: str | None, groups: dict[ArgKind, list[RuntimeArg]]) -> str:
|
||
|
"""Return a format string that specifies the accepted arguments.
|
||
|
|
||
|
The format string is an extended subset of what is supported by
|
||
|
PyArg_ParseTupleAndKeywords(). Only the type 'O' is used, and we
|
||
|
also support some extensions:
|
||
|
|
||
|
- Required keyword-only arguments are introduced after '@'
|
||
|
- If the function receives *args or **kwargs, we add a '%' prefix
|
||
|
|
||
|
Each group requires the previous groups' delimiters to be present
|
||
|
first.
|
||
|
|
||
|
These are used by both vectorcall and legacy wrapper functions.
|
||
|
"""
|
||
|
format = ""
|
||
|
if groups[ARG_STAR] or groups[ARG_STAR2]:
|
||
|
format += "%"
|
||
|
format += "O" * len(groups[ARG_POS])
|
||
|
if groups[ARG_OPT] or groups[ARG_NAMED_OPT] or groups[ARG_NAMED]:
|
||
|
format += "|" + "O" * len(groups[ARG_OPT])
|
||
|
if groups[ARG_NAMED_OPT] or groups[ARG_NAMED]:
|
||
|
format += "$" + "O" * len(groups[ARG_NAMED_OPT])
|
||
|
if groups[ARG_NAMED]:
|
||
|
format += "@" + "O" * len(groups[ARG_NAMED])
|
||
|
if func_name is not None:
|
||
|
format += f":{func_name}"
|
||
|
return format
|
||
|
|
||
|
|
||
|
def generate_wrapper_function(
|
||
|
fn: FuncIR, emitter: Emitter, source_path: str, module_name: str
|
||
|
) -> None:
|
||
|
"""Generate a CPython-compatible vectorcall wrapper for a native function.
|
||
|
|
||
|
In particular, this handles unboxing the arguments, calling the native function, and
|
||
|
then boxing the return value.
|
||
|
"""
|
||
|
emitter.emit_line(f"{wrapper_function_header(fn, emitter.names)} {{")
|
||
|
|
||
|
# If fn is a method, then the first argument is a self param
|
||
|
real_args = list(fn.args)
|
||
|
if fn.sig.num_bitmap_args:
|
||
|
real_args = real_args[: -fn.sig.num_bitmap_args]
|
||
|
if fn.class_name and not fn.decl.kind == FUNC_STATICMETHOD:
|
||
|
arg = real_args.pop(0)
|
||
|
emitter.emit_line(f"PyObject *obj_{arg.name} = self;")
|
||
|
|
||
|
# Need to order args as: required, optional, kwonly optional, kwonly required
|
||
|
# This is because CPyArg_ParseStackAndKeywords format string requires
|
||
|
# them grouped in that way.
|
||
|
groups = make_arg_groups(real_args)
|
||
|
reordered_args = reorder_arg_groups(groups)
|
||
|
|
||
|
emitter.emit_line(make_static_kwlist(reordered_args))
|
||
|
fmt = make_format_string(fn.name, groups)
|
||
|
# Define the arguments the function accepts (but no types yet)
|
||
|
emitter.emit_line(f'static CPyArg_Parser parser = {{"{fmt}", kwlist, 0}};')
|
||
|
|
||
|
for arg in real_args:
|
||
|
emitter.emit_line(
|
||
|
"PyObject *obj_{}{};".format(arg.name, " = NULL" if arg.optional else "")
|
||
|
)
|
||
|
|
||
|
cleanups = [f"CPy_DECREF(obj_{arg.name});" for arg in groups[ARG_STAR] + groups[ARG_STAR2]]
|
||
|
|
||
|
arg_ptrs: list[str] = []
|
||
|
if groups[ARG_STAR] or groups[ARG_STAR2]:
|
||
|
arg_ptrs += [f"&obj_{groups[ARG_STAR][0].name}" if groups[ARG_STAR] else "NULL"]
|
||
|
arg_ptrs += [f"&obj_{groups[ARG_STAR2][0].name}" if groups[ARG_STAR2] else "NULL"]
|
||
|
arg_ptrs += [f"&obj_{arg.name}" for arg in reordered_args]
|
||
|
|
||
|
if fn.name == "__call__" and use_vectorcall(emitter.capi_version):
|
||
|
nargs = "PyVectorcall_NARGS(nargs)"
|
||
|
else:
|
||
|
nargs = "nargs"
|
||
|
parse_fn = "CPyArg_ParseStackAndKeywords"
|
||
|
# Special case some common signatures
|
||
|
if not real_args:
|
||
|
# No args
|
||
|
parse_fn = "CPyArg_ParseStackAndKeywordsNoArgs"
|
||
|
elif len(real_args) == 1 and len(groups[ARG_POS]) == 1:
|
||
|
# Single positional arg
|
||
|
parse_fn = "CPyArg_ParseStackAndKeywordsOneArg"
|
||
|
elif len(real_args) == len(groups[ARG_POS]) + len(groups[ARG_OPT]):
|
||
|
# No keyword-only args, *args or **kwargs
|
||
|
parse_fn = "CPyArg_ParseStackAndKeywordsSimple"
|
||
|
emitter.emit_lines(
|
||
|
"if (!{}(args, {}, kwnames, &parser{})) {{".format(
|
||
|
parse_fn, nargs, "".join(", " + n for n in arg_ptrs)
|
||
|
),
|
||
|
"return NULL;",
|
||
|
"}",
|
||
|
)
|
||
|
for i in range(fn.sig.num_bitmap_args):
|
||
|
name = bitmap_name(i)
|
||
|
emitter.emit_line(f"{BITMAP_TYPE} {name} = 0;")
|
||
|
traceback_code = generate_traceback_code(fn, emitter, source_path, module_name)
|
||
|
generate_wrapper_core(
|
||
|
fn,
|
||
|
emitter,
|
||
|
groups[ARG_OPT] + groups[ARG_NAMED_OPT],
|
||
|
cleanups=cleanups,
|
||
|
traceback_code=traceback_code,
|
||
|
)
|
||
|
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
|
||
|
# Legacy generic wrapper functions
|
||
|
#
|
||
|
# These take a self object, a Python tuple of positional arguments,
|
||
|
# and a dict of keyword arguments. These are a lot slower than
|
||
|
# vectorcall wrappers, especially in calls involving keyword
|
||
|
# arguments.
|
||
|
|
||
|
|
||
|
def legacy_wrapper_function_header(fn: FuncIR, names: NameGenerator) -> str:
|
||
|
return "PyObject *{prefix}{name}(PyObject *self, PyObject *args, PyObject *kw)".format(
|
||
|
prefix=PREFIX, name=fn.cname(names)
|
||
|
)
|
||
|
|
||
|
|
||
|
def generate_legacy_wrapper_function(
|
||
|
fn: FuncIR, emitter: Emitter, source_path: str, module_name: str
|
||
|
) -> None:
|
||
|
"""Generates a CPython-compatible legacy wrapper for a native function.
|
||
|
|
||
|
In particular, this handles unboxing the arguments, calling the native function, and
|
||
|
then boxing the return value.
|
||
|
"""
|
||
|
emitter.emit_line(f"{legacy_wrapper_function_header(fn, emitter.names)} {{")
|
||
|
|
||
|
# If fn is a method, then the first argument is a self param
|
||
|
real_args = list(fn.args)
|
||
|
if fn.sig.num_bitmap_args:
|
||
|
real_args = real_args[: -fn.sig.num_bitmap_args]
|
||
|
if fn.class_name and not fn.decl.kind == FUNC_STATICMETHOD:
|
||
|
arg = real_args.pop(0)
|
||
|
emitter.emit_line(f"PyObject *obj_{arg.name} = self;")
|
||
|
|
||
|
# Need to order args as: required, optional, kwonly optional, kwonly required
|
||
|
# This is because CPyArg_ParseTupleAndKeywords format string requires
|
||
|
# them grouped in that way.
|
||
|
groups = make_arg_groups(real_args)
|
||
|
reordered_args = reorder_arg_groups(groups)
|
||
|
|
||
|
emitter.emit_line(make_static_kwlist(reordered_args))
|
||
|
for arg in real_args:
|
||
|
emitter.emit_line(
|
||
|
"PyObject *obj_{}{};".format(arg.name, " = NULL" if arg.optional else "")
|
||
|
)
|
||
|
|
||
|
cleanups = [f"CPy_DECREF(obj_{arg.name});" for arg in groups[ARG_STAR] + groups[ARG_STAR2]]
|
||
|
|
||
|
arg_ptrs: list[str] = []
|
||
|
if groups[ARG_STAR] or groups[ARG_STAR2]:
|
||
|
arg_ptrs += [f"&obj_{groups[ARG_STAR][0].name}" if groups[ARG_STAR] else "NULL"]
|
||
|
arg_ptrs += [f"&obj_{groups[ARG_STAR2][0].name}" if groups[ARG_STAR2] else "NULL"]
|
||
|
arg_ptrs += [f"&obj_{arg.name}" for arg in reordered_args]
|
||
|
|
||
|
emitter.emit_lines(
|
||
|
'if (!CPyArg_ParseTupleAndKeywords(args, kw, "{}", "{}", kwlist{})) {{'.format(
|
||
|
make_format_string(None, groups), fn.name, "".join(", " + n for n in arg_ptrs)
|
||
|
),
|
||
|
"return NULL;",
|
||
|
"}",
|
||
|
)
|
||
|
for i in range(fn.sig.num_bitmap_args):
|
||
|
name = bitmap_name(i)
|
||
|
emitter.emit_line(f"{BITMAP_TYPE} {name} = 0;")
|
||
|
traceback_code = generate_traceback_code(fn, emitter, source_path, module_name)
|
||
|
generate_wrapper_core(
|
||
|
fn,
|
||
|
emitter,
|
||
|
groups[ARG_OPT] + groups[ARG_NAMED_OPT],
|
||
|
cleanups=cleanups,
|
||
|
traceback_code=traceback_code,
|
||
|
)
|
||
|
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
|
||
|
# Specialized wrapper functions
|
||
|
|
||
|
|
||
|
def generate_dunder_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
|
||
|
"""Generates a wrapper for native __dunder__ methods to be able to fit into the mapping
|
||
|
protocol slot. This specifically means that the arguments are taken as *PyObjects and returned
|
||
|
as *PyObjects.
|
||
|
"""
|
||
|
gen = WrapperGenerator(cl, emitter)
|
||
|
gen.set_target(fn)
|
||
|
gen.emit_header()
|
||
|
gen.emit_arg_processing()
|
||
|
gen.emit_call()
|
||
|
gen.finish()
|
||
|
return gen.wrapper_name()
|
||
|
|
||
|
|
||
|
def generate_ipow_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
|
||
|
"""Generate a wrapper for native __ipow__.
|
||
|
|
||
|
Since __ipow__ fills a ternary slot, but almost no one defines __ipow__ to take three
|
||
|
arguments, the wrapper needs to tweaked to force it to accept three arguments.
|
||
|
"""
|
||
|
gen = WrapperGenerator(cl, emitter)
|
||
|
gen.set_target(fn)
|
||
|
assert len(fn.args) in (2, 3), "__ipow__ should only take 2 or 3 arguments"
|
||
|
gen.arg_names = ["self", "exp", "mod"]
|
||
|
gen.emit_header()
|
||
|
gen.emit_arg_processing()
|
||
|
handle_third_pow_argument(
|
||
|
fn,
|
||
|
emitter,
|
||
|
gen,
|
||
|
if_unsupported=[
|
||
|
'PyErr_SetString(PyExc_TypeError, "__ipow__ takes 2 positional arguments but 3 were given");',
|
||
|
"return NULL;",
|
||
|
],
|
||
|
)
|
||
|
gen.emit_call()
|
||
|
gen.finish()
|
||
|
return gen.wrapper_name()
|
||
|
|
||
|
|
||
|
def generate_bin_op_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
|
||
|
"""Generates a wrapper for a native binary dunder method.
|
||
|
|
||
|
The same wrapper that handles the forward method (e.g. __add__) also handles
|
||
|
the corresponding reverse method (e.g. __radd__), if defined.
|
||
|
|
||
|
Both arguments and the return value are PyObject *.
|
||
|
"""
|
||
|
gen = WrapperGenerator(cl, emitter)
|
||
|
gen.set_target(fn)
|
||
|
if fn.name in ("__pow__", "__rpow__"):
|
||
|
gen.arg_names = ["left", "right", "mod"]
|
||
|
else:
|
||
|
gen.arg_names = ["left", "right"]
|
||
|
wrapper_name = gen.wrapper_name()
|
||
|
|
||
|
gen.emit_header()
|
||
|
if fn.name not in reverse_op_methods and fn.name in reverse_op_method_names:
|
||
|
# There's only a reverse operator method.
|
||
|
generate_bin_op_reverse_only_wrapper(fn, emitter, gen)
|
||
|
else:
|
||
|
rmethod = reverse_op_methods[fn.name]
|
||
|
fn_rev = cl.get_method(rmethod)
|
||
|
if fn_rev is None:
|
||
|
# There's only a forward operator method.
|
||
|
generate_bin_op_forward_only_wrapper(fn, emitter, gen)
|
||
|
else:
|
||
|
# There's both a forward and a reverse operator method.
|
||
|
generate_bin_op_both_wrappers(cl, fn, fn_rev, emitter, gen)
|
||
|
return wrapper_name
|
||
|
|
||
|
|
||
|
def generate_bin_op_forward_only_wrapper(
|
||
|
fn: FuncIR, emitter: Emitter, gen: WrapperGenerator
|
||
|
) -> None:
|
||
|
gen.emit_arg_processing(error=GotoHandler("typefail"), raise_exception=False)
|
||
|
handle_third_pow_argument(fn, emitter, gen, if_unsupported=["goto typefail;"])
|
||
|
gen.emit_call(not_implemented_handler="goto typefail;")
|
||
|
gen.emit_error_handling()
|
||
|
emitter.emit_label("typefail")
|
||
|
# If some argument has an incompatible type, treat this the same as
|
||
|
# returning NotImplemented, and try to call the reverse operator method.
|
||
|
#
|
||
|
# Note that in normal Python you'd instead of an explicit
|
||
|
# return of NotImplemented, but it doesn't generally work here
|
||
|
# the body won't be executed at all if there is an argument
|
||
|
# type check failure.
|
||
|
#
|
||
|
# The recommended way is to still use a type check in the
|
||
|
# body. This will only be used in interpreted mode:
|
||
|
#
|
||
|
# def __add__(self, other: int) -> Foo:
|
||
|
# if not isinstance(other, int):
|
||
|
# return NotImplemented
|
||
|
# ...
|
||
|
generate_bin_op_reverse_dunder_call(fn, emitter, reverse_op_methods[fn.name])
|
||
|
gen.finish()
|
||
|
|
||
|
|
||
|
def generate_bin_op_reverse_only_wrapper(
|
||
|
fn: FuncIR, emitter: Emitter, gen: WrapperGenerator
|
||
|
) -> None:
|
||
|
gen.arg_names = ["right", "left"]
|
||
|
gen.emit_arg_processing(error=GotoHandler("typefail"), raise_exception=False)
|
||
|
handle_third_pow_argument(fn, emitter, gen, if_unsupported=["goto typefail;"])
|
||
|
gen.emit_call()
|
||
|
gen.emit_error_handling()
|
||
|
emitter.emit_label("typefail")
|
||
|
emitter.emit_line("Py_INCREF(Py_NotImplemented);")
|
||
|
emitter.emit_line("return Py_NotImplemented;")
|
||
|
gen.finish()
|
||
|
|
||
|
|
||
|
def generate_bin_op_both_wrappers(
|
||
|
cl: ClassIR, fn: FuncIR, fn_rev: FuncIR, emitter: Emitter, gen: WrapperGenerator
|
||
|
) -> None:
|
||
|
# There's both a forward and a reverse operator method. First
|
||
|
# check if we should try calling the forward one. If the
|
||
|
# argument type check fails, fall back to the reverse method.
|
||
|
#
|
||
|
# Similar to above, we can't perfectly match Python semantics.
|
||
|
# In regular Python code you'd return NotImplemented if the
|
||
|
# operand has the wrong type, but in compiled code we'll never
|
||
|
# get to execute the type check.
|
||
|
emitter.emit_line(
|
||
|
"if (PyObject_IsInstance(obj_left, (PyObject *){})) {{".format(
|
||
|
emitter.type_struct_name(cl)
|
||
|
)
|
||
|
)
|
||
|
gen.emit_arg_processing(error=GotoHandler("typefail"), raise_exception=False)
|
||
|
handle_third_pow_argument(fn, emitter, gen, if_unsupported=["goto typefail2;"])
|
||
|
# Ternary __rpow__ calls aren't a thing so immediately bail
|
||
|
# if ternary __pow__ returns NotImplemented.
|
||
|
if fn.name == "__pow__" and len(fn.args) == 3:
|
||
|
fwd_not_implemented_handler = "goto typefail2;"
|
||
|
else:
|
||
|
fwd_not_implemented_handler = "goto typefail;"
|
||
|
gen.emit_call(not_implemented_handler=fwd_not_implemented_handler)
|
||
|
gen.emit_error_handling()
|
||
|
emitter.emit_line("}")
|
||
|
emitter.emit_label("typefail")
|
||
|
emitter.emit_line(
|
||
|
"if (PyObject_IsInstance(obj_right, (PyObject *){})) {{".format(
|
||
|
emitter.type_struct_name(cl)
|
||
|
)
|
||
|
)
|
||
|
gen.set_target(fn_rev)
|
||
|
gen.arg_names = ["right", "left"]
|
||
|
gen.emit_arg_processing(error=GotoHandler("typefail2"), raise_exception=False)
|
||
|
handle_third_pow_argument(fn_rev, emitter, gen, if_unsupported=["goto typefail2;"])
|
||
|
gen.emit_call()
|
||
|
gen.emit_error_handling()
|
||
|
emitter.emit_line("} else {")
|
||
|
generate_bin_op_reverse_dunder_call(fn, emitter, fn_rev.name)
|
||
|
emitter.emit_line("}")
|
||
|
emitter.emit_label("typefail2")
|
||
|
emitter.emit_line("Py_INCREF(Py_NotImplemented);")
|
||
|
emitter.emit_line("return Py_NotImplemented;")
|
||
|
gen.finish()
|
||
|
|
||
|
|
||
|
def generate_bin_op_reverse_dunder_call(fn: FuncIR, emitter: Emitter, rmethod: str) -> None:
|
||
|
if fn.name in ("__pow__", "__rpow__"):
|
||
|
# Ternary pow() will never call the reverse dunder.
|
||
|
emitter.emit_line("if (obj_mod == Py_None) {")
|
||
|
emitter.emit_line(f"_Py_IDENTIFIER({rmethod});")
|
||
|
emitter.emit_line(
|
||
|
'return CPy_CallReverseOpMethod(obj_left, obj_right, "{}", &PyId_{});'.format(
|
||
|
op_methods_to_symbols[fn.name], rmethod
|
||
|
)
|
||
|
)
|
||
|
if fn.name in ("__pow__", "__rpow__"):
|
||
|
emitter.emit_line("} else {")
|
||
|
emitter.emit_line("Py_INCREF(Py_NotImplemented);")
|
||
|
emitter.emit_line("return Py_NotImplemented;")
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
|
||
|
def handle_third_pow_argument(
|
||
|
fn: FuncIR, emitter: Emitter, gen: WrapperGenerator, *, if_unsupported: list[str]
|
||
|
) -> None:
|
||
|
if fn.name not in ("__pow__", "__rpow__", "__ipow__"):
|
||
|
return
|
||
|
|
||
|
if (fn.name in ("__pow__", "__ipow__") and len(fn.args) == 2) or fn.name == "__rpow__":
|
||
|
# If the power dunder only supports two arguments and the third
|
||
|
# argument (AKA mod) is set to a non-default value, simply bail.
|
||
|
#
|
||
|
# Importantly, this prevents any ternary __rpow__ calls from
|
||
|
# happening (as per the language specification).
|
||
|
emitter.emit_line("if (obj_mod != Py_None) {")
|
||
|
for line in if_unsupported:
|
||
|
emitter.emit_line(line)
|
||
|
emitter.emit_line("}")
|
||
|
# The slot wrapper will receive three arguments, but the call only
|
||
|
# supports two so make sure that the third argument isn't passed
|
||
|
# along. This is needed as two-argument __(i)pow__ is allowed and
|
||
|
# rather common.
|
||
|
if len(gen.arg_names) == 3:
|
||
|
gen.arg_names.pop()
|
||
|
|
||
|
|
||
|
RICHCOMPARE_OPS = {
|
||
|
"__lt__": "Py_LT",
|
||
|
"__gt__": "Py_GT",
|
||
|
"__le__": "Py_LE",
|
||
|
"__ge__": "Py_GE",
|
||
|
"__eq__": "Py_EQ",
|
||
|
"__ne__": "Py_NE",
|
||
|
}
|
||
|
|
||
|
|
||
|
def generate_richcompare_wrapper(cl: ClassIR, emitter: Emitter) -> str | None:
|
||
|
"""Generates a wrapper for richcompare dunder methods."""
|
||
|
# Sort for determinism on Python 3.5
|
||
|
matches = sorted(name for name in RICHCOMPARE_OPS if cl.has_method(name))
|
||
|
if not matches:
|
||
|
return None
|
||
|
|
||
|
name = f"{DUNDER_PREFIX}_RichCompare_{cl.name_prefix(emitter.names)}"
|
||
|
emitter.emit_line(
|
||
|
"static PyObject *{name}(PyObject *obj_lhs, PyObject *obj_rhs, int op) {{".format(
|
||
|
name=name
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_line("switch (op) {")
|
||
|
for func in matches:
|
||
|
emitter.emit_line(f"case {RICHCOMPARE_OPS[func]}: {{")
|
||
|
method = cl.get_method(func)
|
||
|
assert method is not None
|
||
|
generate_wrapper_core(method, emitter, arg_names=["lhs", "rhs"])
|
||
|
emitter.emit_line("}")
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
emitter.emit_line("Py_INCREF(Py_NotImplemented);")
|
||
|
emitter.emit_line("return Py_NotImplemented;")
|
||
|
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
return name
|
||
|
|
||
|
|
||
|
def generate_get_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
|
||
|
"""Generates a wrapper for native __get__ methods."""
|
||
|
name = f"{DUNDER_PREFIX}{fn.name}{cl.name_prefix(emitter.names)}"
|
||
|
emitter.emit_line(
|
||
|
"static PyObject *{name}(PyObject *self, PyObject *instance, PyObject *owner) {{".format(
|
||
|
name=name
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_line("instance = instance ? instance : Py_None;")
|
||
|
emitter.emit_line(f"return {NATIVE_PREFIX}{fn.cname(emitter.names)}(self, instance, owner);")
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
return name
|
||
|
|
||
|
|
||
|
def generate_hash_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
|
||
|
"""Generates a wrapper for native __hash__ methods."""
|
||
|
name = f"{DUNDER_PREFIX}{fn.name}{cl.name_prefix(emitter.names)}"
|
||
|
emitter.emit_line(f"static Py_ssize_t {name}(PyObject *self) {{")
|
||
|
emitter.emit_line(
|
||
|
"{}retval = {}{}{}(self);".format(
|
||
|
emitter.ctype_spaced(fn.ret_type),
|
||
|
emitter.get_group_prefix(fn.decl),
|
||
|
NATIVE_PREFIX,
|
||
|
fn.cname(emitter.names),
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_error_check("retval", fn.ret_type, "return -1;")
|
||
|
if is_int_rprimitive(fn.ret_type):
|
||
|
emitter.emit_line("Py_ssize_t val = CPyTagged_AsSsize_t(retval);")
|
||
|
else:
|
||
|
emitter.emit_line("Py_ssize_t val = PyLong_AsSsize_t(retval);")
|
||
|
emitter.emit_dec_ref("retval", fn.ret_type)
|
||
|
emitter.emit_line("if (PyErr_Occurred()) return -1;")
|
||
|
# We can't return -1 from a hash function..
|
||
|
emitter.emit_line("if (val == -1) return -2;")
|
||
|
emitter.emit_line("return val;")
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
return name
|
||
|
|
||
|
|
||
|
def generate_len_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
|
||
|
"""Generates a wrapper for native __len__ methods."""
|
||
|
name = f"{DUNDER_PREFIX}{fn.name}{cl.name_prefix(emitter.names)}"
|
||
|
emitter.emit_line(f"static Py_ssize_t {name}(PyObject *self) {{")
|
||
|
emitter.emit_line(
|
||
|
"{}retval = {}{}{}(self);".format(
|
||
|
emitter.ctype_spaced(fn.ret_type),
|
||
|
emitter.get_group_prefix(fn.decl),
|
||
|
NATIVE_PREFIX,
|
||
|
fn.cname(emitter.names),
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_error_check("retval", fn.ret_type, "return -1;")
|
||
|
if is_int_rprimitive(fn.ret_type):
|
||
|
emitter.emit_line("Py_ssize_t val = CPyTagged_AsSsize_t(retval);")
|
||
|
else:
|
||
|
emitter.emit_line("Py_ssize_t val = PyLong_AsSsize_t(retval);")
|
||
|
emitter.emit_dec_ref("retval", fn.ret_type)
|
||
|
emitter.emit_line("if (PyErr_Occurred()) return -1;")
|
||
|
emitter.emit_line("return val;")
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
return name
|
||
|
|
||
|
|
||
|
def generate_bool_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
|
||
|
"""Generates a wrapper for native __bool__ methods."""
|
||
|
name = f"{DUNDER_PREFIX}{fn.name}{cl.name_prefix(emitter.names)}"
|
||
|
emitter.emit_line(f"static int {name}(PyObject *self) {{")
|
||
|
emitter.emit_line(
|
||
|
"{}val = {}{}(self);".format(
|
||
|
emitter.ctype_spaced(fn.ret_type), NATIVE_PREFIX, fn.cname(emitter.names)
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_error_check("val", fn.ret_type, "return -1;")
|
||
|
# This wouldn't be that hard to fix but it seems unimportant and
|
||
|
# getting error handling and unboxing right would be fiddly. (And
|
||
|
# way easier to do in IR!)
|
||
|
assert is_bool_rprimitive(fn.ret_type), "Only bool return supported for __bool__"
|
||
|
emitter.emit_line("return val;")
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
return name
|
||
|
|
||
|
|
||
|
def generate_del_item_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
|
||
|
"""Generates a wrapper for native __delitem__.
|
||
|
|
||
|
This is only called from a combined __delitem__/__setitem__ wrapper.
|
||
|
"""
|
||
|
name = "{}{}{}".format(DUNDER_PREFIX, "__delitem__", cl.name_prefix(emitter.names))
|
||
|
input_args = ", ".join(f"PyObject *obj_{arg.name}" for arg in fn.args)
|
||
|
emitter.emit_line(f"static int {name}({input_args}) {{")
|
||
|
generate_set_del_item_wrapper_inner(fn, emitter, fn.args)
|
||
|
return name
|
||
|
|
||
|
|
||
|
def generate_set_del_item_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
|
||
|
"""Generates a wrapper for native __setitem__ method (also works for __delitem__).
|
||
|
|
||
|
This is used with the mapping protocol slot. Arguments are taken as *PyObjects and we
|
||
|
return a negative C int on error.
|
||
|
|
||
|
Create a separate wrapper function for __delitem__ as needed and have the
|
||
|
__setitem__ wrapper call it if the value is NULL. Return the name
|
||
|
of the outer (__setitem__) wrapper.
|
||
|
"""
|
||
|
method_cls = cl.get_method_and_class("__delitem__")
|
||
|
del_name = None
|
||
|
if method_cls and method_cls[1] == cl:
|
||
|
# Generate a separate wrapper for __delitem__
|
||
|
del_name = generate_del_item_wrapper(cl, method_cls[0], emitter)
|
||
|
|
||
|
args = fn.args
|
||
|
if fn.name == "__delitem__":
|
||
|
# Add an extra argument for value that we expect to be NULL.
|
||
|
args = list(args) + [RuntimeArg("___value", object_rprimitive, ARG_POS)]
|
||
|
|
||
|
name = "{}{}{}".format(DUNDER_PREFIX, "__setitem__", cl.name_prefix(emitter.names))
|
||
|
input_args = ", ".join(f"PyObject *obj_{arg.name}" for arg in args)
|
||
|
emitter.emit_line(f"static int {name}({input_args}) {{")
|
||
|
|
||
|
# First check if this is __delitem__
|
||
|
emitter.emit_line(f"if (obj_{args[2].name} == NULL) {{")
|
||
|
if del_name is not None:
|
||
|
# We have a native implementation, so call it
|
||
|
emitter.emit_line(f"return {del_name}(obj_{args[0].name}, obj_{args[1].name});")
|
||
|
else:
|
||
|
# Try to call superclass method instead
|
||
|
emitter.emit_line(f"PyObject *super = CPy_Super(CPyModule_builtins, obj_{args[0].name});")
|
||
|
emitter.emit_line("if (super == NULL) return -1;")
|
||
|
emitter.emit_line(
|
||
|
'PyObject *result = PyObject_CallMethod(super, "__delitem__", "O", obj_{});'.format(
|
||
|
args[1].name
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_line("Py_DECREF(super);")
|
||
|
emitter.emit_line("Py_XDECREF(result);")
|
||
|
emitter.emit_line("return result == NULL ? -1 : 0;")
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
method_cls = cl.get_method_and_class("__setitem__")
|
||
|
if method_cls and method_cls[1] == cl:
|
||
|
generate_set_del_item_wrapper_inner(fn, emitter, args)
|
||
|
else:
|
||
|
emitter.emit_line(f"PyObject *super = CPy_Super(CPyModule_builtins, obj_{args[0].name});")
|
||
|
emitter.emit_line("if (super == NULL) return -1;")
|
||
|
emitter.emit_line("PyObject *result;")
|
||
|
|
||
|
if method_cls is None and cl.builtin_base is None:
|
||
|
msg = f"'{cl.name}' object does not support item assignment"
|
||
|
emitter.emit_line(f'PyErr_SetString(PyExc_TypeError, "{msg}");')
|
||
|
emitter.emit_line("result = NULL;")
|
||
|
else:
|
||
|
# A base class may have __setitem__
|
||
|
emitter.emit_line(
|
||
|
'result = PyObject_CallMethod(super, "__setitem__", "OO", obj_{}, obj_{});'.format(
|
||
|
args[1].name, args[2].name
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_line("Py_DECREF(super);")
|
||
|
emitter.emit_line("Py_XDECREF(result);")
|
||
|
emitter.emit_line("return result == NULL ? -1 : 0;")
|
||
|
emitter.emit_line("}")
|
||
|
return name
|
||
|
|
||
|
|
||
|
def generate_set_del_item_wrapper_inner(
|
||
|
fn: FuncIR, emitter: Emitter, args: Sequence[RuntimeArg]
|
||
|
) -> None:
|
||
|
for arg in args:
|
||
|
generate_arg_check(arg.name, arg.type, emitter, GotoHandler("fail"))
|
||
|
native_args = ", ".join(f"arg_{arg.name}" for arg in args)
|
||
|
emitter.emit_line(
|
||
|
"{}val = {}{}({});".format(
|
||
|
emitter.ctype_spaced(fn.ret_type), NATIVE_PREFIX, fn.cname(emitter.names), native_args
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_error_check("val", fn.ret_type, "goto fail;")
|
||
|
emitter.emit_dec_ref("val", fn.ret_type)
|
||
|
emitter.emit_line("return 0;")
|
||
|
emitter.emit_label("fail")
|
||
|
emitter.emit_line("return -1;")
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
|
||
|
def generate_contains_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
|
||
|
"""Generates a wrapper for a native __contains__ method."""
|
||
|
name = f"{DUNDER_PREFIX}{fn.name}{cl.name_prefix(emitter.names)}"
|
||
|
emitter.emit_line(f"static int {name}(PyObject *self, PyObject *obj_item) {{")
|
||
|
generate_arg_check("item", fn.args[1].type, emitter, ReturnHandler("-1"))
|
||
|
emitter.emit_line(
|
||
|
"{}val = {}{}(self, arg_item);".format(
|
||
|
emitter.ctype_spaced(fn.ret_type), NATIVE_PREFIX, fn.cname(emitter.names)
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_error_check("val", fn.ret_type, "return -1;")
|
||
|
if is_bool_rprimitive(fn.ret_type):
|
||
|
emitter.emit_line("return val;")
|
||
|
else:
|
||
|
emitter.emit_line("int boolval = PyObject_IsTrue(val);")
|
||
|
emitter.emit_dec_ref("val", fn.ret_type)
|
||
|
emitter.emit_line("return boolval;")
|
||
|
emitter.emit_line("}")
|
||
|
|
||
|
return name
|
||
|
|
||
|
|
||
|
# Helpers
|
||
|
|
||
|
|
||
|
def generate_wrapper_core(
|
||
|
fn: FuncIR,
|
||
|
emitter: Emitter,
|
||
|
optional_args: list[RuntimeArg] | None = None,
|
||
|
arg_names: list[str] | None = None,
|
||
|
cleanups: list[str] | None = None,
|
||
|
traceback_code: str | None = None,
|
||
|
) -> None:
|
||
|
"""Generates the core part of a wrapper function for a native function.
|
||
|
|
||
|
This expects each argument as a PyObject * named obj_{arg} as a precondition.
|
||
|
It converts the PyObject *s to the necessary types, checking and unboxing if necessary,
|
||
|
makes the call, then boxes the result if necessary and returns it.
|
||
|
"""
|
||
|
gen = WrapperGenerator(None, emitter)
|
||
|
gen.set_target(fn)
|
||
|
if arg_names:
|
||
|
gen.arg_names = arg_names
|
||
|
gen.cleanups = cleanups or []
|
||
|
gen.optional_args = optional_args or []
|
||
|
gen.traceback_code = traceback_code or ""
|
||
|
|
||
|
error = ReturnHandler("NULL") if not gen.use_goto() else GotoHandler("fail")
|
||
|
gen.emit_arg_processing(error=error)
|
||
|
gen.emit_call()
|
||
|
gen.emit_error_handling()
|
||
|
|
||
|
|
||
|
def generate_arg_check(
|
||
|
name: str,
|
||
|
typ: RType,
|
||
|
emitter: Emitter,
|
||
|
error: ErrorHandler | None = None,
|
||
|
*,
|
||
|
optional: bool = False,
|
||
|
raise_exception: bool = True,
|
||
|
bitmap_arg_index: int = 0,
|
||
|
) -> None:
|
||
|
"""Insert a runtime check for argument and unbox if necessary.
|
||
|
|
||
|
The object is named PyObject *obj_{}. This is expected to generate
|
||
|
a value of name arg_{} (unboxed if necessary). For each primitive a runtime
|
||
|
check ensures the correct type.
|
||
|
"""
|
||
|
error = error or AssignHandler()
|
||
|
if typ.is_unboxed:
|
||
|
if typ.error_overlap and optional:
|
||
|
# Update bitmap is value is provided.
|
||
|
init = emitter.c_undefined_value(typ)
|
||
|
emitter.emit_line(f"{emitter.ctype(typ)} arg_{name} = {init};")
|
||
|
emitter.emit_line(f"if (obj_{name} != NULL) {{")
|
||
|
bitmap = bitmap_name(bitmap_arg_index // BITMAP_BITS)
|
||
|
emitter.emit_line(f"{bitmap} |= 1 << {bitmap_arg_index & (BITMAP_BITS - 1)};")
|
||
|
emitter.emit_unbox(
|
||
|
f"obj_{name}",
|
||
|
f"arg_{name}",
|
||
|
typ,
|
||
|
declare_dest=False,
|
||
|
raise_exception=raise_exception,
|
||
|
error=error,
|
||
|
borrow=True,
|
||
|
)
|
||
|
emitter.emit_line("}")
|
||
|
else:
|
||
|
# Borrow when unboxing to avoid reference count manipulation.
|
||
|
emitter.emit_unbox(
|
||
|
f"obj_{name}",
|
||
|
f"arg_{name}",
|
||
|
typ,
|
||
|
declare_dest=True,
|
||
|
raise_exception=raise_exception,
|
||
|
error=error,
|
||
|
borrow=True,
|
||
|
optional=optional,
|
||
|
)
|
||
|
elif is_object_rprimitive(typ):
|
||
|
# Object is trivial since any object is valid
|
||
|
if optional:
|
||
|
emitter.emit_line(f"PyObject *arg_{name};")
|
||
|
emitter.emit_line(f"if (obj_{name} == NULL) {{")
|
||
|
emitter.emit_line(f"arg_{name} = {emitter.c_error_value(typ)};")
|
||
|
emitter.emit_lines("} else {", f"arg_{name} = obj_{name}; ", "}")
|
||
|
else:
|
||
|
emitter.emit_line(f"PyObject *arg_{name} = obj_{name};")
|
||
|
else:
|
||
|
emitter.emit_cast(
|
||
|
f"obj_{name}",
|
||
|
f"arg_{name}",
|
||
|
typ,
|
||
|
declare_dest=True,
|
||
|
raise_exception=raise_exception,
|
||
|
error=error,
|
||
|
optional=optional,
|
||
|
)
|
||
|
|
||
|
|
||
|
class WrapperGenerator:
|
||
|
"""Helper that simplifies the generation of wrapper functions."""
|
||
|
|
||
|
# TODO: Use this for more wrappers
|
||
|
|
||
|
def __init__(self, cl: ClassIR | None, emitter: Emitter) -> None:
|
||
|
self.cl = cl
|
||
|
self.emitter = emitter
|
||
|
self.cleanups: list[str] = []
|
||
|
self.optional_args: list[RuntimeArg] = []
|
||
|
self.traceback_code = ""
|
||
|
|
||
|
def set_target(self, fn: FuncIR) -> None:
|
||
|
"""Set the wrapped function.
|
||
|
|
||
|
It's fine to modify the attributes initialized here later to customize
|
||
|
the wrapper function.
|
||
|
"""
|
||
|
self.target_name = fn.name
|
||
|
self.target_cname = fn.cname(self.emitter.names)
|
||
|
self.num_bitmap_args = fn.sig.num_bitmap_args
|
||
|
if self.num_bitmap_args:
|
||
|
self.args = fn.args[: -self.num_bitmap_args]
|
||
|
else:
|
||
|
self.args = fn.args
|
||
|
self.arg_names = [arg.name for arg in self.args]
|
||
|
self.ret_type = fn.ret_type
|
||
|
|
||
|
def wrapper_name(self) -> str:
|
||
|
"""Return the name of the wrapper function."""
|
||
|
return "{}{}{}".format(
|
||
|
DUNDER_PREFIX,
|
||
|
self.target_name,
|
||
|
self.cl.name_prefix(self.emitter.names) if self.cl else "",
|
||
|
)
|
||
|
|
||
|
def use_goto(self) -> bool:
|
||
|
"""Do we use a goto for error handling (instead of straight return)?"""
|
||
|
return bool(self.cleanups or self.traceback_code)
|
||
|
|
||
|
def emit_header(self) -> None:
|
||
|
"""Emit the function header of the wrapper implementation."""
|
||
|
input_args = ", ".join(f"PyObject *obj_{arg}" for arg in self.arg_names)
|
||
|
self.emitter.emit_line(
|
||
|
"static PyObject *{name}({input_args}) {{".format(
|
||
|
name=self.wrapper_name(), input_args=input_args
|
||
|
)
|
||
|
)
|
||
|
|
||
|
def emit_arg_processing(
|
||
|
self, error: ErrorHandler | None = None, raise_exception: bool = True
|
||
|
) -> None:
|
||
|
"""Emit validation and unboxing of arguments."""
|
||
|
error = error or self.error()
|
||
|
bitmap_arg_index = 0
|
||
|
for arg_name, arg in zip(self.arg_names, self.args):
|
||
|
# Suppress the argument check for *args/**kwargs, since we know it must be right.
|
||
|
typ = arg.type if arg.kind not in (ARG_STAR, ARG_STAR2) else object_rprimitive
|
||
|
optional = arg in self.optional_args
|
||
|
generate_arg_check(
|
||
|
arg_name,
|
||
|
typ,
|
||
|
self.emitter,
|
||
|
error,
|
||
|
raise_exception=raise_exception,
|
||
|
optional=optional,
|
||
|
bitmap_arg_index=bitmap_arg_index,
|
||
|
)
|
||
|
if optional and typ.error_overlap:
|
||
|
bitmap_arg_index += 1
|
||
|
|
||
|
def emit_call(self, not_implemented_handler: str = "") -> None:
|
||
|
"""Emit call to the wrapper function.
|
||
|
|
||
|
If not_implemented_handler is non-empty, use this C code to handle
|
||
|
a NotImplemented return value (if it's possible based on the return type).
|
||
|
"""
|
||
|
native_args = ", ".join(f"arg_{arg}" for arg in self.arg_names)
|
||
|
if self.num_bitmap_args:
|
||
|
bitmap_args = ", ".join(
|
||
|
[bitmap_name(i) for i in reversed(range(self.num_bitmap_args))]
|
||
|
)
|
||
|
native_args = f"{native_args}, {bitmap_args}"
|
||
|
|
||
|
ret_type = self.ret_type
|
||
|
emitter = self.emitter
|
||
|
if ret_type.is_unboxed or self.use_goto():
|
||
|
# TODO: The Py_RETURN macros return the correct PyObject * with reference count
|
||
|
# handling. Are they relevant?
|
||
|
emitter.emit_line(
|
||
|
"{}retval = {}{}({});".format(
|
||
|
emitter.ctype_spaced(ret_type), NATIVE_PREFIX, self.target_cname, native_args
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_lines(*self.cleanups)
|
||
|
if ret_type.is_unboxed:
|
||
|
emitter.emit_error_check("retval", ret_type, "return NULL;")
|
||
|
emitter.emit_box("retval", "retbox", ret_type, declare_dest=True)
|
||
|
|
||
|
emitter.emit_line("return {};".format("retbox" if ret_type.is_unboxed else "retval"))
|
||
|
else:
|
||
|
if not_implemented_handler and not isinstance(ret_type, RInstance):
|
||
|
# The return value type may overlap with NotImplemented.
|
||
|
emitter.emit_line(
|
||
|
"PyObject *retbox = {}{}({});".format(
|
||
|
NATIVE_PREFIX, self.target_cname, native_args
|
||
|
)
|
||
|
)
|
||
|
emitter.emit_lines(
|
||
|
"if (retbox == Py_NotImplemented) {",
|
||
|
not_implemented_handler,
|
||
|
"}",
|
||
|
"return retbox;",
|
||
|
)
|
||
|
else:
|
||
|
emitter.emit_line(f"return {NATIVE_PREFIX}{self.target_cname}({native_args});")
|
||
|
# TODO: Tracebacks?
|
||
|
|
||
|
def error(self) -> ErrorHandler:
|
||
|
"""Figure out how to deal with errors in the wrapper."""
|
||
|
if self.cleanups or self.traceback_code:
|
||
|
# We'll have a label at the end with error handling code.
|
||
|
return GotoHandler("fail")
|
||
|
else:
|
||
|
# Nothing special needs to done to handle errors, so just return.
|
||
|
return ReturnHandler("NULL")
|
||
|
|
||
|
def emit_error_handling(self) -> None:
|
||
|
"""Emit error handling block at the end of the wrapper, if needed."""
|
||
|
emitter = self.emitter
|
||
|
if self.use_goto():
|
||
|
emitter.emit_label("fail")
|
||
|
emitter.emit_lines(*self.cleanups)
|
||
|
if self.traceback_code:
|
||
|
emitter.emit_line(self.traceback_code)
|
||
|
emitter.emit_line("return NULL;")
|
||
|
|
||
|
def finish(self) -> None:
|
||
|
self.emitter.emit_line("}")
|