Repository: python/mypy

Issue:
generic mutable class attributes introduce a vent in the type system
```py
from typing import Generic, TypeVar

T = TypeVar("T")


class A(Generic[T]):
    shared_attr: list[T] = []

a1 = A[str]()
a1.shared_attr.append("AMONGUS")

a2 = A[int]()
a2.shared_attr.append(1)

value = a2.class_attr[0]
reveal_type(value)  # revealed type: int, actual value: AMONGUS
```

In this example the shared attribute `shared_attr` is imposted upon by different instantiations of that class, causing a sus amongus to be appeared.


Discussion:
Thanks.

I'm guessing part of this is the legacy of pre-PEP 526 days, but it looks like we also don't reject the following:
```
class A(Generic[T]):
    shared_attr: ClassVar[list[T]] = []
```
PEP 526 is actually quite clear about this:
> Note that a ClassVar parameter cannot include any type variables, regardless of the level of nesting: ClassVar[T] and ClassVar[List[Set[T]]] are both invalid if T is a type variable.

Also cc @erictraut since it looks to me like Pyright doesn't handle this either?
I can work on this if no one else has started yet 🙂 

Patch:
diff --git a/mypy/message_registry.py b/mypy/message_registry.py
--- a/mypy/message_registry.py
+++ b/mypy/message_registry.py
@@ -204,6 +204,10 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage":
     'Cannot override class variable (previously declared on base class "{}") with instance '
     "variable"
 )
+CLASS_VAR_WITH_TYPEVARS: Final = 'ClassVar cannot contain type variables'
+CLASS_VAR_OUTSIDE_OF_CLASS: Final = (
+    'ClassVar can only be used for assignments in class body'
+)
 
 # Protocol
 RUNTIME_PROTOCOL_EXPECTED: Final = ErrorMessage(
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -94,7 +94,7 @@
     TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType,
     get_proper_type, get_proper_types, TypeAliasType, TypeVarLikeType
 )
-from mypy.typeops import function_type
+from mypy.typeops import function_type, get_type_vars
 from mypy.type_visitor import TypeQuery
 from mypy.typeanal import (
     TypeAnalyser, analyze_type_alias, no_subscript_builtin_alias,
@@ -3337,6 +3337,12 @@ def check_classvar(self, s: AssignmentStmt) -> None:
             node = lvalue.node
             if isinstance(node, Var):
                 node.is_classvar = True
+            analyzed = self.anal_type(s.type)
+            if analyzed is not None and get_type_vars(analyzed):
+                # This means that we have a type var defined inside of a ClassVar.
+                # This is not allowed by PEP526.
+                # See https://github.com/python/mypy/issues/11538
+                self.fail(message_registry.CLASS_VAR_WITH_TYPEVARS, s)
         elif not isinstance(lvalue, MemberExpr) or self.is_self_member_ref(lvalue):
             # In case of member access, report error only when assigning to self
             # Other kinds of member assignments should be already reported
@@ -3359,7 +3365,7 @@ def is_final_type(self, typ: Optional[Type]) -> bool:
         return sym.node.fullname in ('typing.Final', 'typing_extensions.Final')
 
     def fail_invalid_classvar(self, context: Context) -> None:
-        self.fail('ClassVar can only be used for assignments in class body', context)
+        self.fail(message_registry.CLASS_VAR_OUTSIDE_OF_CLASS, context)
 
     def process_module_assignment(self, lvals: List[Lvalue], rval: Expression,
                                   ctx: AssignmentStmt) -> None:


Test patch:
diff --git a/mypy/test/testsemanal.py b/mypy/test/testsemanal.py
--- a/mypy/test/testsemanal.py
+++ b/mypy/test/testsemanal.py
@@ -20,20 +20,22 @@
 # Semantic analyzer test cases: dump parse tree
 
 # Semantic analysis test case description files.
-semanal_files = ['semanal-basic.test',
-                 'semanal-expressions.test',
-                 'semanal-classes.test',
-                 'semanal-types.test',
-                 'semanal-typealiases.test',
-                 'semanal-modules.test',
-                 'semanal-statements.test',
-                 'semanal-abstractclasses.test',
-                 'semanal-namedtuple.test',
-                 'semanal-typeddict.test',
-                 'semenal-literal.test',
-                 'semanal-classvar.test',
-                 'semanal-python2.test',
-                 'semanal-lambda.test']
+semanal_files = [
+    'semanal-basic.test',
+    'semanal-expressions.test',
+    'semanal-classes.test',
+    'semanal-types.test',
+    'semanal-typealiases.test',
+    'semanal-modules.test',
+    'semanal-statements.test',
+    'semanal-abstractclasses.test',
+    'semanal-namedtuple.test',
+    'semanal-typeddict.test',
+    'semenal-literal.test',
+    'semanal-classvar.test',
+    'semanal-python2.test',
+    'semanal-lambda.test',
+]
 
 
 def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Options:
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
@@ -285,7 +285,7 @@ main:3: error: Cannot assign to class variable "x" via instance
 from typing import ClassVar, Generic, TypeVar
 T = TypeVar('T')
 class A(Generic[T]):
-    x: ClassVar[T]
+    x: ClassVar[T]  # E: ClassVar cannot contain type variables
     @classmethod
     def foo(cls) -> T:
         return cls.x  # OK
@@ -308,7 +308,7 @@ from typing import ClassVar, Generic, Tuple, TypeVar, Union, Type
 T = TypeVar('T')
 U = TypeVar('U')
 class A(Generic[T, U]):
-    x: ClassVar[Union[T, Tuple[U, Type[U]]]]
+    x: ClassVar[Union[T, Tuple[U, Type[U]]]]  # E: ClassVar cannot contain type variables
     @classmethod
     def foo(cls) -> Union[T, Tuple[U, Type[U]]]:
         return cls.x  # OK
diff --git a/test-data/unit/semanal-classvar.test b/test-data/unit/semanal-classvar.test
--- a/test-data/unit/semanal-classvar.test
+++ b/test-data/unit/semanal-classvar.test
@@ -207,3 +207,14 @@ class B:
         pass
 [out]
 main:4: error: ClassVar can only be used for assignments in class body
+
+[case testClassVarWithTypeVariable]
+from typing import ClassVar, TypeVar, Generic, List
+
+T = TypeVar('T')
+
+class Some(Generic[T]):
+    error: ClassVar[T]  # E: ClassVar cannot contain type variables
+    nested: ClassVar[List[List[T]]]  # E: ClassVar cannot contain type variables
+    ok: ClassVar[int]
+[out]