From 0a75c52778eee0cf5d6176cd0f4acd671e1d5197 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 17 Oct 2019 15:30:44 +0100 Subject: [PATCH 1/2] Fix accessing unannotated implicit class methods --- mypy/checkmember.py | 7 +++- mypy/typeops.py | 15 +++++--- test-data/unit/check-classes.test | 36 +++++++++++++++++++ .../fixtures/object_with_init_subclass.pyi | 11 +++++- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 25f75d3c474e7..4272123b4ca65 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -736,7 +736,12 @@ def analyze_class_attribute_access(itype: Instance, mx.not_ready_callback(name, mx.context) return AnyType(TypeOfAny.from_error) else: - return function_type(cast(FuncBase, node.node), mx.builtin_type('builtins.function')) + assert isinstance(node.node, FuncBase) + # Note: if we are accessing class method on class object, the cls argument is bound. + # Annotated and/or explicit class methods go through other code paths above, for + # unannotated implicit class method we can just drop first argument. + return function_type(node.node, mx.builtin_type('builtins.function'), + no_self=node.node.is_class) def add_class_tvars(t: ProperType, itype: Instance, isuper: Optional[Instance], diff --git a/mypy/typeops.py b/mypy/typeops.py index 36a7597ec54d1..ef837d0a10a2b 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -383,14 +383,15 @@ def erase_to_union_or_bound(typ: TypeVarType) -> ProperType: return get_proper_type(typ.upper_bound) -def function_type(func: FuncBase, fallback: Instance) -> FunctionLike: +def function_type(func: FuncBase, fallback: Instance, + no_self: bool = False) -> FunctionLike: if func.type: assert isinstance(func.type, FunctionLike) return func.type else: # Implicit type signature with dynamic types. if isinstance(func, FuncItem): - return callable_type(func, fallback) + return callable_type(func, fallback, no_self=no_self) else: # Broken overloads can have self.type set to None. # TODO: should we instead always set the type in semantic analyzer? @@ -407,7 +408,8 @@ def function_type(func: FuncBase, fallback: Instance) -> FunctionLike: def callable_type(fdef: FuncItem, fallback: Instance, - ret_type: Optional[Type] = None) -> CallableType: + ret_type: Optional[Type] = None, + no_self: bool = False) -> CallableType: # TODO: somewhat unfortunate duplication with prepare_method_signature in semanal if fdef.info and not fdef.is_static and fdef.arg_names: self_type = fill_typevars(fdef.info) # type: Type @@ -417,7 +419,7 @@ def callable_type(fdef: FuncItem, fallback: Instance, else: args = [AnyType(TypeOfAny.unannotated)] * len(fdef.arg_names) - return CallableType( + typ = CallableType( args, fdef.arg_kinds, [None if argument_elide_name(n) else n for n in fdef.arg_names], @@ -428,6 +430,11 @@ def callable_type(fdef: FuncItem, fallback: Instance, column=fdef.column, implicit=True, ) + if no_self and typ.arg_types: + typ = typ.copy_modified(arg_types=typ.arg_types[1:], + arg_kinds=typ.arg_kinds[1:], + arg_names=typ.arg_names[1:]) + return typ def try_getting_str_literals(expr: Expression, typ: Type) -> Optional[List[str]]: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index addcc520f61a8..a208e9ef09e17 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6387,6 +6387,42 @@ class MidBase(Base): pass [file init_subclass/__init__.py] [builtins fixtures/object_with_init_subclass.pyi] +[case testInitSubclassUnannotated] +class A: + def __init_subclass__(cls, *args, **kwargs): + super().__init_subclass__(*args, **kwargs) + +class B(A): + pass + +reveal_type(A.__init_subclass__) # N: Revealed type is 'def (*args: Any, **kwargs: Any) -> Any' +[builtins fixtures/object_with_init_subclass.pyi] + +[case testInitSubclassUnannotatedMulti] +from typing import ClassVar, List, Type + +class A: + registered_classes: ClassVar[List[Type[A]]] = [] + def __init_subclass__(cls, *args, register=True, **kwargs): + if register: + cls.registered_classes.append(cls) + super().__init_subclass__(*args, **kwargs) + +class B(A): ... +class C(A, register=False): ... +class D(C): ... +[builtins fixtures/object_with_init_subclass.pyi] + +[case testClassMethodUnannotated] +class C: + def __new__(cls): ... + @classmethod + def meth(cls): ... + +reveal_type(C.meth) # N: Revealed type is 'def () -> Any' +reveal_type(C.__new__) # N: Revealed type is 'def (cls: Type[__main__.C]) -> Any' +[builtins fixtures/classmethod.pyi] + [case testOverrideGenericSelfClassMethod] from typing import Generic, TypeVar, Type, List diff --git a/test-data/unit/fixtures/object_with_init_subclass.pyi b/test-data/unit/fixtures/object_with_init_subclass.pyi index 407e387bc8c05..8f2f5b9f7ee83 100644 --- a/test-data/unit/fixtures/object_with_init_subclass.pyi +++ b/test-data/unit/fixtures/object_with_init_subclass.pyi @@ -1,4 +1,4 @@ -from typing import Sequence, Iterator, TypeVar, Mapping, Iterable, Optional, Union, overload, Tuple, Generic +from typing import Sequence, Iterator, TypeVar, Mapping, Iterable, Optional, Union, overload, Tuple, Generic, List class object: def __init__(self) -> None: ... @@ -34,6 +34,15 @@ class tuple(Generic[T]): pass class function: pass class ellipsis: pass +# copy-pasted from list.pyi +class list(Sequence[T]): + def __iter__(self) -> Iterator[T]: pass + def __mul__(self, x: int) -> list[T]: pass + def __setitem__(self, x: int, v: T) -> None: pass + def __getitem__(self, x: int) -> T: pass + def __add__(self, x: List[T]) -> T: pass + def __contains__(self, item: object) -> bool: pass + # copy-pasted from dict.pyi class dict(Mapping[KT, VT]): @overload From c747d7a464f5f63fd61df5f0828d8565fc806c05 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 20 Oct 2019 17:46:56 +0100 Subject: [PATCH 2/2] Address CR --- mypy/checkmember.py | 8 +++++--- mypy/typeops.py | 15 ++++----------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 4272123b4ca65..7899aa12c2044 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -737,11 +737,13 @@ def analyze_class_attribute_access(itype: Instance, return AnyType(TypeOfAny.from_error) else: assert isinstance(node.node, FuncBase) + typ = function_type(node.node, mx.builtin_type('builtins.function')) # Note: if we are accessing class method on class object, the cls argument is bound. # Annotated and/or explicit class methods go through other code paths above, for - # unannotated implicit class method we can just drop first argument. - return function_type(node.node, mx.builtin_type('builtins.function'), - no_self=node.node.is_class) + # unannotated implicit class methods we do this here. + if node.node.is_class: + typ = bind_self(typ, is_classmethod=True) + return typ def add_class_tvars(t: ProperType, itype: Instance, isuper: Optional[Instance], diff --git a/mypy/typeops.py b/mypy/typeops.py index ef837d0a10a2b..36a7597ec54d1 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -383,15 +383,14 @@ def erase_to_union_or_bound(typ: TypeVarType) -> ProperType: return get_proper_type(typ.upper_bound) -def function_type(func: FuncBase, fallback: Instance, - no_self: bool = False) -> FunctionLike: +def function_type(func: FuncBase, fallback: Instance) -> FunctionLike: if func.type: assert isinstance(func.type, FunctionLike) return func.type else: # Implicit type signature with dynamic types. if isinstance(func, FuncItem): - return callable_type(func, fallback, no_self=no_self) + return callable_type(func, fallback) else: # Broken overloads can have self.type set to None. # TODO: should we instead always set the type in semantic analyzer? @@ -408,8 +407,7 @@ def function_type(func: FuncBase, fallback: Instance, def callable_type(fdef: FuncItem, fallback: Instance, - ret_type: Optional[Type] = None, - no_self: bool = False) -> CallableType: + ret_type: Optional[Type] = None) -> CallableType: # TODO: somewhat unfortunate duplication with prepare_method_signature in semanal if fdef.info and not fdef.is_static and fdef.arg_names: self_type = fill_typevars(fdef.info) # type: Type @@ -419,7 +417,7 @@ def callable_type(fdef: FuncItem, fallback: Instance, else: args = [AnyType(TypeOfAny.unannotated)] * len(fdef.arg_names) - typ = CallableType( + return CallableType( args, fdef.arg_kinds, [None if argument_elide_name(n) else n for n in fdef.arg_names], @@ -430,11 +428,6 @@ def callable_type(fdef: FuncItem, fallback: Instance, column=fdef.column, implicit=True, ) - if no_self and typ.arg_types: - typ = typ.copy_modified(arg_types=typ.arg_types[1:], - arg_kinds=typ.arg_kinds[1:], - arg_names=typ.arg_names[1:]) - return typ def try_getting_str_literals(expr: Expression, typ: Type) -> Optional[List[str]]: