Skip to content

Commit 1de9b6a

Browse files
committed
Move ClassVar override checks to TypeChecker
1 parent cd61a5d commit 1de9b6a

File tree

4 files changed

+65
-103
lines changed

4 files changed

+65
-103
lines changed

mypy/checker.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,15 @@ def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Type,
11491149
lvalue.kind == MDEF and
11501150
len(lvalue_node.info.bases) > 0):
11511151

1152+
for base in lvalue_node.info.mro[1:]:
1153+
base_node = base.names.get(lvalue_node.name())
1154+
if base_node is not None:
1155+
if not self.check_compatibility_classvar_super(lvalue_node,
1156+
base,
1157+
base_node.node):
1158+
# Show only one error per variable
1159+
break
1160+
11521161
for base in lvalue_node.info.mro[1:]:
11531162
# Only check __slots__ against the 'object'
11541163
# If a base class defines a Tuple of 3 elements, a child of
@@ -1257,6 +1266,17 @@ def lvalue_type_from_base(self, expr_node: Var,
12571266

12581267
return None, None
12591268

1269+
def check_compatibility_classvar_super(self, node: Var,
1270+
base: TypeInfo, base_node: Node) -> bool:
1271+
if (isinstance(base_node, Var) and
1272+
((node.is_classvar and not base_node.is_classvar) or
1273+
(not node.is_classvar and base_node.is_classvar))):
1274+
self.fail('Invalid class attribute definition '
1275+
'(previously declared on base class "%s")' % base.name(),
1276+
node)
1277+
return False
1278+
return True
1279+
12601280
def check_assignment_to_multiple_lvalues(self, lvalues: List[Lvalue], rvalue: Expression,
12611281
context: Context,
12621282
infer_lvalue_type: bool = True) -> None:

mypy/semanal.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,10 +2164,7 @@ def check_classvar(self, s: AssignmentStmt) -> None:
21642164
lvalue = s.lvalues[0]
21652165
if len(s.lvalues) != 1 or not isinstance(lvalue, NameExpr):
21662166
return
2167-
is_classvar = self.check_classvar_definition(lvalue, s.type)
2168-
if self.is_class_scope() and isinstance(lvalue.node, Var):
2169-
# Assignments to class variables outside class scope are checked later
2170-
self.check_classvar_override(lvalue.node, is_classvar)
2167+
self.check_classvar_definition(lvalue, s.type)
21712168

21722169
def check_classvar_definition(self, lvalue: NameExpr, typ: Type) -> bool:
21732170
if not isinstance(typ, UnboundType):
@@ -2186,25 +2183,6 @@ def check_classvar_definition(self, lvalue: NameExpr, typ: Type) -> bool:
21862183
self.fail('Invalid ClassVar definition', lvalue)
21872184
return False
21882185

2189-
def check_classvar_override(self, node: Var, is_classvar: bool) -> None:
2190-
name = node.name()
2191-
for base in self.type.mro[1:]:
2192-
tnode = base.names.get(name)
2193-
if tnode is None:
2194-
continue
2195-
base_node = tnode.node
2196-
if isinstance(base_node, Var):
2197-
v = base_node
2198-
if (is_classvar and not v.is_classvar) or (not is_classvar and v.is_classvar):
2199-
self.fail_classvar_base_incompatibility(node, v)
2200-
return
2201-
2202-
def fail_classvar_base_incompatibility(self, shadowing: Var, original: Var) -> None:
2203-
base_name = original.info.name()
2204-
self.fail('Invalid class attribute definition '
2205-
'(previously declared on base class "%s")' % base_name,
2206-
shadowing)
2207-
22082186
def visit_decorator(self, dec: Decorator) -> None:
22092187
for d in dec.decorators:
22102188
d.accept(self)

test-data/unit/check-classvar.test

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,47 @@ class B(A):
220220
x = None # type: ClassVar[str]
221221
[out]
222222
main:5: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int")
223+
224+
[case testOverrideWithNormalAttribute]
225+
from typing import ClassVar
226+
class A:
227+
x = 1 # type: ClassVar[int]
228+
class B(A):
229+
x = 2 # type: int
230+
[out]
231+
main:5: error: Invalid class attribute definition (previously declared on base class "A")
232+
233+
[case testOverrideWithAttributeWithClassVar]
234+
from typing import ClassVar
235+
class A:
236+
x = 1 # type: int
237+
class B(A):
238+
x = 2 # type: ClassVar[int]
239+
[out]
240+
main:5: error: Invalid class attribute definition (previously declared on base class "A")
241+
242+
[case testOverrideClassVarManyBases]
243+
from typing import ClassVar
244+
class A:
245+
x = 1 # type: ClassVar[int]
246+
class B:
247+
x = 2 # type: int
248+
class C(A, B):
249+
x = 3 # type: ClassVar[int]
250+
[out]
251+
main:7: error: Invalid class attribute definition (previously declared on base class "B")
252+
253+
[case testOverrideClassVarWithClassVar]
254+
from typing import ClassVar
255+
class A:
256+
x = 1 # type: ClassVar[int]
257+
class B(A):
258+
x = 2 # type: ClassVar[int]
259+
260+
[case testOverrideOnABCSubclass]
261+
from abc import ABCMeta
262+
from typing import ClassVar
263+
class A(metaclass=ABCMeta):
264+
x = None # type: ClassVar[int]
265+
class B(A):
266+
x = 0 # type: ClassVar[int]

test-data/unit/semanal-classvar.test

Lines changed: 0 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -54,86 +54,6 @@ MypyFile:1(
5454
IntExpr(1)
5555
Any)))
5656

