diff --git a/mypy/semanal.py b/mypy/semanal.py index 3d849f085ed6..9acccdc310aa 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1995,6 +1995,7 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False, v = Var(lval.name) v.info = self.type v.is_initialized_in_class = True + v.is_inferred = not explicit_type v.set_line(lval) v._fullname = self.qualified_name(lval.name) lval.node = v diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index 7a8a881852e5..f2019130f2c6 100644 --- a/mypy/semanal_pass3.py +++ b/mypy/semanal_pass3.py @@ -18,7 +18,8 @@ Node, Expression, MypyFile, FuncDef, FuncItem, Decorator, RefExpr, Context, TypeInfo, ClassDef, Block, TypedDictExpr, NamedTupleExpr, AssignmentStmt, IndexExpr, TypeAliasExpr, NameExpr, CallExpr, NewTypeExpr, ForStmt, WithStmt, CastExpr, TypeVarExpr, TypeApplication, Lvalue, - TupleExpr, RevealTypeExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef + TupleExpr, RevealTypeExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef, + MDEF, ) from mypy.types import ( Type, Instance, AnyType, TypeOfAny, CallableType, TupleType, TypeVarType, TypedDictType, @@ -258,6 +259,18 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: node = self.sem.lookup(s.lvalues[0].name, s, suppress_errors=True) if node: self.analyze(node.type_override, node) + # Subclass attribute assignments with no type annotation should be + # assumed to be classvar if overriding a declared classvar from the base + # class. + if (isinstance(s.lvalues[0], NameExpr) and s.lvalues[0].kind == MDEF + and isinstance(s.lvalues[0].node, Var)): + var = s.lvalues[0].node + if var.info is not None and var.is_inferred and not var.is_classvar: + for base in var.info.mro[1:]: + tnode = base.names.get(var.name()) + if (tnode is not None and isinstance(tnode.node, Var) + and tnode.node.is_classvar): + var.is_classvar = True super().visit_assignment_stmt(s) def visit_for_stmt(self, s: ForStmt) -> None: diff --git a/test-data/unit/check-classvar.test b/test-data/unit/check-classvar.test index c20f4f43686c..ba35e1ea8223 100644 --- a/test-data/unit/check-classvar.test +++ b/test-data/unit/check-classvar.test @@ -245,6 +245,22 @@ class A: class B(A): x = 2 # type: ClassVar[int] +[case testOverrideClassVarWithImplicitClassVar] +from typing import ClassVar +class A: + x = 1 # type: ClassVar[int] +class B(A): + x = 2 + +[case testOverrideClassVarWithImplicitThenExplicit] +from typing import ClassVar +class A: + x = 1 # type: ClassVar[int] +class B(A): + x = 2 +class C(B): + x = 3 # type: ClassVar[int] + [case testOverrideOnABCSubclass] from abc import ABCMeta from typing import ClassVar