Skip to content

Commit

Permalink
Fix __new__ and __init__ precedence
Browse files Browse the repository at this point in the history
Fixes #5647

See also #5642
  • Loading branch information
hauntsaninja committed Nov 3, 2024
1 parent 1f200dd commit ce89220
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 11 deletions.
18 changes: 10 additions & 8 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -1321,15 +1321,16 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
"""

# We take the type from whichever of __init__ and __new__ is first
# in the MRO, preferring __init__ if there is a tie.
# in the MRO, preferring __new__ if there is a tie.
init_method = info.get("__init__")
new_method = info.get("__new__")
if not init_method or not is_valid_constructor(init_method.node):
# 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
if new_method is None:
new_method = named_type("builtins.object").type.get("__init__")
if not is_valid_constructor(new_method.node):
# Must be an invalid class definition.
return AnyType(TypeOfAny.from_error)
Expand Down Expand Up @@ -1363,12 +1364,13 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
fallback=named_type("builtins.function"),
)
return class_callable(sig, info, fallback, None, is_new=False)

# Otherwise prefer __init__ in a tie. It isn't clear that this
# is the right thing, but __new__ caused problems with
# typeshed (#5647).
method = init_method.node
is_new = False
if init_method.node.info.fullname == "builtins.dict":
# dict.__new__ in typeshed is pretty unhelpful
method = init_method.node
is_new = False
else:
method = new_method.node
is_new = True
# Construct callable type based on signature of __init__. Adjust
# return type and insert type arguments.
if isinstance(method, FuncBase):
Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -6324,7 +6324,7 @@ class A:
def __init__(self, x: int) -> None:
pass

reveal_type(A) # N: Revealed type is "def (x: builtins.int) -> __main__.A"
reveal_type(A) # N: Revealed type is "def (*args: Any) -> __main__.A"
[builtins fixtures/tuple.pyi]

[case testCyclicDecorator]
Expand Down Expand Up @@ -7883,7 +7883,7 @@ if object():
if object():
reveal_type(B()) # N: Revealed type is "Never"
if object():
reveal_type(C()) # N: Revealed type is "Never"
reveal_type(C()) # N: Revealed type is "__main__.C"
if object():
reveal_type(D()) # N: Revealed type is "Never"

Expand Down Expand Up @@ -7933,7 +7933,7 @@ if object():
reveal_type(B(1)) # N: Revealed type is "__main__.B"

if object():
reveal_type(C()) # N: Revealed type is "Never"
reveal_type(C()) # N: Revealed type is "__main__.C"
reveal_type(C(1)) # N: Revealed type is "__main__.C"

if object():
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/object_hashable.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
class object:
def __init__(self) -> None: ...
def __hash__(self) -> int: ...

class type: ...
Expand Down

0 comments on commit ce89220

Please sign in to comment.