Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicit decorated __new__, __class_getitem__ and __init_subclass__ #15353

Merged
merged 10 commits into from
Jul 5, 2023
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 @@ -1383,7 +1385,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
Loading