-
-
Notifications
You must be signed in to change notification settings - Fork 3k
[mypyc] Use defined __new__ method in tp_new and constructor #19739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Co-authored-by: Brian Schubert <brianm.schubert@gmail.com>
@@ -175,3 +175,6 @@ class _TypedDict(Mapping[str, object]): | |||
|
|||
class TypeAliasType: | |||
pass | |||
|
|||
def Self(self, parameters): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We define Self
as Self = 0
in other test stubs. Can you try this here?
@@ -1607,3 +1607,16 @@ def native_class(x): | |||
L0: | |||
r0 = CPy_TYPE(x) | |||
return r0 | |||
|
|||
[case testUnsupportedDunderNew] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also add a successful irbuild test that calls super().__new__(cls)
in a __new__
method. A simple case is sufficient (i.e. don't return a union type from __new__
).
from m import interpreted_subclass | ||
|
||
@mypyc_attr(allow_interpreted_subclasses=True) | ||
class Base: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also add a run test that does subclassing but doesn't allow interpreted subclasses.
Add a run test that does subclassing but subclass doesn't override __new__
.
mypyc/test-data/run-classes.test
Outdated
pass | ||
|
||
class Sub(Base): | ||
def __new__(cls, val: int) -> Base: # type: ignore[misc] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you avoid using # type: ignore[misc]
here? This is something we may disallow in the future, since it can generate unsafe code.
pass | ||
|
||
def test_inherited_dunder_new() -> None: | ||
s = Sub(42) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test that type(s)
is as expected. Test also constructing an instance of Base
, and perform a similar check for it.
mypyc/test-data/run-classes.test
Outdated
return obj | ||
|
||
def __init__(self, val: int): | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also do something here, to ensure that __init__
is also called? E.g. initialize some other attribute.
mypyc/test-data/run-classes.test
Outdated
return super().__new__(cls, val + 1) | ||
|
||
def __init__(self, val: int): | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also test that this gets called as expected.
mypyc/test-data/run-classes.test
Outdated
IntLike = Union[int, Add] | ||
|
||
[file driver.py] | ||
from native import Add |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only testing behavior in interpreted code. What about also testing that similar things work in compiled code?
assert t0.val == 0 | ||
t0 = Test0.__new__(1) | ||
assert t0.val == 1 | ||
with assertRaises(TypeError, "__new__() missing required argument 'val'"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also test a __new__
method that raises an exception in the body (using raise
, for example).
obj = super().__new__(cls) | ||
|
||
with assertRaises(AttributeError, "attribute 'native' of 'Test' undefined"): | ||
print(obj.native) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also test an attribute of type mypy_extensions.u8
that is undefined (it uses a bitfield to track whether attribute is defined).
Test that an attribute with a default value is initialized correctly (i.e. x = 5
in class body).
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 thetp_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:
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.tp_new
method of the parent type results in an error because cpython expects the sub type to use a wrapper fortp_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.__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.