Repository: python/mypy

Issue:
False negative when accessing annotated class attributes
Consider this example:
```py
class X:
    __slots__ = ('a',)
    a: int

print(X.a) # <member 'a' of 'X' objects>
reveal_type(X.a) # builtins.int
reveal_type(X.a.__get__) # Any
    # "int" has no attribute "__get__"; maybe "__gt__", "__ge__", or "__ne__"?
```
I expect to get `member_descriptor` and some `Callable`, but instead i see two wrong reveals and one error.

I can use this bug to write unsafe code:
```py
class X:
    __slots__ = ('a',)
    a: int

x = X()
x.a = X.a # no error!
assert isinstance(x.a, int), x.a
# AssertionError: <member 'a' of 'X' objects>
```


**My Environment**
- `mypy 0.961 (compiled: no)`
- no command-line flags, no config file
- `CPython 3.10.4`
- Windows 10


Discussion:
Some hints:
* The slots of a class are available in the `slots` attribute of `TypeInfo`
* The relevant code is in `mypy.checkmember`. Probably look at `analyze_type_callable_member_access` and `analyze_type_type_member_access`.
* We can probably just generate an error if trying to access `Cls.x` if `x` has a slot. It's rarely the right thing to do.
This might be related:
```py
class X:
    __slots__ = ('x',)
    x: int = 1

# Traceback (most recent call last):
#   File "...", line ..., in <module>
#     class X:
# ValueError: 'x' in __slots__ conflicts with class variable

reveal_type(X.x) # int
reveal_type(X().x) # int
```
Mypy gives no errors, pylint complains `Value 'x' in slots conflicts with class variable (class-variable-slots-conflict)`.
At runtime i have `ValueError: 'x' in __slots__ conflicts with class variable`
Hello :)
I'm new to open source contribution. 
If this issue is still relevant, I'd like to start working on it.
Could you please assign it to me?


Resolving patch:
diff --git a/mypy/checkmember.py b/mypy/checkmember.py
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -912,6 +912,10 @@ def analyze_class_attribute_access(
         if isinstance(node.node, TypeInfo):
             mx.msg.fail(message_registry.CANNOT_ASSIGN_TO_TYPE, mx.context)
 
+    # Refuse class attribute access if slot defined
+    if info.slots and name in info.slots:
+        mx.msg.fail(message_registry.CLASS_VAR_CONFLICTS_SLOTS.format(name), mx.context)
+
     # If a final attribute was declared on `self` in `__init__`, then it
     # can't be accessed on the class object.
     if node.implicit and isinstance(node.node, Var) and node.node.is_final:
diff --git a/mypy/message_registry.py b/mypy/message_registry.py
--- a/mypy/message_registry.py
+++ b/mypy/message_registry.py
@@ -137,6 +137,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
 MODULE_LEVEL_GETATTRIBUTE: Final = ErrorMessage(
     "__getattribute__ is not valid at the module level"
 )
+CLASS_VAR_CONFLICTS_SLOTS: Final = '"{}" in __slots__ conflicts with class variable access'
 NAME_NOT_IN_SLOTS: Final = ErrorMessage(
     'Trying to assign name "{}" that is not in "__slots__" of type "{}"'
 )


Test patch:
diff --git a/test-data/unit/check-slots.test b/test-data/unit/check-slots.test
--- a/test-data/unit/check-slots.test
+++ b/test-data/unit/check-slots.test
@@ -517,3 +517,13 @@ class A:
         self.b = 2
         self.missing = 3
 [builtins fixtures/tuple.pyi]
+
+[case testSlotsWithClassVar]
+from typing import ClassVar
+class X:
+    __slots__ = ('a',)
+    a: int
+x = X()
+X.a  # E: "a" in __slots__ conflicts with class variable access
+x.a
+[builtins fixtures/tuple.pyi]