Repository: python/mypy

Issue:
False positive error on subclass overriding a ClassVar
This seems to be a bug.

With the following code:

```
from typing import ClassVar

class Base:
    name: ClassVar[str]

class Sub(Base):
    name = "Sub"
```

mypy complains that `:7: error: Cannot override class variable (previously declared on base class "Base") with instance variable`.

But the assignment on the subclass is clearly at the class level. Assigning a value to a class attribute that has already been declared as one should not result in an error. I'd expect this error only if the assignment took place e.g. inside `__init__`.

Explicitly annotating the subclass assignment (`name: ClassVar[str] = "Sub"`) gets rid of the error, but that repeated annotation should not be necessary.

Reproduced on mypy 0.580-dev-9374acf2d808905d392b2ec8ea52bfce03f9ecad under Python 3.6.4, with no flags or options.

I realize that inability to distinguish instance vs class attributes is a known mypy limitation (#1097), but in general it just results in false negatives. This seems worth a distinct issue since it appears that mypy is attempting to make the distinction, but doing so incorrectly, resulting in a false positive.



Discussion:
Agreed this is fishy. Do you feel up to submitting a PR to fix this?
I can try to find time to look at it soon.

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- 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
--- 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:


Test patch:
diff --git a/test-data/unit/check-classvar.test b/test-data/unit/check-classvar.test
--- 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