Skip to content

Commit 6e6d672

Browse files
authored
Fix type object signature when both __new__ and __init__ present (#5642)
Currently mypy will prefer __init__ to __new__ for determining the signature of a type object if there exists any __init__ other than object's. Instead, prefer the closest definition in the MRO, so that subclass __new__ can override parent __init__. Fixes #1435.
1 parent b41bb66 commit 6e6d672

File tree

2 files changed

+60
-10
lines changed

2 files changed

+60
-10
lines changed

mypy/checkmember.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -650,18 +650,28 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) ->
650650
where ... are argument types for the __init__/__new__ method (without the self
651651
argument). Also, the fallback type will be 'type' instead of 'function'.
652652
"""
653+
654+
# We take the type from whichever of __init__ and __new__ is first
655+
# in the MRO, preferring __init__ if there is a tie.
653656
init_method = info.get_method('__init__')
657+
new_method = info.get_method('__new__')
654658
if not init_method:
655659
# Must be an invalid class definition.
656660
return AnyType(TypeOfAny.from_error)
661+
# There *should* always be a __new__ method except the test stubs
662+
# lack it, so just copy init_method in that situation
663+
new_method = new_method or init_method
664+
665+
init_index = info.mro.index(init_method.info)
666+
new_index = info.mro.index(new_method.info)
667+
668+
fallback = info.metaclass_type or builtin_type('builtins.type')
669+
if init_index < new_index:
670+
method = init_method
671+
elif init_index > new_index:
672+
method = new_method
657673
else:
658-
fallback = info.metaclass_type or builtin_type('builtins.type')
659674
if init_method.info.fullname() == 'builtins.object':
660-
# No non-default __init__ -> look at __new__ instead.
661-
new_method = info.get_method('__new__')
662-
if new_method and new_method.info.fullname() != 'builtins.object':
663-
# Found one! Get signature from __new__.
664-
return type_object_type_from_function(new_method, info, fallback)
665675
# Both are defined by object. But if we've got a bogus
666676
# base class, we can't know for sure, so check for that.
667677
if info.fallback_to_any:
@@ -673,9 +683,14 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) ->
673683
ret_type=any_type,
674684
fallback=builtin_type('builtins.function'))
675685
return class_callable(sig, info, fallback, None)
676-
# Construct callable type based on signature of __init__. Adjust
677-
# return type and insert type arguments.
678-
return type_object_type_from_function(init_method, info, fallback)
686+
687+
# Otherwise prefer __init__ in a tie. It isn't clear that this
688+
# is the right thing, but __new__ caused problems with
689+
# typeshed (#5647).
690+
method = init_method
691+
# Construct callable type based on signature of __init__. Adjust
692+
# return type and insert type arguments.
693+
return type_object_type_from_function(method, info, fallback)
679694

680695

681696
def type_object_type_from_function(init_or_new: FuncBase, info: TypeInfo,

test-data/unit/check-classes.test

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1828,7 +1828,7 @@ class Num1:
18281828

18291829
class Num2(Num1):
18301830
@overload
1831-
def __add__(self, other: Num2) -> Num2: ...
1831+
def __add__(self, other: Num2) -> Num2: ...
18321832
@overload
18331833
def __add__(self, other: Num1) -> Num2: ...
18341834
def __add__(self, other): pass
@@ -5118,6 +5118,41 @@ class C:
51185118
[builtins fixtures/property.pyi]
51195119
[out]
51205120

5121+
[case testNewAndInit1]
5122+
class A:
5123+
def __init__(self, x: int) -> None:
5124+
pass
5125+
5126+
class B(A):
5127+
def __new__(cls) -> int:
5128+
return 10
5129+
5130+
B()
5131+
5132+
[case testNewAndInit2]
5133+
from typing import Any
5134+
5135+
class A:
5136+
def __new__(cls, *args: Any) -> 'A':
5137+
...
5138+
5139+
class B(A):
5140+
def __init__(self, x: int) -> None:
5141+
pass
5142+
5143+
reveal_type(B) # E: Revealed type is 'def (x: builtins.int) -> __main__.B'
5144+
5145+
[case testNewAndInit3]
5146+
from typing import Any
5147+
5148+
class A:
5149+
def __new__(cls, *args: Any) -> 'A':
5150+
...
5151+
def __init__(self, x: int) -> None:
5152+
pass
5153+
5154+
reveal_type(A) # E: Revealed type is 'def (x: builtins.int) -> __main__.A'
5155+
51215156
[case testCyclicDecorator]
51225157
import b
51235158
[file a.py]

0 commit comments

Comments
 (0)