Skip to content

Commit 37a2b9a

Browse files
authored
[mypyc] Add a mypyc_attr to support interpreted subclasses (#8004)
This operates by generating a "shadow vtable" containing pointers to glue methods that dispatch to the appropriate method via the C API. We then install those shadow vtables in interpreted subclasses so that overridden methods will be called. This does not support directly inheriting from traits, which I think will require generating vtables dynamically (and maybe some more nonsense too.) Closes #296.
1 parent 1013b63 commit 37a2b9a

File tree

8 files changed

+377
-56
lines changed

8 files changed

+377
-56
lines changed

mypy-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
typing_extensions>=3.7.4
2-
mypy_extensions>=0.4.0,<0.5.0
2+
mypy_extensions>=0.4.3,<0.5.0
33
typed_ast>=1.4.0,<1.5.0

mypyc/emitclass.py

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,10 @@ def emit_line() -> None:
202202
fields['tp_basicsize'] = base_size
203203

204204
if generate_full:
205-
emitter.emit_line('static PyObject *{}(void);'.format(setup_name))
205+
# Declare setup method that allocates and initializes an object. type is the
206+
# type of the class being initialized, which could be another class if there
207+
# is an interpreted subclass.
208+
emitter.emit_line('static PyObject *{}(PyTypeObject *type);'.format(setup_name))
206209
assert cl.ctor is not None
207210
emitter.emit_line(native_function_header(cl.ctor, emitter) + ';')
208211

@@ -216,7 +219,15 @@ def emit_line() -> None:
216219
generate_dealloc_for_class(cl, dealloc_name, clear_name, emitter)
217220
emit_line()
218221
generate_native_getters_and_setters(cl, emitter)
219-
vtable_name = generate_vtables(cl, vtable_setup_name, vtable_name, emitter)
222+
223+
if cl.allow_interpreted_subclasses:
224+
shadow_vtable_name = generate_vtables(
225+
cl, vtable_setup_name + "_shadow", vtable_name + "_shadow", emitter, shadow=True
226+
) # type: Optional[str]
227+
emit_line()
228+
else:
229+
shadow_vtable_name = None
230+
vtable_name = generate_vtables(cl, vtable_setup_name, vtable_name, emitter, shadow=False)
220231
emit_line()
221232
if needs_getseters:
222233
generate_getseter_declarations(cl, emitter)
@@ -241,7 +252,8 @@ def emit_line() -> None:
241252

242253
emitter.emit_line()
243254
if generate_full:
244-
generate_setup_for_class(cl, setup_name, defaults_fn, vtable_name, emitter)
255+
generate_setup_for_class(
256+
cl, setup_name, defaults_fn, vtable_name, shadow_vtable_name, emitter)
245257
emitter.emit_line()
246258
generate_constructor_for_class(
247259
cl, cl.ctor, init_fn, setup_name, vtable_name, emitter)
@@ -344,7 +356,8 @@ def generate_native_getters_and_setters(cl: ClassIR,
344356
def generate_vtables(base: ClassIR,
345357
vtable_setup_name: str,
346358
vtable_name: str,
347-
emitter: Emitter) -> str:
359+
emitter: Emitter,
360+
shadow: bool) -> str:
348361
"""Emit the vtables and vtable setup functions for a class.
349362
350363
This includes both the primary vtable and any trait implementation vtables.
@@ -354,13 +367,18 @@ def generate_vtables(base: ClassIR,
354367
emit empty array definitions to store the vtables and a function to
355368
populate them.
356369
370+
If shadow is True, generate "shadow vtables" that point to the
371+
shadow glue methods (which should dispatch via the Python C-API).
372+
357373
Returns the expression to use to refer to the vtable, which might be
358374
different than the name, if there are trait vtables.
375+
359376
"""
360377

361378
def trait_vtable_name(trait: ClassIR) -> str:
362-
return '{}_{}_trait_vtable'.format(
363-
base.name_prefix(emitter.names), trait.name_prefix(emitter.names))
379+
return '{}_{}_trait_vtable{}'.format(
380+
base.name_prefix(emitter.names), trait.name_prefix(emitter.names),
381+
'_shadow' if shadow else '')
364382

365383
# Emit array definitions with enough space for all the entries
366384
emitter.emit_line('static CPyVTableItem {}[{}];'.format(
@@ -376,13 +394,16 @@ def trait_vtable_name(trait: ClassIR) -> str:
376394
emitter.emit_line('{}{}(void)'.format(NATIVE_PREFIX, vtable_setup_name))
377395
emitter.emit_line('{')
378396

397+
if base.allow_interpreted_subclasses and not shadow:
398+
emitter.emit_line('{}{}_shadow();'.format(NATIVE_PREFIX, vtable_setup_name))
399+
379400
subtables = []
380401
for trait, vtable in base.trait_vtables.items():
381402
name = trait_vtable_name(trait)
382-
generate_vtable(vtable, name, emitter, [])
403+
generate_vtable(vtable, name, emitter, [], shadow)
383404
subtables.append((trait, name))
384405

385-
generate_vtable(base.vtable_entries, vtable_name, emitter, subtables)
406+
generate_vtable(base.vtable_entries, vtable_name, emitter, subtables, shadow)
386407

387408
emitter.emit_line('return 1;')
388409
emitter.emit_line('}')
@@ -393,7 +414,8 @@ def trait_vtable_name(trait: ClassIR) -> str:
393414
def generate_vtable(entries: VTableEntries,
394415
vtable_name: str,
395416
emitter: Emitter,
396-
subtables: List[Tuple[ClassIR, str]]) -> None:
417+
subtables: List[Tuple[ClassIR, str]],
418+
shadow: bool) -> None:
397419
emitter.emit_line('CPyVTableItem {}_scratch[] = {{'.format(vtable_name))
398420
if subtables:
399421
emitter.emit_line('/* Array of trait vtables */')
@@ -404,10 +426,11 @@ def generate_vtable(entries: VTableEntries,
404426

405427
for entry in entries:
406428
if isinstance(entry, VTableMethod):
429+
method = entry.shadow_method if shadow and entry.shadow_method else entry.method
407430
emitter.emit_line('(CPyVTableItem){}{}{},'.format(
408431
emitter.get_group_prefix(entry.method.decl),
409432
NATIVE_PREFIX,
410-
entry.method.cname(emitter.names)))
433+
method.cname(emitter.names)))
411434
else:
412435
cl, attr, is_setter = entry
413436
namer = native_setter_name if is_setter else native_getter_name
@@ -425,18 +448,27 @@ def generate_setup_for_class(cl: ClassIR,
425448
func_name: str,
426449
defaults_fn: Optional[FuncIR],
427450
vtable_name: str,
451+
shadow_vtable_name: Optional[str],
428452
emitter: Emitter) -> None:
429453
"""Generate a native function that allocates an instance of a class."""
430454
emitter.emit_line('static PyObject *')
431-
emitter.emit_line('{}(void)'.format(func_name))
455+
emitter.emit_line('{}(PyTypeObject *type)'.format(func_name))
432456
emitter.emit_line('{')
433457
emitter.emit_line('{} *self;'.format(cl.struct_name(emitter.names)))
434-
emitter.emit_line('self = ({struct} *){type_struct}->tp_alloc({type_struct}, 0);'.format(
435-
struct=cl.struct_name(emitter.names),
436-
type_struct=emitter.type_struct_name(cl)))
458+
emitter.emit_line('self = ({struct} *)type->tp_alloc(type, 0);'.format(
459+
struct=cl.struct_name(emitter.names)))
437460
emitter.emit_line('if (self == NULL)')
438461
emitter.emit_line(' return NULL;')
439-
emitter.emit_line('self->vtable = {};'.format(vtable_name))
462+
463+
if shadow_vtable_name:
464+
emitter.emit_line('if (type != {}) {{'.format(emitter.type_struct_name(cl)))
465+
emitter.emit_line('self->vtable = {};'.format(shadow_vtable_name))
466+
emitter.emit_line('} else {')
467+
emitter.emit_line('self->vtable = {};'.format(vtable_name))
468+
emitter.emit_line('}')
469+
else:
470+
emitter.emit_line('self->vtable = {};'.format(vtable_name))
471+
440472
for base in reversed(cl.base_mro):
441473
for attr, rtype in base.attributes.items():
442474
emitter.emit_line('self->{} = {};'.format(
@@ -464,7 +496,7 @@ def generate_constructor_for_class(cl: ClassIR,
464496
"""Generate a native function that allocates and initializes an instance of a class."""
465497
emitter.emit_line('{}'.format(native_function_header(fn, emitter)))
466498
emitter.emit_line('{')
467-
emitter.emit_line('PyObject *self = {}();'.format(setup_name))
499+
emitter.emit_line('PyObject *self = {}({});'.format(setup_name, emitter.type_struct_name(cl)))
468500
emitter.emit_line('if (self == NULL)')
469501
emitter.emit_line(' return NULL;')
470502
args = ', '.join(['self'] + [REG_PREFIX + arg.name for arg in fn.sig.args])
@@ -525,13 +557,15 @@ def generate_new_for_class(cl: ClassIR,
525557
'{}(PyTypeObject *type, PyObject *args, PyObject *kwds)'.format(func_name))
526558
emitter.emit_line('{')
527559
# TODO: Check and unbox arguments
528-
emitter.emit_line('if (type != {}) {{'.format(emitter.type_struct_name(cl)))
529-
emitter.emit_line(
530-
'PyErr_SetString(PyExc_TypeError, "interpreted classes cannot inherit from compiled");')
531-
emitter.emit_line('return NULL;')
532-
emitter.emit_line('}')
560+
if not cl.allow_interpreted_subclasses:
561+
emitter.emit_line('if (type != {}) {{'.format(emitter.type_struct_name(cl)))
562+
emitter.emit_line(
563+
'PyErr_SetString(PyExc_TypeError, "interpreted classes cannot inherit from compiled");'
564+
)
565+
emitter.emit_line('return NULL;')
566+
emitter.emit_line('}')
533567

534-
emitter.emit_line('return {}();'.format(setup_name))
568+
emitter.emit_line('return {}(type);'.format(setup_name))
535569
emitter.emit_line('}')
536570

537571

0 commit comments

Comments
 (0)