Skip to content

Commit 37a2b9a

Browse files
authored
[mypyc] Add a mypyc_attr to support interpreted subclasses (python#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 python#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy