From 7841662b3cc6150b6b3975e63157a318c33141d3 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 17 Nov 2021 11:21:39 +0000 Subject: [PATCH] Revert "Fix callable instance variable support (#10548)" (#11571) This reverts commit 6ab0efcc2ae3bcf649098b86d1edbcab2e4b1e9b. Reverting the change since it causes a significant backward compatibility break. Code like this previously worked as expected: ``` class C: def f(self, ...) -> None: pass g = f C().g(...) # Now sometimes generates an error ``` However, #10548 broke this in a subtle way (no error on definition, it sometimes generates an error on call), and instead required the introduction of a ClassVar annotation for `g`, which is non-intuitive and error-prone. For example, some typeshed stubs use method aliases such as the above (e.g. stubs for `logging`), and the change broke those stubs. It's also arguably inconsistent, since normally ClassVar annotations are optional. Any fix to the original issue should avoid breaking method aliases such as the above. Hopefully the fix can be adjusted suitably. The PR may have broken incremental mode somehow as well -- cached and uncached runs sometimes produce different results. The root cause is still unclear, however. --- mypy/checkmember.py | 18 +------ test-data/unit/check-dataclasses.test | 73 ++++++++++++++++++++------- test-data/unit/check-functions.test | 40 +++++---------- test-data/unit/check-functools.test | 6 +-- test-data/unit/check-selftype.test | 18 +++---- test-data/unit/check-slots.test | 3 +- 6 files changed, 83 insertions(+), 75 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 3efc39753627..01c6afeb9cec 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -532,17 +532,6 @@ def instance_alias_type(alias: TypeAlias, return expand_type_by_instance(tp, target) -def is_instance_var(var: Var, info: TypeInfo) -> bool: - """Return if var is an instance variable according to PEP 526.""" - return ( - # check the type_info node is the var (not a decorated function, etc.) - var.name in info.names and info.names[var.name].node is var - and not var.is_classvar - # variables without annotations are treated as classvar - and not var.is_inferred - ) - - def analyze_var(name: str, var: Var, itype: Instance, @@ -571,12 +560,7 @@ def analyze_var(name: str, t = get_proper_type(expand_type_by_instance(typ, itype)) result: Type = t typ = get_proper_type(typ) - if ( - var.is_initialized_in_class - and not is_instance_var(var, info) - and isinstance(typ, FunctionLike) - and not typ.is_type_obj() - ): + if var.is_initialized_in_class and isinstance(typ, FunctionLike) and not typ.is_type_obj(): if mx.is_lvalue: if var.is_property: if not var.is_settable_property: diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 3d5f0a1da6e5..73476a646c99 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1263,45 +1263,81 @@ reveal_type(A.__dataclass_fields__) # N: Revealed type is "builtins.dict[builti [builtins fixtures/dict.pyi] -[case testDataclassCallableFieldAccess] +[case testDataclassCallableProperty] # flags: --python-version 3.7 from dataclasses import dataclass from typing import Callable @dataclass class A: - x: Callable[[int], int] - y: Callable[[int], int] = lambda i: i + foo: Callable[[int], int] -a = A(lambda i:i) -x: int = a.x(0) -y: str = a.y(0) # E: Incompatible types in assignment (expression has type "int", variable has type "str") -reveal_type(a.x) # N: Revealed type is "def (builtins.int) -> builtins.int" -reveal_type(a.y) # N: Revealed type is "def (builtins.int) -> builtins.int" -reveal_type(A.y) # N: Revealed type is "def (builtins.int) -> builtins.int" +def my_foo(x: int) -> int: + return x +a = A(foo=my_foo) +a.foo(1) +reveal_type(a.foo) # N: Revealed type is "def (builtins.int) -> builtins.int" +reveal_type(A.foo) # N: Revealed type is "def (builtins.int) -> builtins.int" +[typing fixtures/typing-medium.pyi] [builtins fixtures/dataclasses.pyi] -[case testDataclassCallableFieldAssignment] +[case testDataclassCallableAssignment] # flags: --python-version 3.7 from dataclasses import dataclass from typing import Callable @dataclass class A: - x: Callable[[int], int] + foo: Callable[[int], int] + +def my_foo(x: int) -> int: + return x -def x(i: int) -> int: - return i -def x2(s: str) -> str: - return s +a = A(foo=my_foo) -a = A(lambda i:i) -a.x = x -a.x = x2 # E: Incompatible types in assignment (expression has type "Callable[[str], str]", variable has type "Callable[[int], int]") +def another_foo(x: int) -> int: + return x + +a.foo = another_foo +[builtins fixtures/dataclasses.pyi] +[case testDataclassCallablePropertyWrongType] +# flags: --python-version 3.7 +from dataclasses import dataclass +from typing import Callable + +@dataclass +class A: + foo: Callable[[int], int] + +def my_foo(x: int) -> str: + return "foo" + +a = A(foo=my_foo) # E: Argument "foo" to "A" has incompatible type "Callable[[int], str]"; expected "Callable[[int], int]" +[typing fixtures/typing-medium.pyi] [builtins fixtures/dataclasses.pyi] +[case testDataclassCallablePropertyWrongTypeAssignment] +# flags: --python-version 3.7 +from dataclasses import dataclass +from typing import Callable + +@dataclass +class A: + foo: Callable[[int], int] + +def my_foo(x: int) -> int: + return x + +a = A(foo=my_foo) + +def another_foo(x: int) -> str: + return "foo" + +a.foo = another_foo # E: Incompatible types in assignment (expression has type "Callable[[int], str]", variable has type "Callable[[int], int]") +[typing fixtures/typing-medium.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassFieldDoesNotFailOnKwargsUnpacking] # flags: --python-version 3.7 @@ -1351,7 +1387,6 @@ class Foo: reveal_type(Foo(bar=1.5)) # N: Revealed type is "__main__.Foo" [builtins fixtures/dataclasses.pyi] - [case testDataclassWithSlotsArg] # flags: --python-version 3.10 from dataclasses import dataclass diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 6279e34a2904..62107686880f 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -599,12 +599,12 @@ A().f('') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "i [case testMethodAsDataAttribute] -from typing import Any, Callable, ClassVar +from typing import Any, Callable class B: pass x = None # type: Any class A: - f = x # type: ClassVar[Callable[[A], None]] - g = x # type: ClassVar[Callable[[A, B], None]] + f = x # type: Callable[[A], None] + g = x # type: Callable[[A, B], None] a = None # type: A a.f() a.g(B()) @@ -612,38 +612,26 @@ a.f(a) # E: Too many arguments a.g() # E: Too few arguments [case testMethodWithInvalidMethodAsDataAttribute] -from typing import Any, Callable, ClassVar +from typing import Any, Callable class B: pass x = None # type: Any class A: - f = x # type: ClassVar[Callable[[], None]] - g = x # type: ClassVar[Callable[[B], None]] + f = x # type: Callable[[], None] + g = x # type: Callable[[B], None] a = None # type: A a.f() # E: Attribute function "f" with type "Callable[[], None]" does not accept self argument a.g() # E: Invalid self argument "A" to attribute function "g" with type "Callable[[B], None]" [case testMethodWithDynamicallyTypedMethodAsDataAttribute] -from typing import Any, Callable, ClassVar +from typing import Any, Callable class B: pass x = None # type: Any class A: - f = x # type: ClassVar[Callable[[Any], Any]] + f = x # type: Callable[[Any], Any] a = None # type: A a.f() a.f(a) # E: Too many arguments -[case testMethodWithInferredMethodAsDataAttribute] -from typing import Any -def m(self: "A") -> int: ... - -class A: - n = m - -a = A() -reveal_type(a.n()) # N: Revealed type is "builtins.int" -reveal_type(A.n(a)) # N: Revealed type is "builtins.int" -A.n() # E: Too few arguments - [case testOverloadedMethodAsDataAttribute] from foo import * [file foo.pyi] @@ -685,35 +673,35 @@ a.g(B()) a.g(a) # E: Argument 1 has incompatible type "A[B]"; expected "B" [case testInvalidMethodAsDataAttributeInGenericClass] -from typing import Any, TypeVar, Generic, Callable, ClassVar +from typing import Any, TypeVar, Generic, Callable t = TypeVar('t') class B: pass class C: pass x = None # type: Any class A(Generic[t]): - f = x # type: ClassVar[Callable[[A[B]], None]] + f = x # type: Callable[[A[B]], None] ab = None # type: A[B] ac = None # type: A[C] ab.f() ac.f() # E: Invalid self argument "A[C]" to attribute function "f" with type "Callable[[A[B]], None]" [case testPartiallyTypedSelfInMethodDataAttribute] -from typing import Any, TypeVar, Generic, Callable, ClassVar +from typing import Any, TypeVar, Generic, Callable t = TypeVar('t') class B: pass class C: pass x = None # type: Any class A(Generic[t]): - f = x # type: ClassVar[Callable[[A], None]] + f = x # type: Callable[[A], None] ab = None # type: A[B] ac = None # type: A[C] ab.f() ac.f() [case testCallableDataAttribute] -from typing import Callable, ClassVar +from typing import Callable class A: - g = None # type: ClassVar[Callable[[A], None]] + g = None # type: Callable[[A], None] def __init__(self, f: Callable[[], None]) -> None: self.f = f a = A(None) diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test index 5332454202cc..33653c8d3fbc 100644 --- a/test-data/unit/check-functools.test +++ b/test-data/unit/check-functools.test @@ -25,12 +25,12 @@ Ord() >= 1 # E: Unsupported operand types for >= ("Ord" and "int") [case testTotalOrderingLambda] from functools import total_ordering -from typing import Any, Callable, ClassVar +from typing import Any, Callable @total_ordering class Ord: - __eq__: ClassVar[Callable[[Any, object], bool]] = lambda self, other: False - __lt__: ClassVar[Callable[[Any, "Ord"], bool]] = lambda self, other: False + __eq__: Callable[[Any, object], bool] = lambda self, other: False + __lt__: Callable[[Any, "Ord"], bool] = lambda self, other: False reveal_type(Ord() < Ord()) # N: Revealed type is "builtins.bool" reveal_type(Ord() <= Ord()) # N: Revealed type is "builtins.bool" diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 7e37276a3f75..a1fa234e6d0d 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -366,7 +366,7 @@ reveal_type(x.f) # N: Revealed type is "builtins.int" [builtins fixtures/property.pyi] [case testSelfTypeProperSupertypeAttribute] -from typing import Callable, TypeVar, ClassVar +from typing import Callable, TypeVar class K: pass T = TypeVar('T', bound=K) class A(K): @@ -374,8 +374,8 @@ class A(K): def g(self: K) -> int: return 0 @property def gt(self: T) -> T: return self - f: ClassVar[Callable[[object], int]] - ft: ClassVar[Callable[[T], T]] + f: Callable[[object], int] + ft: Callable[[T], T] class B(A): pass @@ -392,15 +392,15 @@ reveal_type(B().ft()) # N: Revealed type is "__main__.B*" [builtins fixtures/property.pyi] [case testSelfTypeProperSupertypeAttributeTuple] -from typing import Callable, TypeVar, Tuple, ClassVar +from typing import Callable, TypeVar, Tuple T = TypeVar('T') class A(Tuple[int, int]): @property def g(self: object) -> int: return 0 @property def gt(self: T) -> T: return self - f: ClassVar[Callable[[object], int]] - ft: ClassVar[Callable[[T], T]] + f: Callable[[object], int] + ft: Callable[[T], T] class B(A): pass @@ -450,7 +450,7 @@ reveal_type(X1.ft()) # N: Revealed type is "Type[__main__.X]" [builtins fixtures/property.pyi] [case testSelfTypeProperSupertypeAttributeGeneric] -from typing import Callable, TypeVar, Generic, ClassVar +from typing import Callable, TypeVar, Generic Q = TypeVar('Q', covariant=True) class K(Generic[Q]): q: Q @@ -460,8 +460,8 @@ class A(K[Q]): def g(self: K[object]) -> int: return 0 @property def gt(self: K[T]) -> T: return self.q - f: ClassVar[Callable[[object], int]] - ft: ClassVar[Callable[[T], T]] + f: Callable[[object], int] + ft: Callable[[T], T] class B(A[Q]): pass diff --git a/test-data/unit/check-slots.test b/test-data/unit/check-slots.test index 96e4eba3c966..254aa983f82f 100644 --- a/test-data/unit/check-slots.test +++ b/test-data/unit/check-slots.test @@ -361,7 +361,8 @@ a.a = 1 a.b = custom_obj a.c = custom_obj a.d = custom_obj -a.e = custom_obj +# TODO: Should this be allowed? +a.e = custom_obj # E: Cannot assign to a method [out] [builtins fixtures/tuple.pyi] [builtins fixtures/dict.pyi]