Skip to content

Commit 1e23aa9

Browse files
committed
Inferred subclass override of a ClassVar is also a ClassVar. Fixes python#4715.
1 parent 9374acf commit 1e23aa9

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
@@ -1995,6 +1995,7 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False,
19951995
v = Var(lval.name)
19961996
v.info = self.type
19971997
v.is_initialized_in_class = True
1998+
v.is_inferred = not explicit_type
19981999
v.set_line(lval)
19992000
v._fullname = self.qualified_name(lval.name)
20002001
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,
@@ -258,6 +259,18 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
258259
node = self.sem.lookup(s.lvalues[0].name, s, suppress_errors=True)
259260
if node:
260261
self.analyze(node.type_override, node)
262+
# Subclass attribute assignments with no type annotation should be
263+
# assumed to be classvar if overriding a declared classvar from the base
264+
# class.
265+
if (isinstance(s.lvalues[0], NameExpr) and s.lvalues[0].kind == MDEF
266+
and isinstance(s.lvalues[0].node, Var)):
267+
var = s.lvalues[0].node
268+
if var.info is not None and var.is_inferred and not var.is_classvar:
269+
for base in var.info.mro[1:]:
270+
tnode = base.names.get(var.name())
271+
if (tnode is not None and isinstance(tnode.node, Var)
272+
and tnode.node.is_classvar):
273+
var.is_classvar = True
261274
super().visit_assignment_stmt(s)
262275

263276
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)