8000 Fix type object signature when both __new__ and __init__ present · python/mypy@b06d60a · GitHub
[go: up one dir, main page]

Skip to content

Commit b06d60a

Browse files
committed
Fix type object signature when both __new__ and __init__ present
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 9dea7c7 commit b06d60a

File tree

2 files changed

+60
-10
lines changed

2 files changed

+60
-10
lines changed

mypy/checkmember.py

Lines changed: 23 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,13 @@ 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 a few problems.
689+
method = init_method
690+
# Construct callable type based on signature of __init__. Adjust
691+
# return type and insert type arguments.
692+
return type_object_type_from_function(method, info, fallback)
679693

680694

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

test-data/unit/check-classes.test

Lines changed: 37 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
@@ -5117,3 +5117,39 @@ class C:
51175117
def x(self) -> int: pass
51185118
[builtins fixtures/property.pyi]
51195119
[out]
5120+
5121+
[case testNewAndInit1]
5122+
class A:
5123+
def __init__(self, x: int) -> None:
5124+
pass
5125+
5126+
class B(A):
5127+
def __new__(cls) -> 'B':
5128+
...
5129+
5130+
B()
5131+
reveal_type(B) # E: Revealed type is 'def () -> __main__.B'
5132+
5133+
[case testNewAndInit2]
5134+
from typing import Any
5135+
5136+
class A:
5137+
def __new__(cls, *args: Any) -> 'A':
5138+
...
5139+
5140+
class B(A):
5141+
def __init__(self, x: int) -> None:
5142+
pass
5143+
5144+
reveal_type(B) # E: Revealed type is 'def (x: builtins.int) -> __main__.B'
5145+
5146+
[case testNewAndInit3]
5147+
from typing import Any
5148+
5149+
class A:
5150+
def __new__(cls, *args: Any) -> 'A':
5151+
...
5152+
def __init__(self, x: int) -> None:
5153+
pass
5154+
5155+
reveal_type(A) # E: Revealed type is 'def (x: builtins.int) -> __main__.A'

0 commit comments

Comments
 (0)
0