blob: a7d2497ae272cc52789df7ec8d3aba819f003153 [file] [log] [blame]
"""Code generation for native classes and related wrappers."""
from typing import Optional, List, Tuple, Dict, Callable, Mapping, Set
from collections import OrderedDict
from mypyc.common import PREFIX, NATIVE_PREFIX, REG_PREFIX
from mypyc.emit import Emitter, HeaderDeclaration
from mypyc.emitfunc import native_function_header, native_getter_name, native_setter_name
from mypyc.emitwrapper import (
generate_dunder_wrapper, generate_hash_wrapper, generate_richcompare_wrapper,
generate_bool_wrapper, generate_get_wrapper,
)
from mypyc.ops import (
ClassIR, FuncIR, FuncDecl, RType, RTuple, object_rprimitive,
VTableMethod, VTableEntries,
FUNC_STATICMETHOD, FUNC_CLASSMETHOD,
)
from mypyc.sametype import is_same_type
from mypyc.namegen import NameGenerator
def native_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
return '{}{}'.format(NATIVE_PREFIX, fn.cname(emitter.names))
def wrapper_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
return '{}{}'.format(PREFIX, fn.cname(emitter.names))
# We maintain a table from dunder function names to struct slots they
# correspond to and functions that generate a wrapper (if necessary)
# and return the function name to stick in the slot.
# TODO: Add remaining dunder methods
SlotGenerator = Callable[[ClassIR, FuncIR, Emitter], str]
SlotTable = Mapping[str, Tuple[str, SlotGenerator]]
SLOT_DEFS = {
'__init__': ('tp_init', lambda c, t, e: generate_init_for_class(c, t, e)),
'__call__': ('tp_call', wrapper_slot),
'__str__': ('tp_str', native_slot),
'__repr__': ('tp_repr', native_slot),
'__next__': ('tp_iternext', native_slot),
'__iter__': ('tp_iter', native_slot),
'__hash__': ('tp_hash', generate_hash_wrapper),
'__get__': ('tp_descr_get', generate_get_wrapper),
} # type: SlotTable
AS_MAPPING_SLOT_DEFS = {
'__getitem__': ('mp_subscript', generate_dunder_wrapper),
} # type: SlotTable
AS_NUMBER_SLOT_DEFS = {
'__bool__': ('nb_bool', generate_bool_wrapper),
} # type: SlotTable
AS_ASYNC_SLOT_DEFS = {
'__await__': ('am_await', native_slot),
'__aiter__': ('am_aiter', native_slot),
'__anext__': ('am_anext', native_slot),
} # type: SlotTable
SIDE_TABLES = [
('as_mapping', 'PyMappingMethods', AS_MAPPING_SLOT_DEFS),
('as_number', 'PyNumberMethods', AS_NUMBER_SLOT_DEFS),
('as_async', 'PyAsyncMethods', AS_ASYNC_SLOT_DEFS),
]
# Slots that need to always be filled in because they don't get
# inherited right.
ALWAYS_FILL = {
'__hash__',
}
def generate_slots(cl: ClassIR, table: SlotTable, emitter: Emitter) -> Dict[str, str]:
fields = OrderedDict() # type: Dict[str, str]
# Sort for determinism on Python 3.5
for name, (slot, generator) in sorted(table.items()):
method_cls = cl.get_method_and_class(name)
if method_cls and (method_cls[1] == cl or name in ALWAYS_FILL):
fields[slot] = generator(cl, method_cls[0], emitter)
return fields
def generate_class_type_decl(cl: ClassIR, c_emitter: Emitter,
external_emitter: Emitter,
emitter: Emitter) -> None:
context = c_emitter.context
name = emitter.type_struct_name(cl)
context.declarations[name] = HeaderDeclaration(
'PyTypeObject *{};'.format(emitter.type_struct_name(cl)),
needs_export=True)
# If this is a non-extension class, all we want is the type object decl.
if not cl.is_ext_class:
return
generate_object_struct(cl, external_emitter)
generate_full = not cl.is_trait and not cl.builtin_base
if generate_full:
declare_native_getters_and_setters(cl, emitter)
context.declarations[emitter.native_function_name(cl.ctor)] = HeaderDeclaration(
'{};'.format(native_function_header(cl.ctor, emitter)),
needs_export=True,
)
def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
"""Generate C code for a class.
This is the main entry point to the module.
"""
name = cl.name
name_prefix = cl.name_prefix(emitter.names)
setup_name = '{}_setup'.format(name_prefix)
new_name = '{}_new'.format(name_prefix)
members_name = '{}_members'.format(name_prefix)
getseters_name = '{}_getseters'.format(name_prefix)
vtable_name = '{}_vtable'.format(name_prefix)
traverse_name = '{}_traverse'.format(name_prefix)
clear_name = '{}_clear'.format(name_prefix)
dealloc_name = '{}_dealloc'.format(name_prefix)
methods_name = '{}_methods'.format(name_prefix)
vtable_setup_name = '{}_trait_vtable_setup'.format(name_prefix)
fields = OrderedDict() # type: Dict[str, str]
fields['tp_name'] = '"{}"'.format(name)
generate_full = not cl.is_trait and not cl.builtin_base
needs_getseters = not cl.is_generated
if generate_full:
fields['tp_new'] = new_name
fields['tp_dealloc'] = '(destructor){}_dealloc'.format(name_prefix)
fields['tp_traverse'] = '(traverseproc){}_traverse'.format(name_prefix)
fields['tp_clear'] = '(inquiry){}_clear'.format(name_prefix)
if needs_getseters:
fields['tp_getset'] = getseters_name
fields['tp_methods'] = methods_name
def emit_line() -> None:
emitter.emit_line()
emit_line()
# If the class has a method to initialize default attribute
# values, we need to call it during initialization.
defaults_fn = cl.get_method('__mypyc_defaults_setup')
# If there is a __init__ method, we'll use it in the native constructor.
init_fn = cl.get_method('__init__')
# Fill out slots in the type object from dunder methods.
fields.update(generate_slots(cl, SLOT_DEFS, emitter))
# Fill out dunder methods that live in tables hanging off the side.
for table_name, type, slot_defs in SIDE_TABLES:
slots = generate_slots(cl, slot_defs, emitter)
if slots:
table_struct_name = generate_side_table_for_class(cl, table_name, type, slots, emitter)
fields['tp_{}'.format(table_name)] = '&{}'.format(table_struct_name)
richcompare_name = generate_richcompare_wrapper(cl, emitter)
if richcompare_name:
fields['tp_richcompare'] = richcompare_name
# If the class inherits from python, make space for a __dict__
struct_name = cl.struct_name(emitter.names)
if cl.builtin_base:
base_size = 'sizeof({})'.format(cl.builtin_base)
elif cl.is_trait:
base_size = 'sizeof(PyObject)'
else:
base_size = 'sizeof({})'.format(struct_name)
# Since our types aren't allocated using type() we need to
# populate these fields ourselves if we want them to have correct
# values. PyType_Ready will inherit the offsets from tp_base but
# that isn't what we want.
# XXX: there is no reason for the __weakref__ stuff to be mixed up with __dict__
if cl.has_dict:
# __dict__ lives right after the struct and __weakref__ lives right after that
# TODO: They should get members in the struct instead of doing this nonsense.
weak_offset = '{} + sizeof(PyObject *)'.format(base_size)
emitter.emit_lines(
'PyMemberDef {}[] = {{'.format(members_name),
'{{"__dict__", T_OBJECT_EX, {}, 0, NULL}},'.format(base_size),
'{{"__weakref__", T_OBJECT_EX, {}, 0, NULL}},'.format(weak_offset),
'{0}',
'};',
)
fields['tp_members'] = members_name
fields['tp_basicsize'] = '{} + 2*sizeof(PyObject *)'.format(base_size)
fields['tp_dictoffset'] = base_size
fields['tp_weaklistoffset'] = weak_offset
else:
fields['tp_basicsize'] = base_size
if generate_full:
emitter.emit_line('static PyObject *{}(void);'.format(setup_name))
assert cl.ctor is not None
emitter.emit_line(native_function_header(cl.ctor, emitter) + ';')
emit_line()
generate_new_for_class(cl, new_name, vtable_name, setup_name, emitter)
emit_line()
generate_traverse_for_class(cl, traverse_name, emitter)
emit_line()
generate_clear_for_class(cl, clear_name, emitter)
emit_line()
generate_dealloc_for_class(cl, dealloc_name, clear_name, emitter)
emit_line()
generate_native_getters_and_setters(cl, emitter)
vtable_name = generate_vtables(cl, vtable_setup_name, vtable_name, emitter)
emit_line()
if needs_getseters:
generate_getseter_declarations(cl, emitter)
emit_line()
generate_getseters_table(cl, getseters_name, emitter)
emit_line()
generate_methods_table(cl, methods_name, emitter)
emit_line()
flags = ['Py_TPFLAGS_DEFAULT', 'Py_TPFLAGS_HEAPTYPE', 'Py_TPFLAGS_BASETYPE']
if generate_full:
flags.append('Py_TPFLAGS_HAVE_GC')
fields['tp_flags'] = ' | '.join(flags)
emitter.emit_line("static PyTypeObject {}_template_ = {{".format(emitter.type_struct_name(cl)))
emitter.emit_line("PyVarObject_HEAD_INIT(NULL, 0)")
for field, value in fields.items():
emitter.emit_line(".{} = {},".format(field, value))
emitter.emit_line("};")
emitter.emit_line("static PyTypeObject *{t}_template = &{t}_template_;".format(
t=emitter.type_struct_name(cl)))
emitter.emit_line()
if generate_full:
generate_setup_for_class(cl, setup_name, defaults_fn, vtable_name, emitter)
emitter.emit_line()
generate_constructor_for_class(
cl, cl.ctor, init_fn, setup_name, vtable_name, emitter)
emitter.emit_line()
if needs_getseters:
generate_getseters(cl, emitter)
def getter_name(cl: ClassIR, attribute: str, names: NameGenerator) -> str:
return names.private_name(cl.module_name, '{}_get{}'.format(cl.name, attribute))
def setter_name(cl: ClassIR, attribute: str, names: NameGenerator) -> str:
return names.private_name(cl.module_name, '{}_set{}'.format(cl.name, attribute))
def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None:
seen_attrs = set() # type: Set[Tuple[str, RType]]
lines = [] # type: List[str]
lines += ['typedef struct {',
'PyObject_HEAD',
'CPyVTableItem *vtable;']
for base in reversed(cl.base_mro):
if not base.is_trait:
for attr, rtype in base.attributes.items():
if (attr, rtype) not in seen_attrs:
lines.append('{}{};'.format(emitter.ctype_spaced(rtype),
emitter.attr(attr)))
seen_attrs.add((attr, rtype))
if isinstance(rtype, RTuple):
emitter.declare_tuple_struct(rtype)
lines.append('}} {};'.format(cl.struct_name(emitter.names)))
lines.append('')
emitter.context.declarations[cl.struct_name(emitter.names)] = HeaderDeclaration(
lines,
is_type=True
)
def declare_native_getters_and_setters(cl: ClassIR,
emitter: Emitter) -> None:
decls = emitter.context.declarations
for attr, rtype in cl.attributes.items():
getter_name = native_getter_name(cl, attr, emitter.names)
setter_name = native_setter_name(cl, attr, emitter.names)
decls[getter_name] = HeaderDeclaration(
'{}{}({} *self);'.format(emitter.ctype_spaced(rtype),
getter_name,
cl.struct_name(emitter.names)),
needs_export=True,
)
decls[setter_name] = HeaderDeclaration(
'bool {}({} *self, {}value);'.format(native_setter_name(cl, attr, emitter.names),
cl.struct_name(emitter.names),
emitter.ctype_spaced(rtype)),
needs_export=True,
)
def generate_native_getters_and_setters(cl: ClassIR,
emitter: Emitter) -> None:
for attr, rtype in cl.attributes.items():
attr_field = emitter.attr(attr)
# Native getter
emitter.emit_line('{}{}({} *self)'.format(emitter.ctype_spaced(rtype),
native_getter_name(cl, attr, emitter.names),
cl.struct_name(emitter.names)))
emitter.emit_line('{')
if rtype.is_refcounted:
emit_undefined_check(rtype, emitter, attr_field, '==')
emitter.emit_lines(
'PyErr_SetString(PyExc_AttributeError, "attribute {} of {} undefined");'.format(
repr(attr), repr(cl.name)),
'} else {')
emitter.emit_inc_ref('self->{}'.format(attr_field), rtype)
emitter.emit_line('}')
emitter.emit_line('return self->{};'.format(attr_field))
emitter.emit_line('}')
emitter.emit_line()
# Native setter
emitter.emit_line(
'bool {}({} *self, {}value)'.format(native_setter_name(cl, attr, emitter.names),
cl.struct_name(emitter.names),
emitter.ctype_spaced(rtype)))
emitter.emit_line('{')
if rtype.is_refcounted:
emit_undefined_check(rtype, emitter, attr_field, '!=')
emitter.emit_dec_ref('self->{}'.format(attr_field), rtype)
emitter.emit_line('}')
# This steal the reference to src, so we don't need to increment the arg
emitter.emit_lines('self->{} = value;'.format(attr_field),
'return 1;',
'}')
emitter.emit_line()
def generate_vtables(base: ClassIR,
vtable_setup_name: str,
vtable_name: str,
emitter: Emitter) -> str:
"""Emit the vtables and vtable setup functions for a class.
This includes both the primary vtable and any trait implementation vtables.
To account for both dynamic loading and dynamic class creation,
vtables are populated dynamically at class creation time, so we
emit empty array definitions to store the vtables and a function to
populate them.
Returns the expression to use to refer to the vtable, which might be
different than the name, if there are trait vtables.
"""
def trait_vtable_name(trait: ClassIR) -> str:
return '{}_{}_trait_vtable'.format(
base.name_prefix(emitter.names), trait.name_prefix(emitter.names))
# Emit array definitions with enough space for all the entries
emitter.emit_line('static CPyVTableItem {}[{}];'.format(
vtable_name,
max(1, len(base.vtable_entries) + 2 * len(base.trait_vtables))))
for trait, vtable in base.trait_vtables.items():
emitter.emit_line('static CPyVTableItem {}[{}];'.format(
trait_vtable_name(trait),
max(1, len(vtable))))
# Emit vtable setup function
emitter.emit_line('static bool')
emitter.emit_line('{}{}(void)'.format(NATIVE_PREFIX, vtable_setup_name))
emitter.emit_line('{')
subtables = []
for trait, vtable in base.trait_vtables.items():
name = trait_vtable_name(trait)
generate_vtable(vtable, name, emitter, [])
subtables.append((trait, name))
generate_vtable(base.vtable_entries, vtable_name, emitter, subtables)
emitter.emit_line('return 1;')
emitter.emit_line('}')
return vtable_name if not subtables else "{} + {}".format(vtable_name, len(subtables) * 2)
def generate_vtable(entries: VTableEntries,
vtable_name: str,
emitter: Emitter,
subtables: List[Tuple[ClassIR, str]]) -> None:
emitter.emit_line('CPyVTableItem {}_scratch[] = {{'.format(vtable_name))
if subtables:
emitter.emit_line('/* Array of trait vtables */')
for trait, table in subtables:
emitter.emit_line('(CPyVTableItem){}, (CPyVTableItem){},'.format(
emitter.type_struct_name(trait), table))
emitter.emit_line('/* Start of real vtable */')
for entry in entries:
if isinstance(entry, VTableMethod):
emitter.emit_line('(CPyVTableItem){}{}{},'.format(
emitter.get_group_prefix(entry.method.decl),
NATIVE_PREFIX,
entry.method.cname(emitter.names)))
else:
cl, attr, is_setter = entry
namer = native_setter_name if is_setter else native_getter_name
emitter.emit_line('(CPyVTableItem){}{},'.format(
emitter.get_group_prefix(cl),
namer(cl, attr, emitter.names)))
# msvc doesn't allow empty arrays; maybe allowing them at all is an extension?
if not entries:
emitter.emit_line('NULL')
emitter.emit_line('};')
emitter.emit_line('memcpy({name}, {name}_scratch, sizeof({name}));'.format(name=vtable_name))
def generate_setup_for_class(cl: ClassIR,
func_name: str,
defaults_fn: Optional[FuncIR],
vtable_name: str,
emitter: Emitter) -> None:
"""Generate a native function that allocates an instance of a class."""
emitter.emit_line('static PyObject *')
emitter.emit_line('{}(void)'.format(func_name))
emitter.emit_line('{')
emitter.emit_line('{} *self;'.format(cl.struct_name(emitter.names)))
emitter.emit_line('self = ({struct} *){type_struct}->tp_alloc({type_struct}, 0);'.format(
struct=cl.struct_name(emitter.names),
type_struct=emitter.type_struct_name(cl)))
emitter.emit_line('if (self == NULL)')
emitter.emit_line(' return NULL;')
emitter.emit_line('self->vtable = {};'.format(vtable_name))
for base in reversed(cl.base_mro):
for attr, rtype in base.attributes.items():
emitter.emit_line('self->{} = {};'.format(
emitter.attr(attr), emitter.c_undefined_value(rtype)))
# Initialize attributes to default values, if necessary
if defaults_fn is not None:
emitter.emit_lines(
'if ({}{}((PyObject *)self) == 0) {{'.format(
NATIVE_PREFIX, defaults_fn.cname(emitter.names)),
'Py_DECREF(self);',
'return NULL;',
'}')
emitter.emit_line('return (PyObject *)self;')
emitter.emit_line('}')
def generate_constructor_for_class(cl: ClassIR,
fn: FuncDecl,
init_fn: Optional[FuncIR],
setup_name: str,
vtable_name: str,
emitter: Emitter) -> None:
"""Generate a native function that allocates and initializes an instance of a class."""
emitter.emit_line('{}'.format(native_function_header(fn, emitter)))
emitter.emit_line('{')
emitter.emit_line('PyObject *self = {}();'.format(setup_name))
emitter.emit_line('if (self == NULL)')
emitter.emit_line(' return NULL;')
args = ', '.join(['self'] + [REG_PREFIX + arg.name for arg in fn.sig.args])
if init_fn is not None:
emitter.emit_line('char res = {}{}{}({});'.format(
emitter.get_group_prefix(init_fn.decl),
NATIVE_PREFIX, init_fn.cname(emitter.names), args))
emitter.emit_line('if (res == 2) {')
emitter.emit_line('Py_DECREF(self);')
emitter.emit_line('return NULL;')
emitter.emit_line('}')
# If there is a nontrivial ctor that we didn't define, invoke it via tp_init
elif len(fn.sig.args) > 1:
emitter.emit_line(
'int res = {}->tp_init({});'.format(
emitter.type_struct_name(cl),
args))
emitter.emit_line('if (res < 0) {')
emitter.emit_line('Py_DECREF(self);')
emitter.emit_line('return NULL;')
emitter.emit_line('}')
emitter.emit_line('return self;')
emitter.emit_line('}')
def generate_init_for_class(cl: ClassIR,
init_fn: FuncIR,
emitter: Emitter) -> str:
"""Generate an init function suitable for use as tp_init.
tp_init needs to be a function that returns an int, and our
__init__ methods return a PyObject. Translate NULL to -1,
everything else to 0.
"""
func_name = '{}_init'.format(cl.name_prefix(emitter.names))
emitter.emit_line('static int')
emitter.emit_line(
'{}(PyObject *self, PyObject *args, PyObject *kwds)'.format(func_name))
emitter.emit_line('{')
emitter.emit_line('return {}{}(self, args, kwds) != NULL ? 0 : -1;'.format(
PREFIX, init_fn.cname(emitter.names)))
emitter.emit_line('}')
return func_name
def generate_new_for_class(cl: ClassIR,
func_name: str,
vtable_name: str,
setup_name: str,
emitter: Emitter) -> None:
emitter.emit_line('static PyObject *')
emitter.emit_line(
'{}(PyTypeObject *type, PyObject *args, PyObject *kwds)'.format(func_name))
emitter.emit_line('{')
# TODO: Check and unbox arguments
emitter.emit_line('if (type != {}) {{'.format(emitter.type_struct_name(cl)))
emitter.emit_line(
'PyErr_SetString(PyExc_TypeError, "interpreted classes cannot inherit from compiled");')
emitter.emit_line('return NULL;')
emitter.emit_line('}')
emitter.emit_line('return {}();'.format(setup_name))
emitter.emit_line('}')
def generate_traverse_for_class(cl: ClassIR,
func_name: str,
emitter: Emitter) -> None:
"""Emit function that performs cycle GC traversal of an instance."""
emitter.emit_line('static int')
emitter.emit_line('{}({} *self, visitproc visit, void *arg)'.format(
func_name,
cl.struct_name(emitter.names)))
emitter.emit_line('{')
for base in reversed(cl.base_mro):
for attr, rtype in base.attributes.items():
emitter.emit_gc_visit('self->{}'.format(emitter.attr(attr)), rtype)
if cl.has_dict:
struct_name = cl.struct_name(emitter.names)
# __dict__ lives right after the struct and __weakref__ lives right after that
emitter.emit_gc_visit('*((PyObject **)((char *)self + sizeof({})))'.format(
struct_name), object_rprimitive)
emitter.emit_gc_visit(
'*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({})))'.format(
struct_name),
object_rprimitive)
emitter.emit_line('return 0;')
emitter.emit_line('}')
def generate_clear_for_class(cl: ClassIR,
func_name: str,
emitter: Emitter) -> None:
emitter.emit_line('static int')
emitter.emit_line('{}({} *self)'.format(func_name, cl.struct_name(emitter.names)))
emitter.emit_line('{')
for base in reversed(cl.base_mro):
for attr, rtype in base.attributes.items():
emitter.emit_gc_clear('self->{}'.format(emitter.attr(attr)), rtype)
if cl.has_dict:
struct_name = cl.struct_name(emitter.names)
# __dict__ lives right after the struct and __weakref__ lives right after that
emitter.emit_gc_clear('*((PyObject **)((char *)self + sizeof({})))'.format(
struct_name), object_rprimitive)
emitter.emit_gc_clear(
'*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({})))'.format(
struct_name),
object_rprimitive)
emitter.emit_line('return 0;')
emitter.emit_line('}')
def generate_dealloc_for_class(cl: ClassIR,
dealloc_func_name: str,
clear_func_name: str,
emitter: Emitter) -> None:
emitter.emit_line('static void')
emitter.emit_line('{}({} *self)'.format(dealloc_func_name, cl.struct_name(emitter.names)))
emitter.emit_line('{')
emitter.emit_line('PyObject_GC_UnTrack(self);')
emitter.emit_line('{}(self);'.format(clear_func_name))
emitter.emit_line('Py_TYPE(self)->tp_free((PyObject *)self);')
emitter.emit_line('}')
def generate_methods_table(cl: ClassIR,
name: str,
emitter: Emitter) -> None:
emitter.emit_line('static PyMethodDef {}[] = {{'.format(name))
for fn in cl.methods.values():
if fn.decl.is_prop_setter or fn.decl.is_prop_getter:
continue
emitter.emit_line('{{"{}",'.format(fn.name))
emitter.emit_line(' (PyCFunction){}{},'.format(PREFIX, fn.cname(emitter.names)))
flags = ['METH_VARARGS', 'METH_KEYWORDS']
if fn.decl.kind == FUNC_STATICMETHOD:
flags.append('METH_STATIC')
elif fn.decl.kind == FUNC_CLASSMETHOD:
flags.append('METH_CLASS')
emitter.emit_line(' {}, NULL}},'.format(' | '.join(flags)))
# Provide a default __getstate__ and __setstate__
if not cl.has_method('__setstate__') and not cl.has_method('__getstate__'):
emitter.emit_lines(
'{"__setstate__", (PyCFunction)CPyPickle_SetState, METH_O, NULL},',
'{"__getstate__", (PyCFunction)CPyPickle_GetState, METH_NOARGS, NULL},',
)
emitter.emit_line('{NULL} /* Sentinel */')
emitter.emit_line('};')
def generate_side_table_for_class(cl: ClassIR,
name: str,
type: str,
slots: Dict[str, str],
emitter: Emitter) -> Optional[str]:
name = '{}_{}'.format(cl.name_prefix(emitter.names), name)
emitter.emit_line('static {} {} = {{'.format(type, name))
for field, value in slots.items():
emitter.emit_line(".{} = {},".format(field, value))
emitter.emit_line("};")
return name
def generate_getseter_declarations(cl: ClassIR, emitter: Emitter) -> None:
if not cl.is_trait:
for attr in cl.attributes:
emitter.emit_line('static PyObject *')
emitter.emit_line('{}({} *self, void *closure);'.format(
getter_name(cl, attr, emitter.names),
cl.struct_name(emitter.names)))
emitter.emit_line('static int')
emitter.emit_line('{}({} *self, PyObject *value, void *closure);'.format(
setter_name(cl, attr, emitter.names),
cl.struct_name(emitter.names)))
for prop in cl.properties:
# Generate getter declaration
emitter.emit_line('static PyObject *')
emitter.emit_line('{}({} *self, void *closure);'.format(
getter_name(cl, prop, emitter.names),
cl.struct_name(emitter.names)))
# Generate property setter declaration if a setter exists
if cl.properties[prop][1]:
emitter.emit_line('static int')
emitter.emit_line('{}({} *self, PyObject *value, void *closure);'.format(
setter_name(cl, prop, emitter.names),
cl.struct_name(emitter.names)))
def generate_getseters_table(cl: ClassIR,
name: str,
emitter: Emitter) -> None:
emitter.emit_line('static PyGetSetDef {}[] = {{'.format(name))
if not cl.is_trait:
for attr in cl.attributes:
emitter.emit_line('{{"{}",'.format(attr))
emitter.emit_line(' (getter){}, (setter){},'.format(
getter_name(cl, attr, emitter.names), setter_name(cl, attr, emitter.names)))
emitter.emit_line(' NULL, NULL},')
for prop in cl.properties:
emitter.emit_line('{{"{}",'.format(prop))
emitter.emit_line(' (getter){},'.format(getter_name(cl, prop, emitter.names)))
setter = cl.properties[prop][1]
if setter:
emitter.emit_line(' (setter){},'.format(setter_name(cl, prop, emitter.names)))
emitter.emit_line('NULL, NULL},')
else:
emitter.emit_line('NULL, NULL, NULL},')
emitter.emit_line('{NULL} /* Sentinel */')
emitter.emit_line('};')
def generate_getseters(cl: ClassIR, emitter: Emitter) -> None:
if not cl.is_trait:
for i, (attr, rtype) in enumerate(cl.attributes.items()):
generate_getter(cl, attr, rtype, emitter)
emitter.emit_line('')
generate_setter(cl, attr, rtype, emitter)
if i < len(cl.attributes) - 1:
emitter.emit_line('')
for prop, (getter, setter) in cl.properties.items():
rtype = getter.sig.ret_type
emitter.emit_line('')
generate_readonly_getter(cl, prop, rtype, getter, emitter)
if setter:
arg_type = setter.sig.args[1].type
emitter.emit_line('')
generate_property_setter(cl, prop, arg_type, setter, emitter)
def generate_getter(cl: ClassIR,
attr: str,
rtype: RType,
emitter: Emitter) -> None:
attr_field = emitter.attr(attr)
emitter.emit_line('static PyObject *')
emitter.emit_line('{}({} *self, void *closure)'.format(getter_name(cl, attr, emitter.names),
cl.struct_name(emitter.names)))
emitter.emit_line('{')
emit_undefined_check(rtype, emitter, attr_field, '==')
emitter.emit_line('PyErr_SetString(PyExc_AttributeError,')
emitter.emit_line(' "attribute {} of {} undefined");'.format(repr(attr),
repr(cl.name)))
emitter.emit_line('return NULL;')
emitter.emit_line('}')
emitter.emit_inc_ref('self->{}'.format(attr_field), rtype)
emitter.emit_box('self->{}'.format(attr_field), 'retval', rtype, declare_dest=True)
emitter.emit_line('return retval;')
emitter.emit_line('}')
def generate_setter(cl: ClassIR,
attr: str,
rtype: RType,
emitter: Emitter) -> None:
attr_field = emitter.attr(attr)
emitter.emit_line('static int')
emitter.emit_line('{}({} *self, PyObject *value, void *closure)'.format(
setter_name(cl, attr, emitter.names),
cl.struct_name(emitter.names)))
emitter.emit_line('{')
if rtype.is_refcounted:
emit_undefined_check(rtype, emitter, attr_field, '!=')
emitter.emit_dec_ref('self->{}'.format(attr_field), rtype)
emitter.emit_line('}')
emitter.emit_line('if (value != NULL) {')
if rtype.is_unboxed:
emitter.emit_unbox('value', 'tmp', rtype, custom_failure='return -1;', declare_dest=True)
elif is_same_type(rtype, object_rprimitive):
emitter.emit_line('PyObject *tmp = value;')
else:
emitter.emit_cast('value', 'tmp', rtype, declare_dest=True)
emitter.emit_lines('if (!tmp)',
' return -1;')
emitter.emit_inc_ref('tmp', rtype)
emitter.emit_line('self->{} = tmp;'.format(attr_field))
emitter.emit_line('} else')
emitter.emit_line(' self->{} = {};'.format(attr_field, emitter.c_undefined_value(rtype)))
emitter.emit_line('return 0;')
emitter.emit_line('}')
def generate_readonly_getter(cl: ClassIR,
attr: str,
rtype: RType,
func_ir: FuncIR,
emitter: Emitter) -> None:
emitter.emit_line('static PyObject *')
emitter.emit_line('{}({} *self, void *closure)'.format(getter_name(cl, attr, emitter.names),
cl.struct_name(emitter.names)))
emitter.emit_line('{')
if rtype.is_unboxed:
emitter.emit_line('{}retval = {}{}((PyObject *) self);'.format(
emitter.ctype_spaced(rtype), NATIVE_PREFIX, func_ir.cname(emitter.names)))
emitter.emit_box('retval', 'retbox', rtype, declare_dest=True)
emitter.emit_line('return retbox;')
else:
emitter.emit_line('return {}{}((PyObject *) self);'.format(NATIVE_PREFIX,
func_ir.cname(emitter.names)))
emitter.emit_line('}')
def generate_property_setter(cl: ClassIR,
attr: str,
arg_type: RType,
func_ir: FuncIR,
emitter: Emitter) -> None:
emitter.emit_line('static int')
emitter.emit_line('{}({} *self, PyObject *value, void *closure)'.format(
setter_name(cl, attr, emitter.names),
cl.struct_name(emitter.names)))
emitter.emit_line('{')
if arg_type.is_unboxed:
emitter.emit_unbox('value', 'tmp', arg_type, custom_failure='return -1;',
declare_dest=True)
emitter.emit_line('{}{}((PyObject *) self, tmp);'.format(
NATIVE_PREFIX,
func_ir.cname(emitter.names)))
else:
emitter.emit_line('{}{}((PyObject *) self, value);'.format(
NATIVE_PREFIX,
func_ir.cname(emitter.names)))
emitter.emit_line('return 0;')
emitter.emit_line('}')
def emit_undefined_check(rtype: RType, emitter: Emitter, attr: str, compare: str) -> None:
if isinstance(rtype, RTuple):
attr_expr = 'self->{}'.format(attr)
emitter.emit_line(
'if ({}) {{'.format(
emitter.tuple_undefined_check_cond(
rtype, attr_expr, emitter.c_undefined_value, compare)))
else:
emitter.emit_line(
'if (self->{} {} {}) {{'.format(attr, compare, emitter.c_undefined_value(rtype)))