Skip to content

Commit

Permalink
Fix callable instance variable support (python#10548)
Browse files Browse the repository at this point in the history
Fixes python#708 and Fixes python#5485

Prevent handling as bounded method of callable members declared as instance variables.
  • Loading branch information
wyfo authored and ilevkivskyi committed Aug 12, 2022
1 parent 52f1dd3 commit 8d51709
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 94 deletions.
33 changes: 23 additions & 10 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,15 +659,23 @@ def instance_alias_type(alias: TypeAlias, named_type: Callable[[str], Instance])
return expand_type_by_instance(tp, target)


def analyze_var(
name: str,
var: Var,
itype: Instance,
info: TypeInfo,
mx: MemberContext,
*,
implicit: bool = False,
) -> Type:
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,
info: TypeInfo,
mx: MemberContext, *,
implicit: bool = False) -> Type:
"""Analyze access to an attribute via a Var node.
This is conceptually part of analyze_member_access and the arguments are similar.
Expand All @@ -690,7 +698,12 @@ def analyze_var(
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 isinstance(typ, FunctionLike) and not typ.is_type_obj():
if (
var.is_initialized_in_class
and not is_instance_var(var, info)
and isinstance(typ, FunctionLike)
and not typ.is_type_obj()
):
if mx.is_lvalue:
if var.is_property:
if not var.is_settable_property:
Expand Down
76 changes: 18 additions & 58 deletions test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -1304,80 +1304,40 @@ reveal_type(A.__dataclass_fields__) # N: Revealed type is "builtins.dict[builti

[builtins fixtures/dict.pyi]

[case testDataclassCallableProperty]
[case testDataclassCallableFieldAccess]
# flags: --python-version 3.7
from dataclasses import dataclass
from typing import Callable

@dataclass
class A:
foo: Callable[[int], int]
x: Callable[[int], int]
y: Callable[[int], int] = lambda i: i

def my_foo(x: int) -> int:
return x
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"

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 testDataclassCallableAssignment]
# 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) -> int:
return x

a.foo = another_foo
[builtins fixtures/dataclasses.pyi]

[case testDataclassCallablePropertyWrongType]
[case testDataclassCallableFieldAssignment]
# flags: --python-version 3.7
from dataclasses import dataclass
from typing import Callable

@dataclass
class A:
foo: Callable[[int], int]
x: Callable[[int], int]

def my_foo(x: int) -> str:
return "foo"
def x(i: int) -> int:
return i
def x2(s: str) -> str:
return s

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]
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]")
[builtins fixtures/dataclasses.pyi]

[case testDataclassFieldDoesNotFailOnKwargsUnpacking]
Expand Down
40 changes: 26 additions & 14 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -571,39 +571,51 @@ A().f('') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "i


[case testMethodAsDataAttribute]
from typing import Any, Callable
from typing import Any, Callable, ClassVar
class B: pass
x = None # type: Any
class A:
f = x # type: Callable[[A], None]
g = x # type: Callable[[A, B], None]
f = x # type: ClassVar[Callable[[A], None]]
g = x # type: ClassVar[Callable[[A, B], None]]
a = None # type: A
a.f()
a.g(B())
a.f(a) # E: Too many arguments
a.g() # E: Too few arguments

[case testMethodWithInvalidMethodAsDataAttribute]
from typing import Any, Callable
from typing import Any, Callable, ClassVar
class B: pass
x = None # type: Any
class A:
f = x # type: Callable[[], None]
g = x # type: Callable[[B], None]
f = x # type: ClassVar[Callable[[], None]]
g = x # type: ClassVar[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
from typing import Any, Callable, ClassVar
class B: pass
x = None # type: Any
class A:
f = x # type: Callable[[Any], Any]
f = x # type: ClassVar[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]
Expand Down Expand Up @@ -645,35 +657,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
from typing import Any, TypeVar, Generic, Callable, ClassVar
t = TypeVar('t')
class B: pass
class C: pass
x = None # type: Any
class A(Generic[t]):
f = x # type: Callable[[A[B]], None]
f = x # type: ClassVar[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
from typing import Any, TypeVar, Generic, Callable, ClassVar
t = TypeVar('t')
class B: pass
class C: pass
x = None # type: Any
class A(Generic[t]):
f = x # type: Callable[[A], None]
f = x # type: ClassVar[Callable[[A], None]]
ab = None # type: A[B]
ac = None # type: A[C]
ab.f()
ac.f()

[case testCallableDataAttribute]
from typing import Callable
from typing import Callable, ClassVar
class A:
g = None # type: Callable[[A], None]
g = None # type: ClassVar[Callable[[A], None]]
def __init__(self, f: Callable[[], None]) -> None:
self.f = f
a = A(None)
Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-functools.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
from typing import Any, Callable, ClassVar

@total_ordering
class Ord:
__eq__: Callable[[Any, object], bool] = lambda self, other: False
__lt__: Callable[[Any, "Ord"], bool] = lambda self, other: False
__eq__: ClassVar[Callable[[Any, object], bool]] = lambda self, other: False
__lt__: ClassVar[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"
Expand Down
18 changes: 9 additions & 9 deletions test-data/unit/check-selftype.test
Original file line number Diff line number Diff line change
Expand Up @@ -366,16 +366,16 @@ reveal_type(x.f) # N: Revealed type is "builtins.int"
[builtins fixtures/property.pyi]

[case testSelfTypeProperSupertypeAttribute]
from typing import Callable, TypeVar
from typing import Callable, TypeVar, ClassVar
class K: pass
T = TypeVar('T', bound=K)
class A(K):
@property
def g(self: K) -> int: return 0
@property
def gt(self: T) -> T: return self
f: Callable[[object], int]
ft: Callable[[T], T]
f: ClassVar[Callable[[object], int]]
ft: ClassVar[Callable[[T], T]]

class B(A):
pass
Expand All @@ -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
from typing import Callable, TypeVar, Tuple, ClassVar
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: Callable[[object], int]
ft: Callable[[T], T]
f: ClassVar[Callable[[object], int]]
ft: ClassVar[Callable[[T], T]]

class B(A):
pass
Expand Down Expand Up @@ -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
from typing import Callable, TypeVar, Generic, ClassVar
Q = TypeVar('Q', covariant=True)
class K(Generic[Q]):
q: Q
Expand All @@ -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: Callable[[object], int]
ft: Callable[[T], T]
f: ClassVar[Callable[[object], int]]
ft: ClassVar[Callable[[T], T]]

class B(A[Q]):
pass
Expand Down

0 comments on commit 8d51709

Please sign in to comment.