diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 1c38bb4f00dc..08d4ff412e4e 100644 --- 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 index 242d64ee9075..1de294f9952d 100644 --- 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]): diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 7fcac7ed75e9..494ae54400fb 100644 --- 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"