KEMBAR78
[mypyc] Use defined __new__ method in tp_new and constructor by p-sawicki · Pull Request #19739 · python/mypy · GitHub
Skip to content

Conversation

p-sawicki
Copy link
Collaborator

Fixes #16012

mypyc ignored custom implementations of __new__ because, even though a C function representing the method was generated, it was called neither in the type constructor nor in the method assigned to the tp_new pointer.

Now if there's a __new__ method defined for a type, the corresponding function is called in place of the setup function which is responsible for allocating memory for new objects and initializing their attributes to default values.

The setup function is still called when creating instances of the type as calls resolving to object.__new__() are transformed to call the setup function. This way, __new__ can return instances of other types and instances of the type of the class where __new__ is defined are setup correctly.

There are a couple of limitations:

  • Programs with super().__new__() calls in __new__ methods of non-native classes are rejected because it's more difficult to resolve the setup function for non-native classes but this could probably be supported in the future.
  • Similarly, programs are rejected when a class inherits from a non-compiled class. In this case calling the tp_new method of the parent type results in an error because cpython expects the sub type to use a wrapper for tp_new which compiled classes don't. Allowing this would require compiled types to be initialized more closely to the way cpython does it which might need a lot of work.
  • Lastly, when __new__ is annotated with @classmethod, calling it without the type parameter works in compiled code but raises an error in interpreted. I'm not sure of the reason and it's difficult to make it a compiler error because it's outside of what mypyc sees.

Co-authored-by: Brian Schubert <brianm.schubert@gmail.com>
Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

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

Thanks for the test updates! I still have a few more ideas about test coverage.

val: int

def __new__(cls, val: int):
obj = super().__new__(cls)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Test what happens if you call e.g. super().__new__(str). The specialization should only trigger if the first argument is the cls argument of the surrounding method, 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.

Right, good point. Restricted the transformation to this case only and added a test.


t0 = Test0()
assert t0.val == 0
t0 = Test0.__new__(Test0, 1)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you have a test for calling ClsName.__new__(...) in compiled code? Also add an irbuild test for this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, there's some calls like this in the run test:

def test_arg_mismatch() -> None:
    t0 = Test0()
    assert t0.val == 0
    t0 = Test0.__new__(1)
    assert t0.val == 1
    with assertRaises(TypeError, "__new__() missing required argument 'val'"):
        t1 = Test1()
    t1 = Test1.__new__(Test1, 2)
    assert t1.val == 2
    with assertRaises(TypeError, "__new__() takes at most 0 arguments"):
        t2 = Test2(42)
    t2 = Test2.__new__(Test2)
    with assertRaises(AttributeError, "attribute 'val' of 'Test2' undefined"):
        print(t2.val)

i've added an irbuild test too.

Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

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

One minor comment, looks good overall.


new_node: SymbolNode | None = None
new_typeinfo = cdef.info.get("__new__")
if new_typeinfo and new_typeinfo.fullname and not new_typeinfo.fullname.startswith("builtins"):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add comment explaining what is happening here.

This name seems a bit confusing. new_typeinfo refers to a symbol table node? If that's the case, maybe rename it as new_symbol or similar.

The prefix should be "builtins." (with a dot at the end), since there can be a user-defined module such as builtinsx.

@JukkaL JukkaL merged commit c32b0a5 into python:master Aug 29, 2025
13 checks passed
@p-sawicki p-sawicki deleted the dunder-new-support branch August 29, 2025 16:50
p-sawicki added a commit that referenced this pull request Sep 22, 2025
#19739 introduced support for compiling `__new__` methods in native
classes. Native classes differ internally from regular python types
which results in `TypeError`s being raised when `object.__new__(cls)` is
called when `cls` is a native class.

To avoid making this call, `super().__new__(cls)` is transformed into a
call to an internal setup function. I forgot to replicate this for calls
to equivalent `object.__new__(cls)` calls so this PR fixes that. This
introduced a regression because before my changes, `__new__` methods
with `object.__new__(cls)` were effectively ignored at runtime, and
after my changes they started raising `TypeError`s.

Note that these calls are left as-is outside of `__new__` methods so
it's still possible to trigger the `TypeError` but that is not a
regression as this was the case before. For example this code:
```
class Test:
    pass

t = object.__new__(Test)
```
results in `TypeError: object.__new__(Test) is not safe, use
Test.__new__()`. This differs from interpreted python but the error
message is actually correct in that using `Test.__new__(Test)` instead
works.
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.

Mypyc ignores custom __new__ without warning

3 participants