Skip to content

Commit ae778cc

Browse files
authored
Fix dmypy suggest interaction with __new__ (#18966)
Fixes #18964. `__new__` is special - it has `is_static` set in `semanal.py`, but still has first `cls` argument. This PR tells `dmypy suggest` about that.
1 parent a48ffed commit ae778cc

File tree

6 files changed

+37
-6
lines changed

6 files changed

+37
-6
lines changed

mypy/checker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,7 +1362,7 @@ def check_func_def(
13621362
if typ.type_is:
13631363
arg_index = 0
13641364
# For methods and classmethods, we want the second parameter
1365-
if ref_type is not None and (not defn.is_static or defn.name == "__new__"):
1365+
if ref_type is not None and defn.has_self_or_cls_argument:
13661366
arg_index = 1
13671367
if arg_index < len(typ.arg_types) and not is_subtype(
13681368
typ.type_is, typ.arg_types[arg_index]
@@ -1382,7 +1382,7 @@ def check_func_def(
13821382
isinstance(defn, FuncDef)
13831383
and ref_type is not None
13841384
and i == 0
1385-
and (not defn.is_static or defn.name == "__new__")
1385+
and defn.has_self_or_cls_argument
13861386
and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]
13871387
):
13881388
if defn.is_class or defn.name == "__new__":

mypy/nodes.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,8 @@ def __init__(self) -> None:
508508
self.info = FUNC_NO_INFO
509509
self.is_property = False
510510
self.is_class = False
511+
# Is this a `@staticmethod` (explicit or implicit)?
512+
# Note: use has_self_or_cls_argument to check if there is `self` or `cls` argument
511513
self.is_static = False
512514
self.is_final = False
513515
self.is_explicit_override = False
@@ -524,6 +526,15 @@ def name(self) -> str:
524526
def fullname(self) -> str:
525527
return self._fullname
526528

529+
@property
530+
def has_self_or_cls_argument(self) -> bool:
531+
"""If used as a method, does it have an argument for method binding (`self`, `cls`)?
532+
533+
This is true for `__new__` even though `__new__` does not undergo method binding,
534+
because we still usually assume that `cls` corresponds to the enclosing class.
535+
"""
536+
return not self.is_static or self.name == "__new__"
537+
527538

528539
OverloadPart: _TypeAlias = Union["FuncDef", "Decorator"]
529540

mypy/semanal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo, has_self_type:
10691069
functype = func.type
10701070
if func.name == "__new__":
10711071
func.is_static = True
1072-
if not func.is_static or func.name == "__new__":
1072+
if func.has_self_or_cls_argument:
10731073
if func.name in ["__init_subclass__", "__class_getitem__"]:
10741074
func.is_class = True
10751075
if not func.arguments:
@@ -1602,7 +1602,7 @@ def analyze_function_body(self, defn: FuncItem) -> None:
16021602
# The first argument of a non-static, non-class method is like 'self'
16031603
# (though the name could be different), having the enclosing class's
16041604
# instance type.
1605-
if is_method and (not defn.is_static or defn.name == "__new__") and defn.arguments:
1605+
if is_method and defn.has_self_or_cls_argument and defn.arguments:
16061606
if not defn.is_class:
16071607
defn.arguments[0].variable.is_self = True
16081608
else:

mypy/suggestions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ def get_suggestion(self, mod: str, node: FuncDef) -> PyAnnotateSignature:
486486
if self.no_errors and orig_errors:
487487
raise SuggestionFailure("Function does not typecheck.")
488488

489-
is_method = bool(node.info) and not node.is_static
489+
is_method = bool(node.info) and node.has_self_or_cls_argument
490490

491491
with state.strict_optional_set(graph[mod].options.strict_optional):
492492
guesses = self.get_guesses(

mypy/typeops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ def callable_type(
890890
fdef: FuncItem, fallback: Instance, ret_type: Type | None = None
891891
) -> CallableType:
892892
# TODO: somewhat unfortunate duplication with prepare_method_signature in semanal
893-
if fdef.info and (not fdef.is_static or fdef.name == "__new__") and fdef.arg_names:
893+
if fdef.info and fdef.has_self_or_cls_argument and fdef.arg_names:
894894
self_type: Type = fill_typevars(fdef.info)
895895
if fdef.is_class or fdef.name == "__new__":
896896
self_type = TypeType.make_normalized(self_type)

test-data/unit/fine-grained-suggest.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,26 @@ def bar(iany) -> None:
795795
(int) -> None
796796
==
797797

798+
[case testSuggestNewInit]
799+
# suggest: foo.F.__init__
800+
# suggest: foo.F.__new__
801+
[file foo.py]
802+
class F:
803+
def __new__(cls, t):
804+
return super().__new__(cls)
805+
806+
def __init__(self, t):
807+
self.t = t
808+
809+
[file bar.py]
810+
from foo import F
811+
def bar(iany) -> None:
812+
F(0)
813+
[out]
814+
(int) -> None
815+
(int) -> Any
816+
==
817+
798818
[case testSuggestColonBasic]
799819
# suggest: tmp/foo.py:1
800820
# suggest: tmp/bar/baz.py:2

0 commit comments

Comments
 (0)