Repository: python/mypy

Issue:
`Self` doesn't work when appears in a callable directly nested in `ClassVar`
Currently this test fails
```
[case testTypingSelfCallableClassVar]
from typing import Self, ClassVar, Callable

class C:
    f: ClassVar[Callable[[Self], Self]]
class D(C): ...

reveal_type(D().f())  # N: Revealed type is "__main__.D"
```
Note, according to conventions, callable that appears directly in `ClassVar` should act as a method, not as an instance attribute with a callable type, i.e. in this very specific case callable needs to be made generic.


Discussion:

Resolving patch:
diff --git a/mypy/checkmember.py b/mypy/checkmember.py
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -40,6 +40,7 @@
     class_callable,
     erase_to_bound,
     function_type,
+    get_type_vars,
     make_simplified_union,
     supported_self_type,
     tuple_fallback,
@@ -68,7 +69,6 @@
     TypeVarType,
     UnionType,
     get_proper_type,
-    has_type_vars,
 )
 from mypy.typetraverser import TypeTraverserVisitor
 
@@ -767,6 +767,9 @@ def analyze_var(
                 # and similarly for B1 when checking against B
                 dispatched_type = meet.meet_types(mx.original_type, itype)
                 signature = freshen_all_functions_type_vars(functype)
+                bound = get_proper_type(expand_self_type(var, signature, mx.original_type))
+                assert isinstance(bound, FunctionLike)
+                signature = bound
                 signature = check_self_arg(
                     signature, dispatched_type, var.is_classmethod, mx.context, name, mx.msg
                 )
@@ -960,11 +963,11 @@ def analyze_class_attribute_access(
             #     C.x  # Error, ambiguous access
             #     C[int].x  # Also an error, since C[int] is same as C at runtime
             # Exception is Self type wrapped in ClassVar, that is safe.
-            if node.node.info.self_type is not None and node.node.is_classvar:
-                exclude = node.node.info.self_type.id
-            else:
-                exclude = None
-            if isinstance(t, TypeVarType) and t.id != exclude or has_type_vars(t, exclude):
+            def_vars = set(node.node.info.defn.type_vars)
+            if not node.node.is_classvar and node.node.info.self_type:
+                def_vars.add(node.node.info.self_type)
+            typ_vars = set(get_type_vars(t))
+            if def_vars & typ_vars:
                 # Exception: access on Type[...], including first argument of class methods is OK.
                 if not isinstance(get_proper_type(mx.original_type), TypeType) or node.implicit:
                     if node.node.is_classvar:
@@ -978,7 +981,7 @@ def analyze_class_attribute_access(
             #     C.x -> Any
             #     C[int].x -> int
             t = get_proper_type(expand_self_type(node.node, t, itype))
-            t = erase_typevars(expand_type_by_instance(t, isuper))
+            t = erase_typevars(expand_type_by_instance(t, isuper), {tv.id for tv in def_vars})
 
         is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or (
             isinstance(node.node, FuncBase) and node.node.is_class
diff --git a/mypy/types.py b/mypy/types.py
--- a/mypy/types.py
+++ b/mypy/types.py
@@ -3236,12 +3236,11 @@ def replace_alias_tvars(
 
 
 class HasTypeVars(TypeQuery[bool]):
-    def __init__(self, exclude: TypeVarId | None = None) -> None:
+    def __init__(self) -> None:
         super().__init__(any)
-        self.exclude = exclude
 
     def visit_type_var(self, t: TypeVarType) -> bool:
-        return t.id != self.exclude
+        return True
 
     def visit_type_var_tuple(self, t: TypeVarTupleType) -> bool:
         return True
@@ -3250,9 +3249,9 @@ def visit_param_spec(self, t: ParamSpecType) -> bool:
         return True
 
 
-def has_type_vars(typ: Type, exclude: TypeVarId | None = None) -> bool:
+def has_type_vars(typ: Type) -> bool:
     """Check if a type contains any type variables (recursively)."""
-    return typ.accept(HasTypeVars(exclude))
+    return typ.accept(HasTypeVars())
 
 
 class HasRecursiveType(TypeQuery[bool]):


Test patch:
diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test
--- a/test-data/unit/check-selftype.test
+++ b/test-data/unit/check-selftype.test
@@ -1750,3 +1750,25 @@ from typing import Self, final
 class C:
     def meth(self) -> Self:
         return C()  # OK for final classes
+
+[case testTypingSelfCallableClassVar]
+from typing import Self, ClassVar, Callable, TypeVar
+
+class C:
+    f: ClassVar[Callable[[Self], Self]]
+class D(C): ...
+
+reveal_type(D.f)  # N: Revealed type is "def (__main__.D) -> __main__.D"
+reveal_type(D().f)  # N: Revealed type is "def () -> __main__.D"
+
+[case testSelfTypeCallableClassVarOldStyle]
+from typing import ClassVar, Callable, TypeVar
+
+T = TypeVar("T")
+class C:
+    f: ClassVar[Callable[[T], T]]
+
+class D(C): ...
+
+reveal_type(D.f)  # N: Revealed type is "def [T] (T`-1) -> T`-1"
+reveal_type(D().f)  # N: Revealed type is "def () -> __main__.D"