57-
[case testOverrideWithNormalAttribute]
58-
from typing import ClassVar
59-
class A:
60-
x = 1 # type: ClassVar[int]
61-
class B(A):
62-
x = 2 # type: int
63-
[out]
64-
main:5: error: Invalid class attribute definition (previously declared on base class "A")
65-
66-
[case testOverrideWithAttributeWithClassVar]
67-
from typing import ClassVar
68-
class A:
69-
x = 1 # type: int
70-
class B(A):
71-
x = 2 # type: ClassVar[int]
72-
[out]
73-
main:5: error: Invalid class attribute definition (previously declared on base class "A")
74-
75-
[case testOverrideClassVarManyBases]
76-
from typing import ClassVar
77-
class A:
78-
x = 1 # type: ClassVar[int]
79-
class B:
80-
x = 2 # type: int
81-
class C(A, B):
82-
x = 3 # type: ClassVar[int]
83-
[out]
84-
main:7: error: Invalid class attribute definition (previously declared on base class "B")
85-
86-
[case testOverrideClassVarWithClassVar]
87-
from typing import ClassVar
88-
class A:
89-
x = 1 # type: ClassVar[int]
90-
class B(A):
91-
x = 2 # type: ClassVar[int]
92-
[out]
93-
MypyFile:1(
94-
ImportFrom:1(typing, [ClassVar])
95-
ClassDef:2(
96-
A
97-
AssignmentStmt:3(
98-
NameExpr(x [m])
99-
IntExpr(1)
100-
builtins.int))
101-
ClassDef:4(
102-
B
103-
BaseType(
104-
__main__.A)
105-
AssignmentStmt:5(
106-
NameExpr(x [m])
107-
IntExpr(2)
108-
builtins.int)))
109-
110-
[case testOverrideOnABCSubclass]
111-
from abc import ABCMeta
112-
from typing import ClassVar
113-
class A(metaclass=ABCMeta):
114-
x = None # type: ClassVar[int]
115-
class B(A):
116-
x = 0 # type: ClassVar[int]
117-
[out]
118-
MypyFile:1(
119-
ImportFrom:1(abc, [ABCMeta])
120-
ImportFrom:2(typing, [ClassVar])
121-
ClassDef:3(
122-
A
123-
Metaclass(ABCMeta)
124-
AssignmentStmt:4(
125-
NameExpr(x [m])
126-
NameExpr(None [builtins.None])
127-
builtins.int))
128-
ClassDef:5(
129-
B
130-
BaseType(
131-
__main__.A)
132-
AssignmentStmt:6(
133-
NameExpr(x [m])
134-
IntExpr(0)
135-
builtins.int)))
136-
13757
[case testClassVarWithTypeVar]
13858
from typing import ClassVar, TypeVar
13959
T = TypeVar('T')

0 commit comments

Comments
 (0)