Repository: python/mypy

Issue:
Protocol member expected instance variable, got class variable
**Bug Report**

This program ought to be valid, because all the protocol `Foo` is declaring is that the attribute `foo` is readable and returns an `int`.   This requirement is met by `Bar`'s class variable.

```
from typing import *

class Foo(Protocol):
    @property
    def foo(self) -> int:
        ...

class Bar:
    foo : ClassVar[int] = 42

x : Foo = Bar()
```

However, mypy says `Protocol member Foo.foo expected instance variable, got class variablemypy`.

if you just declare `foo : int  = 42`, mypy accepts it, so clearly it understands that variables qualify as read-only properties, just not *class* variables.


**Your Environment**

- Mypy version used: 0.812
- Mypy configuration options from `mypy.ini` (and other config files):
    ```
    [mypy]
    ignore_missing_imports = True
    follow_imports = silent
    show_column_numbers = True
    check_untyped_defs = True
    ```
- Python version used: 3.8.2



Discussion:

Resolving patch:
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -2658,6 +2658,7 @@ def get_bad_protocol_flags(
         if (
             IS_CLASSVAR in subflags
             and IS_CLASSVAR not in superflags
+            and IS_SETTABLE in superflags
             or IS_CLASSVAR in superflags
             and IS_CLASSVAR not in subflags
             or IS_SETTABLE in superflags
diff --git a/mypy/subtypes.py b/mypy/subtypes.py
--- a/mypy/subtypes.py
+++ b/mypy/subtypes.py
@@ -1027,7 +1027,10 @@ def named_type(fullname: str) -> Instance:
                 if not is_subtype(supertype, subtype):
                     return False
             if not class_obj:
-                if (IS_CLASSVAR in subflags) != (IS_CLASSVAR in superflags):
+                if IS_SETTABLE not in superflags:
+                    if IS_CLASSVAR in superflags and IS_CLASSVAR not in subflags:
+                        return False
+                elif (IS_CLASSVAR in subflags) != (IS_CLASSVAR in superflags):
                     return False
             else:
                 if IS_VAR in superflags and IS_CLASSVAR not in subflags:


Test patch:
diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test
--- a/test-data/unit/check-protocols.test
+++ b/test-data/unit/check-protocols.test
@@ -1153,6 +1153,25 @@ x2 = y2 # E: Incompatible types in assignment (expression has type "PP", variabl
         # N: Protocol member P.attr expected settable variable, got read-only attribute
 [builtins fixtures/property.pyi]
 
+[case testClassVarProtocolImmutable]
+from typing import Protocol, ClassVar
+
+class P(Protocol):
+    @property
+    def x(self) -> int: ...
+
+class C:
+    x: ClassVar[int]
+
+class Bad:
+    x: ClassVar[str]
+
+x: P = C()
+y: P = Bad()  # E: Incompatible types in assignment (expression has type "Bad", variable has type "P") \
+              # N: Following member(s) of "Bad" have conflicts: \
+              # N:     x: expected "int", got "str"
+[builtins fixtures/property.pyi]
+
 [case testSettablePropertyInProtocols]
 from typing import Protocol