Skip to content

Commit 8d51709

Browse files
wyfoilevkivskyi
authored andcommitted
Fix callable instance variable support (python#10548)
Fixes python#708 and Fixes python#5485 Prevent handling as bounded method of callable members declared as instance variables.
1 parent 52f1dd3 commit 8d51709

File tree

5 files changed

+79
-94
lines changed

5 files changed

+79
-94
lines changed

mypy/checkmember.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -659,15 +659,23 @@ def instance_alias_type(alias: TypeAlias, named_type: Callable[[str], Instance])
659659
return expand_type_by_instance(tp, target)
660660

661661

662-
def analyze_var(
663-
name: str,
664-
var: Var,
665-
itype: Instance,
666-
info: TypeInfo,
667-
mx: MemberContext,
668-
*,
669-
implicit: bool = False,
670-
) -> Type:
662+
def is_instance_var(var: Var, info: TypeInfo) -> bool:
663+
"""Return if var is an instance variable according to PEP 526."""
664+
return (
665+
# check the type_info node is the var (not a decorated function, etc.)
666+
var.name in info.names and info.names[var.name].node is var
667+
and not var.is_classvar
668+
# variables without annotations are treated as classvar
669+
and not var.is_inferred
670+
)
671+
672+
673+
def analyze_var(name: str,
674+
var: Var,
675+
itype: Instance,
676+
info: TypeInfo,
677+
mx: MemberContext, *,
678+
implicit: bool = False) -> Type:
671679
"""Analyze access to an attribute via a Var node.
672680
673681
This is conceptually part of analyze_member_access and the arguments are similar.
@@ -690,7 +698,12 @@ def analyze_var(
690698
t = get_proper_type(expand_type_by_instance(typ, itype))
691699
result: Type = t
692700
typ = get_proper_type(typ)
693-
if var.is_initialized_in_class and isinstance(typ, FunctionLike) and not typ.is_type_obj():
701+
if (
702+
var.is_initialized_in_class
703+
and not is_instance_var(var, info)
704+
and isinstance(typ, FunctionLike)
705+
and not typ.is_type_obj()
706+
):
694707
if mx.is_lvalue:
695708
if var.is_property:
696709
if not var.is_settable_property:

test-data/unit/check-dataclasses.test

Lines changed: 18 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,80 +1304,40 @@ reveal_type(A.__dataclass_fields__) # N: Revealed type is "builtins.dict[builti
13041304

13051305
[builtins fixtures/dict.pyi]
13061306

1307-
[case testDataclassCallableProperty]
1307+
[case testDataclassCallableFieldAccess]
13081308
# flags: --python-version 3.7
13091309
from dataclasses import dataclass
13101310
from typing import Callable
13111311

13121312
@dataclass
13131313
class A:
1314-
foo: Callable[[int], int]
1314+
x: Callable[[int], int]
1315+
y: Callable[[int], int] = lambda i: i
13151316

1316-
def my_foo(x: int) -> int:
1317-
return x
1317+
a = A(lambda i:i)
1318+
x: int = a.x(0)
1319+
y: str = a.y(0) # E: Incompatible types in assignment (expression has type "int", variable has type "str")
1320+
reveal_type(a.x) # N: Revealed type is "def (builtins.int) -> builtins.int"
1321+
reveal_type(a.y) # N: Revealed type is "def (builtins.int) -> builtins.int"
1322+
reveal_type(A.y) # N: Revealed type is "def (builtins.int) -> builtins.int"
13181323

1319-
a = A(foo=my_foo)
1320-
a.foo(1)
1321-
reveal_type(a.foo) # N: Revealed type is "def (builtins.int) -> builtins.int"
1322-
reveal_type(A.foo) # N: Revealed type is "def (builtins.int) -> builtins.int"
1323-
[typing fixtures/typing-medium.pyi]
1324-
[builtins fixtures/dataclasses.pyi]
1325-
1326-
[case testDataclassCallableAssignment]
1327-
# flags: --python-version 3.7
1328-
from dataclasses import dataclass
1329-
from typing import Callable
1330-
1331-
@dataclass
1332-
class A:
1333-
foo: Callable[[int], int]
1334-
1335-
def my_foo(x: int) -> int:
1336-
return x
1337-
1338-
a = A(foo=my_foo)
1339-
1340-
def another_foo(x: int) -> int:
1341-
return x
1342-
1343-
a.foo = another_foo
1344-
[builtins fixtures/dataclasses.pyi]
1345-
1346-
[case testDataclassCallablePropertyWrongType]
1324+
[case testDataclassCallableFieldAssignment]
13471325
# flags: --python-version 3.7
13481326
from dataclasses import dataclass
13491327
from typing import Callable
13501328

13511329
@dataclass
13521330
class A:
1353-
foo: Callable[[int], int]
1331+
x: Callable[[int], int]
13541332

1355-
def my_foo(x: int) -> str:
1356-
return "foo"
1333+
def x(i: int) -> int:
1334+
return i
1335+
def x2(s: str) -> str:
1336+
return s
13571337

1358-
a = A(foo=my_foo) # E: Argument "foo" to "A" has incompatible type "Callable[[int], str]"; expected "Callable[[int], int]"
1359-
[typing fixtures/typing-medium.pyi]
1360-
[builtins fixtures/dataclasses.pyi]
1361-
1362-
[case testDataclassCallablePropertyWrongTypeAssignment]
1363-
# flags: --python-version 3.7
1364-
from dataclasses import dataclass
1365-
from typing import Callable
1366-
1367-
@dataclass
1368-
class A:
1369-
foo: Callable[[int], int]
1370-
1371-
def my_foo(x: int) -> int:
1372-
return x
1373-
1374-
a = A(foo=my_foo)
1375-
1376-
def another_foo(x: int) -> str:
1377-
return "foo"
1378-
1379-
a.foo = another_foo # E: Incompatible types in assignment (expression has type "Callable[[int], str]", variable has type "Callable[[int], int]")
1380-
[typing fixtures/typing-medium.pyi]
1338+
a = A(lambda i:i)
1339+
a.x = x
1340+
a.x = x2 # E: Incompatible types in assignment (expression has type "Callable[[str], str]", variable has type "Callable[[int], int]")
13811341
[builtins fixtures/dataclasses.pyi]
13821342

13831343
[case testDataclassFieldDoesNotFailOnKwargsUnpacking]

test-data/unit/check-functions.test

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -571,39 +571,51 @@ A().f('') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "i
571571

572572

573573
[case testMethodAsDataAttribute]
574-
from typing import Any, Callable
574+
from typing import Any, Callable, ClassVar
575575
class B: pass
576576
x = None # type: Any
577577
class A:
578-
f = x # type: Callable[[A], None]
579-
g = x # type: Callable[[A, B], None]
578+
f = x # type: ClassVar[Callable[[A], None]]
579+
g = x # type: ClassVar[Callable[[A, B], None]]
580580
a = None # type: A
581581
a.f()
582582
a.g(B())
583583
a.f(a) # E: Too many arguments
584584
a.g() # E: Too few arguments
585585

586586
[case testMethodWithInvalidMethodAsDataAttribute]
587-
from typing import Any, Callable
587+
from typing import Any, Callable, ClassVar
588588
class B: pass
589589
x = None # type: Any
590590
class A:
591-
f = x # type: Callable[[], None]
592-
g = x # type: Callable[[B], None]
591+
f = x # type: ClassVar[Callable[[], None]]
592+
g = x # type: ClassVar[Callable[[B], None]]
593593
a = None # type: A
594594
a.f() # E: Attribute function "f" with type "Callable[[], None]" does not accept self argument
595595
a.g() # E: Invalid self argument "A" to attribute function "g" with type "Callable[[B], None]"
596596

597597
[case testMethodWithDynamicallyTypedMethodAsDataAttribute]
598-
from typing import Any, Callable
598+
from typing import Any, Callable, ClassVar
599599
class B: pass
600600
x = None # type: Any
601601
class A:
602-
f = x # type: Callable[[Any], Any]
602+
f = x # type: ClassVar[Callable[[Any], Any]]
603603
a = None # type: A
604604
a.f()
605605
a.f(a) # E: Too many arguments
606606

607+
[case testMethodWithInferredMethodAsDataAttribute]
608+
from typing import Any
609+
def m(self: "A") -> int: ...
610+
611+
class A:
612+
n = m
613+
614+
a = A()
615+
reveal_type(a.n()) # N: Revealed type is "builtins.int"
616+
reveal_type(A.n(a)) # N: Revealed type is "builtins.int"
617+
A.n() # E: Too few arguments
618+
607619
[case testOverloadedMethodAsDataAttribute]
608620
from foo import *
609621
[file foo.pyi]
@@ -645,35 +657,35 @@ a.g(B())
645657
a.g(a) # E: Argument 1 has incompatible type "A[B]"; expected "B"
646658

647659
[case testInvalidMethodAsDataAttributeInGenericClass]
648-
from typing import Any, TypeVar, Generic, Callable
660+
from typing import Any, TypeVar, Generic, Callable, ClassVar
649661
t = TypeVar('t')
650662
class B: pass
651663
class C: pass
652664
x = None # type: Any
653665
class A(Generic[t]):
654-
f = x # type: Callable[[A[B]], None]
666+
f = x # type: ClassVar[Callable[[A[B]], None]]
655667
ab = None # type: A[B]
656668
ac = None # type: A[C]
657669
ab.f()
658670
ac.f() # E: Invalid self argument "A[C]" to attribute function "f" with type "Callable[[A[B]], None]"
659671

660672
[case testPartiallyTypedSelfInMethodDataAttribute]
661-
from typing import Any, TypeVar, Generic, Callable
673+
from typing import Any, TypeVar, Generic, Callable, ClassVar
662674
t = TypeVar('t')
663675
class B: pass
664676
class C: pass
665677
x = None # type: Any
666678
class A(Generic[t]):
667-
f = x # type: Callable[[A], None]
679+
f = x # type: ClassVar[Callable[[A], None]]
668680
ab = None # type: A[B]
669681
ac = None # type: A[C]
670682
ab.f()
671683
ac.f()
672684

673685
[case testCallableDataAttribute]
674-
from typing import Callable
686+
from typing import Callable, ClassVar
675687
class A:
676-
g = None # type: Callable[[A], None]
688+
g = None # type: ClassVar[Callable[[A], None]]
677689
def __init__(self, f: Callable[[], None]) -> None:
678690
self.f = f
679691
a = A(None)

test-data/unit/check-functools.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ Ord() >= 1 # E: Unsupported operand types for >= ("Ord" and "int")
2525

2626
[case testTotalOrderingLambda]
2727
from functools import total_ordering
28-
from typing import Any, Callable
28+
from typing import Any, Callable, ClassVar
2929

3030
@total_ordering
3131
class Ord:
32-
__eq__: Callable[[Any, object], bool] = lambda self, other: False
33-
__lt__: Callable[[Any, "Ord"], bool] = lambda self, other: False
32+
__eq__: ClassVar[Callable[[Any, object], bool]] = lambda self, other: False
33+
__lt__: ClassVar[Callable[[Any, "Ord"], bool]] = lambda self, other: False
3434

3535
reveal_type(Ord() < Ord()) # N: Revealed type is "builtins.bool"
3636
reveal_type(Ord() <= Ord()) # N: Revealed type is "builtins.bool"

test-data/unit/check-selftype.test

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -366,16 +366,16 @@ reveal_type(x.f) # N: Revealed type is "builtins.int"
366366
[builtins fixtures/property.pyi]
367367

368368
[case testSelfTypeProperSupertypeAttribute]
369-
from typing import Callable, TypeVar
369+
from typing import Callable, TypeVar, ClassVar
370370
class K: pass
371371
T = TypeVar('T', bound=K)
372372
class A(K):
373373
@property
374374
def g(self: K) -> int: return 0
375375
@property
376376
def gt(self: T) -> T: return self
377-
f: Callable[[object], int]
378-
ft: Callable[[T], T]
377+
f: ClassVar[Callable[[object], int]]
378+
ft: ClassVar[Callable[[T], T]]
379379

380380
class B(A):
381381
pass
@@ -392,15 +392,15 @@ reveal_type(B().ft()) # N: Revealed type is "__main__.B"
392392
[builtins fixtures/property.pyi]
393393

394394
[case testSelfTypeProperSupertypeAttributeTuple]
395-
from typing import Callable, TypeVar, Tuple
395+
from typing import Callable, TypeVar, Tuple, ClassVar
396396
T = TypeVar('T')
397397
class A(Tuple[int, int]):
398398
@property
399399
def g(self: object) -> int: return 0
400400
@property
401401
def gt(self: T) -> T: return self
402-
f: Callable[[object], int]
403-
ft: Callable[[T], T]
402+
f: ClassVar[Callable[[object], int]]
403+
ft: ClassVar[Callable[[T], T]]
404404

405405
class B(A):
406406
pass
@@ -450,7 +450,7 @@ reveal_type(X1.ft()) # N: Revealed type is "Type[__main__.X]"
450450
[builtins fixtures/property.pyi]
451451

452452
[case testSelfTypeProperSupertypeAttributeGeneric]
453-
from typing import Callable, TypeVar, Generic
453+
from typing import Callable, TypeVar, Generic, ClassVar
454454
Q = TypeVar('Q', covariant=True)
455455
class K(Generic[Q]):
456456
q: Q
@@ -460,8 +460,8 @@ class A(K[Q]):
460460
def g(self: K[object]) -> int: return 0
461461
@property
462462
def gt(self: K[T]) -> T: return self.q
463-
f: Callable[[object], int]
464-
ft: Callable[[T], T]
463+
f: ClassVar[Callable[[object], int]]
464+
ft: ClassVar[Callable[[T], T]]
465465

466466
class B(A[Q]):
467467
pass

0 commit comments

Comments
 (0)