Skip to content

Commit

Permalink
Better consistency with explicit staticmethods (#15353)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikkemperman committed Jul 5, 2023
1 parent 7d1a899 commit c13f1d4
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 13 deletions.
5 changes: 2 additions & 3 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
6 changes: 1 addition & 5 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 5 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
52 changes: 52 additions & 0 deletions test-data/unit/check-selftype.test
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit c13f1d4

Please sign in to comment.