Skip to content

Commit

Permalink
Fix type object signature when both __new__ and __init__ present
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
msullivan committed Sep 19, 2018
1 parent 9dea7c7 commit b06d60a
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 10 deletions.
32 changes: 23 additions & 9 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,18 +650,28 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) ->
where ... are argument types for the __init__/__new__ method (without the self
argument). Also, the fallback type will be 'type' instead of 'function'.
"""

# We take the type from whichever of __init__ and __new__ is first
# in the MRO, preferring __init__ if there is a tie.
init_method = info.get_method('__init__')
new_method = info.get_method('__new__')
if not init_method:
# Must be an invalid class definition.
return AnyType(TypeOfAny.from_error)
# There *should* always be a __new__ method except the test stubs
# lack it, so just copy init_method in that situation
new_method = new_method or init_method

init_index = info.mro.index(init_method.info)
new_index = info.mro.index(new_method.info)

fallback = info.metaclass_type or builtin_type('builtins.type')
if init_index < new_index:
method = init_method
elif init_index > new_index:
method = new_method
else:
fallback = info.metaclass_type or builtin_type('builtins.type')
if init_method.info.fullname() == 'builtins.object':
# No non-default __init__ -> look at __new__ instead.
new_method = info.get_method('__new__')
if new_method and new_method.info.fullname() != 'builtins.object':
# Found one! Get signature from __new__.
return type_object_type_from_function(new_method, info, fallback)
# Both are defined by object. But if we've got a bogus
# base class, we can't know for sure, so check for that.
if info.fallback_to_any:
Expand All @@ -673,9 +683,13 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) ->
ret_type=any_type,
fallback=builtin_type('builtins.function'))
return class_callable(sig, info, fallback, None)
# Construct callable type based on signature of __init__. Adjust
# return type and insert type arguments.
return type_object_type_from_function(init_method, info, fallback)

# Otherwise prefer __init__ in a tie. It isn't clear that this
# is the right thing, but __new__ caused a few problems.
method = init_method
# Construct callable type based on signature of __init__. Adjust
# return type and insert type arguments.
return type_object_type_from_function(method, info, fallback)


def type_object_type_from_function(init_or_new: FuncBase, info: TypeInfo,
Expand Down
38 changes: 37 additions & 1 deletion test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1828,7 +1828,7 @@ class Num1:

class Num2(Num1):
@overload
def __add__(self, other: Num2) -> Num2: ...
def __add__(self, other: Num2) -> Num2: ...
@overload
def __add__(self, other: Num1) -> Num2: ...
def __add__(self, other): pass
Expand Down Expand Up @@ -5117,3 +5117,39 @@ class C:
def x(self) -> int: pass
[builtins fixtures/property.pyi]
[out]

[case testNewAndInit1]
class A:
def __init__(self, x: int) -> None:
pass

class B(A):
def __new__(cls) -> 'B':
...

B()
reveal_type(B) # E: Revealed type is 'def () -> __main__.B'

[case testNewAndInit2]
from typing import Any

class A:
def __new__(cls, *args: Any) -> 'A':
...

class B(A):
def __init__(self, x: int) -> None:
pass

reveal_type(B) # E: Revealed type is 'def (x: builtins.int) -> __main__.B'

[case testNewAndInit3]
from typing import Any

class A:
def __new__(cls, *args: Any) -> 'A':
...
def __init__(self, x: int) -> None:
pass

reveal_type(A) # E: Revealed type is 'def (x: builtins.int) -> __main__.A'

0 comments on commit b06d60a

Please sign in to comment.