Skip to content

Commit c13f1d4

Browse files
Better consistency with explicit staticmethods (#15353)
1 parent 7d1a899 commit c13f1d4

File tree

6 files changed

+62
-13
lines changed

6 files changed

+62
-13
lines changed

mypy/checker.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,11 +1164,10 @@ def check_func_def(
11641164
isinstance(defn, FuncDef)
11651165
and ref_type is not None
11661166
and i == 0
1167-
and not defn.is_static
1167+
and (not defn.is_static or defn.name == "__new__")
11681168
and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]
11691169
):
1170-
isclass = defn.is_class or defn.name in ("__new__", "__init_subclass__")
1171-
if isclass:
1170+
if defn.is_class or defn.name == "__new__":
11721171
ref_type = mypy.types.TypeType.make_normalized(ref_type)
11731172
erased = get_proper_type(erase_to_bound(arg_type))
11741173
if not is_subtype(ref_type, erased, ignore_type_params=True):

mypy/checkmember.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -319,11 +319,7 @@ def analyze_instance_member_access(
319319
mx.msg.cant_assign_to_method(mx.context)
320320
signature = function_type(method, mx.named_type("builtins.function"))
321321
signature = freshen_all_functions_type_vars(signature)
322-
if name == "__new__" or method.is_static:
323-
# __new__ is special and behaves like a static method -- don't strip
324-
# the first argument.
325-
pass
326-
else:
322+
if not method.is_static:
327323
if name != "__call__":
328324
# TODO: use proper treatment of special methods on unions instead
329325
# of this hack here and below (i.e. mx.self_type).

mypy/nodes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ class FuncBase(Node):
512512
"info",
513513
"is_property",
514514
"is_class", # Uses "@classmethod" (explicit or implicit)
515-
"is_static", # Uses "@staticmethod"
515+
"is_static", # Uses "@staticmethod" (explicit or implicit)
516516
"is_final", # Uses "@final"
517517
"is_explicit_override", # Uses "@override"
518518
"_fullname",

mypy/semanal.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -959,9 +959,11 @@ def remove_unpack_kwargs(self, defn: FuncDef, typ: CallableType) -> CallableType
959959

960960
def prepare_method_signature(self, func: FuncDef, info: TypeInfo, has_self_type: bool) -> None:
961961
"""Check basic signature validity and tweak annotation of self/cls argument."""
962-
# Only non-static methods are special.
962+
# Only non-static methods are special, as well as __new__.
963963
functype = func.type
964-
if not func.is_static:
964+
if func.name == "__new__":
965+
func.is_static = True
966+
if not func.is_static or func.name == "__new__":
965967
if func.name in ["__init_subclass__", "__class_getitem__"]:
966968
func.is_class = True
967969
if not func.arguments:
@@ -1397,7 +1399,7 @@ def analyze_function_body(self, defn: FuncItem) -> None:
13971399
# The first argument of a non-static, non-class method is like 'self'
13981400
# (though the name could be different), having the enclosing class's
13991401
# instance type.
1400-
if is_method and not defn.is_static and defn.arguments:
1402+
if is_method and (not defn.is_static or defn.name == "__new__") and defn.arguments:
14011403
if not defn.is_class:
14021404
defn.arguments[0].variable.is_self = True
14031405
else:

mypy/typeops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ def callable_type(
710710
fdef: FuncItem, fallback: Instance, ret_type: Type | None = None
711711
) -> CallableType:
712712
# TODO: somewhat unfortunate duplication with prepare_method_signature in semanal
713-
if fdef.info and not fdef.is_static and fdef.arg_names:
713+
if fdef.info and (not fdef.is_static or fdef.name == "__new__") and fdef.arg_names:
714714
self_type: Type = fill_typevars(fdef.info)
715715
if fdef.is_class or fdef.name == "__new__":
716716
self_type = TypeType.make_normalized(self_type)

test-data/unit/check-selftype.test

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,58 @@ class E:
509509
def __init_subclass__(cls) -> None:
510510
reveal_type(cls) # N: Revealed type is "Type[__main__.E]"
511511

512+
[case testSelfTypeNew_explicit]
513+
from typing import TypeVar, Type
514+
515+
T = TypeVar('T', bound='A')
516+
class A:
517+
@staticmethod
518+
def __new__(cls: Type[T]) -> T:
519+
return cls()
520+
521+
@classmethod
522+
def __init_subclass__(cls: Type[T]) -> None:
523+
pass
524+
525+
class B:
526+
@staticmethod
527+
def __new__(cls: Type[T]) -> T: # E: The erased type of self "Type[__main__.A]" is not a supertype of its class "Type[__main__.B]"
528+
return cls()
529+
530+
@classmethod
531+
def __init_subclass__(cls: Type[T]) -> None: # E: The erased type of self "Type[__main__.A]" is not a supertype of its class "Type[__main__.B]"
532+
pass
533+
534+
class C:
535+
@staticmethod
536+
def __new__(cls: Type[C]) -> C:
537+
return cls()
538+
539+
@classmethod
540+
def __init_subclass__(cls: Type[C]) -> None:
541+
pass
542+
543+
class D:
544+
@staticmethod
545+
def __new__(cls: D) -> D: # E: The erased type of self "__main__.D" is not a supertype of its class "Type[__main__.D]"
546+
return cls
547+
548+
@classmethod
549+
def __init_subclass__(cls: D) -> None: # E: The erased type of self "__main__.D" is not a supertype of its class "Type[__main__.D]"
550+
pass
551+
552+
class E:
553+
@staticmethod
554+
def __new__(cls) -> E:
555+
reveal_type(cls) # N: Revealed type is "Type[__main__.E]"
556+
return cls()
557+
558+
@classmethod
559+
def __init_subclass__(cls) -> None:
560+
reveal_type(cls) # N: Revealed type is "Type[__main__.E]"
561+
562+
[builtins fixtures/classmethod.pyi]
563+
512564
[case testSelfTypePropertyUnion]
513565
from typing import Union
514566
class A:

0 commit comments

Comments
 (0)