-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Honor return type of __new__
even if not a subclass
#15182
Comments
This is handled here where there is an explicit check to ignore the type given in Lines 184 to 196 in 13f35ad
I'm not sure where all of those conditions come from but I don't see how any of them can override the hint given in __new__ because __new__ can return anything (this is generally the reason for using __new__ rather than __init__ ).
|
Hi @oscarbenjamin, this seems to be a duplicate of #8330. I've opened #14471 since then but I'm kind of stuck on it. Hopefully if someone with the required knowledge could see what's wrong in the PR we could get it merged one day! |
Yes, I saw that issue. I thought this was different but now that I've seen the code that causes this I can see that it causes both issues. To be honest having looked at the code I don't really understand what any of it is doing. It doesn't bear any resemblance to Python's runtime semantics for Lines 1198 to 1199 in 13f35ad
That makes no sense to me:
I would expect the inference to work something like this. We have a class: class A(B, metaclass=Meta):
def __new__(cls, *args, **kwargs):
...
def __init__(self, *args, **kwargs):
.... When we call class type:
def __call__(A, *args, **kwargs):
# Use __new__ to create the object
if A.__new__ is object.__new__:
obj = object.__new__(A)
else:
obj = A.__new__(A, *args, **kwargs)
# Call __init__ with the created object (only if is an instance of A):
if isinstance(obj, A):
if type(obj).__init__ is not object.__init__:
obj.__init__(*args, **kwargs)
# Return the object obtained from __new__
return obj The final piece to know is that We see here that the type of the object returned is just the return type of |
As a workaround, I'm using the |
This currently makes it impossible to correctly type |
Like @oscarbenjamin noted, this fails in the contravariant case, i.e. when But when class Other:
def __new__(cls, arg: T, /) -> T: # error: "__new__" must return a class instance (got "T") [misc]
return arg
assert_type(Other(42), 'int') # error: Expression is of type "Other", not "int" [assert-type] For details, see https://mypy-play.net/?mypy=latest&python=3.12&flags=strict&gist=336602d390b5e6566e9fca93d7fa48a6 |
The PR fixing this can be found here: #16020 |
The official typing specs now include a section on the
So this confirms that this is, in fact, a bug (and IMO a rather big one). |
The relevant parts of the code have not changed since I linked them above. Within mypy
Having seen the code it is not surprising that other examples don't work as well (gh-14122): class A:
pass
class Meta(type):
def __call__(self, *args, **kwargs) -> A:
return A()
class B(metaclass=Meta):
pass
print(type(B())) # A
reveal_type(B()) # B |
Looking at the (updated) test cases, feels like it does? In particular: mypy/test-data/unit/check-classes.test Lines 6925 to 6931 in cdccffa
While a type ignore comment is needed, |
@Viicos Ah good, that's better than nothing I guess. But it still doesn't constitute a full fix, because that error is a false negative. |
Maybe in some cases but likely not others. As noted in the comments the code uses a "hack" to make this work for some cases. It begins by checking I would expect that it still fails for other cases like this: from __future__ import annotations
class A:
def __new__(cls) -> A:
return super().__new__(cls)
class B(A):
def __new__(cls) -> A:
return super().__new__(cls)
class C(B):
def __init__(self): # possibly use self:C here.
pass
# C.__init__ is earlier in the "combined MRO" than B.__new__
reveal_type(B()) # should be A
reveal_type(C()) # should also be A I think the overall logic in |
This follows gh-1020 which was closed by gh-7188. This arises because I am adding type hints to sympy (sympy/sympy#25103).
In the following code we have a subclass whose
__new__
method is only guaranteed to return objects that are types of the superclass:With mypy this is rejected and the type of
B()
is inferred incorrectly but pyright accepts this and gets the inference forB()
correct:The fix for
__new__
in gh-7188 was:the return type
__new__
's return typeThe issue here is about mixing the last two points. Here mypy produces an error but then does not respect
__new__
's return type. The return type is not respected since mypy infers that it is of typeB
unlike pyright which infers typeA
as specified by the hint. I don't mind mypy reporting an error here but in context-> A
is the accurate type hint and mypy should respect that because anything else is just not correct. I can add a type ignore to silence the mypy error but the inference will still be wrong in all downstream/user code and is also inconsistent with pyright.The text was updated successfully, but these errors were encountered: