Skip to content

Conversation

@p-sawicki
Copy link
Collaborator

@p-sawicki p-sawicki commented Nov 26, 2025

Fixes an issue where a subclass would have its vtable pointer set to the base class' vtable when there is a __new__ method defined in the base class. This resulted in the subclass constructor calling the setup function of the base class because mypyc transforms object.__new__ into the setup function.

The fix is to store the pointers to the setup functions in tp_methods of type objects and look them up dynamically when instantiating new objects.

@p-sawicki p-sawicki force-pushed the fix-vtable-with-inherited-dunder-new branch from a0ae332 to 3e8d126 Compare November 26, 2025 21:10
Copy link
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a viable fix, but will leave up to @JukkaL to double-check.

continue;
}

while (def->ml_name && strcmp(def->ml_name, "__internal_mypyc_setup")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This search may be slow, we know that this method should be first if it is there, so maybe only check the first method, and if it has a different name, move to next base?

if isinstance(typ_arg, NameExpr) and len(method_args) > 0 and method_args[0] == typ_arg.name:
subtype = builder.accept(expr.args[0])
return builder.add(Call(ir.setup, [subtype], expr.line))
return builder.call_c(setup_object, [subtype], expr.line)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment here explaining why a dynamic method dispatch is needed here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a fast path if class is known to not have subclasses -- we would still be able to use the old direct call, right?

if isinstance(typ_arg, NameExpr) and len(method_args) > 0 and method_args[0] == typ_arg.name:
subtype = builder.accept(expr.args[0])
return builder.add(Call(ir.setup, [subtype], expr.line))
return builder.call_c(setup_object, [subtype], expr.line)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a fast path if class is known to not have subclasses -- we would still be able to use the old direct call, right?

}
}
if (!def || !def->ml_name) {
PyErr_SetString(PyExc_LookupError, "Internal error: Unable to find object setup function");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add something to the error message that indicates that this error is related to mypyc, and not CPython.

if isinstance(typ_arg, NameExpr) and len(method_args) > 0 and method_args[0] == typ_arg.name:
subtype = builder.accept(expr.args[0])
return builder.add(Call(ir.setup, [subtype], expr.line))
classes = all_concrete_classes(ir)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ir.subclasses() seems like the correct way -- we need to use the slow path for an ABC that has only a single concrete subclass, I think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok that makes sense. i didn't add a test for this because for now __new__ cannot be called in an abstract class anyway as it inherits from a python type.

@p-sawicki p-sawicki merged commit 03b12a1 into python:master Nov 27, 2025
14 checks passed
@p-sawicki p-sawicki deleted the fix-vtable-with-inherited-dunder-new branch November 27, 2025 18:21
p-sawicki added a commit that referenced this pull request Nov 28, 2025
Fixes an issue where a subclass would have its vtable pointer set to the
base class' vtable when there is a `__new__` method defined in the base
class. This resulted in the subclass constructor calling the setup
function of the base class because mypyc transforms `object.__new__`
into the setup function.

The fix is to store the pointers to the setup functions in `tp_methods`
of type objects and look them up dynamically when instantiating new
objects.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants