From c13f1d416e907f58bc77d086b84819f500f1bde9 Mon Sep 17 00:00:00 2001 From: Erik Kemperman Date: Wed, 5 Jul 2023 09:34:11 +0200 Subject: [PATCH] Better consistency with explicit staticmethods (#15353) --- mypy/checker.py | 5 ++- mypy/checkmember.py | 6 +--- mypy/nodes.py | 2 +- mypy/semanal.py | 8 +++-- mypy/typeops.py | 2 +- test-data/unit/check-selftype.test | 52 ++++++++++++++++++++++++++++++ 6 files changed, 62 insertions(+), 13 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index bf200299d8b3..5ed1c792778b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1164,11 +1164,10 @@ def check_func_def( isinstance(defn, FuncDef) and ref_type is not None and i == 0 - and not defn.is_static + and (not defn.is_static or defn.name == "__new__") and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2] ): - isclass = defn.is_class or defn.name in ("__new__", "__init_subclass__") - if isclass: + if defn.is_class or defn.name == "__new__": ref_type = mypy.types.TypeType.make_normalized(ref_type) erased = get_proper_type(erase_to_bound(arg_type)) if not is_subtype(ref_type, erased, ignore_type_params=True): diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 36fea9daa3e3..343dfe3de243 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -319,11 +319,7 @@ def analyze_instance_member_access( mx.msg.cant_assign_to_method(mx.context) signature = function_type(method, mx.named_type("builtins.function")) signature = freshen_all_functions_type_vars(signature) - if name == "__new__" or method.is_static: - # __new__ is special and behaves like a static method -- don't strip - # the first argument. - pass - else: + if not method.is_static: if name != "__call__": # TODO: use proper treatment of special methods on unions instead # of this hack here and below (i.e. mx.self_type). diff --git a/mypy/nodes.py b/mypy/nodes.py index eb093f5d45b0..2d763fc482d3 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -512,7 +512,7 @@ class FuncBase(Node): "info", "is_property", "is_class", # Uses "@classmethod" (explicit or implicit) - "is_static", # Uses "@staticmethod" + "is_static", # Uses "@staticmethod" (explicit or implicit) "is_final", # Uses "@final" "is_explicit_override", # Uses "@override" "_fullname", diff --git a/mypy/semanal.py b/mypy/semanal.py index 1f15691ae205..f4f281e7a77a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -959,9 +959,11 @@ def remove_unpack_kwargs(self, defn: FuncDef, typ: CallableType) -> CallableType def prepare_method_signature(self, func: FuncDef, info: TypeInfo, has_self_type: bool) -> None: """Check basic signature validity and tweak annotation of self/cls argument.""" - # Only non-static methods are special. + # Only non-static methods are special, as well as __new__. functype = func.type - if not func.is_static: + if func.name == "__new__": + func.is_static = True + if not func.is_static or func.name == "__new__": if func.name in ["__init_subclass__", "__class_getitem__"]: func.is_class = True if not func.arguments: @@ -1397,7 +1399,7 @@ def analyze_function_body(self, defn: FuncItem) -> None: # The first argument of a non-static, non-class method is like 'self' # (though the name could be different), having the enclosing class's # instance type. - if is_method and not defn.is_static and defn.arguments: + if is_method and (not defn.is_static or defn.name == "__new__") and defn.arguments: if not defn.is_class: defn.arguments[0].variable.is_self = True else: diff --git a/mypy/typeops.py b/mypy/typeops.py index 58a641a54ab7..519d3de995f5 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -710,7 +710,7 @@ def callable_type( fdef: FuncItem, fallback: Instance, ret_type: Type | None = None ) -> CallableType: # TODO: somewhat unfortunate duplication with prepare_method_signature in semanal - if fdef.info and not fdef.is_static and fdef.arg_names: + if fdef.info and (not fdef.is_static or fdef.name == "__new__") and fdef.arg_names: self_type: Type = fill_typevars(fdef.info) if fdef.is_class or fdef.name == "__new__": self_type = TypeType.make_normalized(self_type) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 4155e3343851..dc7a6c239fe6 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -509,6 +509,58 @@ class E: def __init_subclass__(cls) -> None: reveal_type(cls) # N: Revealed type is "Type[__main__.E]" +[case testSelfTypeNew_explicit] +from typing import TypeVar, Type + +T = TypeVar('T', bound='A') +class A: + @staticmethod + def __new__(cls: Type[T]) -> T: + return cls() + + @classmethod + def __init_subclass__(cls: Type[T]) -> None: + pass + +class B: + @staticmethod + 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]" + return cls() + + @classmethod + 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]" + pass + +class C: + @staticmethod + def __new__(cls: Type[C]) -> C: + return cls() + + @classmethod + def __init_subclass__(cls: Type[C]) -> None: + pass + +class D: + @staticmethod + def __new__(cls: D) -> D: # E: The erased type of self "__main__.D" is not a supertype of its class "Type[__main__.D]" + return cls + + @classmethod + def __init_subclass__(cls: D) -> None: # E: The erased type of self "__main__.D" is not a supertype of its class "Type[__main__.D]" + pass + +class E: + @staticmethod + def __new__(cls) -> E: + reveal_type(cls) # N: Revealed type is "Type[__main__.E]" + return cls() + + @classmethod + def __init_subclass__(cls) -> None: + reveal_type(cls) # N: Revealed type is "Type[__main__.E]" + +[builtins fixtures/classmethod.pyi] + [case testSelfTypePropertyUnion] from typing import Union class A: