Skip to content

Commit c3cb000

Browse files
carljmJukkaL
authored andcommitted
Inferred subclass override of a ClassVar is also a ClassVar (#4718)
Fixes #4715.
1 parent 88ee411 commit c3cb000

File tree

3 files changed

+31
-1
lines changed

3 files changed

+31
-1
lines changed

mypy/semanal.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1832,6 +1832,7 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False,
18321832
v = Var(lval.name)
18331833
v.info = self.type
18341834
v.is_initialized_in_class = True
1835+
v.is_inferred = not explicit_type
18351836
v.set_line(lval)
18361837
v._fullname = self.qualified_name(lval.name)
18371838
lval.node = v

mypy/semanal_pass3.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
Node, Expression, MypyFile, FuncDef, FuncItem, Decorator, RefExpr, Context, TypeInfo, ClassDef,
1919
Block, TypedDictExpr, NamedTupleExpr, AssignmentStmt, IndexExpr, TypeAliasExpr, NameExpr,
2020
CallExpr, NewTypeExpr, ForStmt, WithStmt, CastExpr, TypeVarExpr, TypeApplication, Lvalue,
21-
TupleExpr, RevealTypeExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef
21+
TupleExpr, RevealTypeExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef,
22+
MDEF,
2223
)
2324
from mypy.types import (
2425
Type, Instance, AnyType, TypeOfAny, CallableType, TupleType, TypeVarType, TypedDictType,
@@ -253,6 +254,18 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
253254
node = self.sem.lookup(s.lvalues[0].name, s, suppress_errors=True)
254255
if node:
255256
self.analyze(node.type_override, node)
257+
# Subclass attribute assignments with no type annotation should be
258+
# assumed to be classvar if overriding a declared classvar from the base
259+
# class.
260+
if (isinstance(s.lvalues[0], NameExpr) and s.lvalues[0].kind == MDEF
261+
and isinstance(s.lvalues[0].node, Var)):
262+
var = s.lvalues[0].node
263+
if var.info is not None and var.is_inferred and not var.is_classvar:
264+
for base in var.info.mro[1:]:
265+
tnode = base.names.get(var.name())
266+
if (tnode is not None and isinstance(tnode.node, Var)
267+
and tnode.node.is_classvar):
268+
var.is_classvar = True
256269
super().visit_assignment_stmt(s)
257270

258271
def visit_for_stmt(self, s: ForStmt) -> None:

test-data/unit/check-classvar.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,22 @@ class A:
245245
class B(A):
246246
x = 2 # type: ClassVar[int]
247247

248+
[case testOverrideClassVarWithImplicitClassVar]
249+
from typing import ClassVar
250+
class A:
251+
x = 1 # type: ClassVar[int]
252+
class B(A):
253+
x = 2
254+
255+
[case testOverrideClassVarWithImplicitThenExplicit]
256+
from typing import ClassVar
257+
class A:
258+
x = 1 # type: ClassVar[int]
259+
class B(A):
260+
x = 2
261+
class C(B):
262+
x = 3 # type: ClassVar[int]
263+
248264
[case testOverrideOnABCSubclass]
249265
from abc import ABCMeta
250266
from typing import ClassVar

0 commit comments

Comments
 (0)