Skip to content

Commit 715b982

Browse files
authored
Handle union types when binding self (python#18867)
Currently we only bind self if the type is callable, but we actually should do this for all callable items in a union. This use case is probably quite niche (since adding an annotation makes a variable an instance variable, and we rarely infer unions). I found it when looking at `checkmember`-related issues it was easy to handle it. I also use this opportunity to refactor and add comments to `analyze_var()`.
1 parent a35e3c0 commit 715b982

File tree

2 files changed

+59
-35
lines changed

2 files changed

+59
-35
lines changed

mypy/checkmember.py

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -870,15 +870,13 @@ def analyze_var(
870870
mx.msg.read_only_property(name, itype.type, mx.context)
871871
if var.is_classvar:
872872
mx.msg.cant_assign_to_classvar(name, mx.context)
873-
t = freshen_all_functions_type_vars(typ)
874-
t = expand_self_type_if_needed(t, mx, var, original_itype)
875-
t = expand_type_by_instance(t, itype)
876-
freeze_all_type_vars(t)
877-
result = t
878-
typ = get_proper_type(typ)
873+
# This is the most common case for variables, so start with this.
874+
result = expand_without_binding(typ, var, itype, original_itype, mx)
879875

876+
# A non-None value indicates that we should actually bind self for this variable.
880877
call_type: ProperType | None = None
881878
if var.is_initialized_in_class and (not is_instance_var(var) or mx.is_operator):
879+
typ = get_proper_type(typ)
882880
if isinstance(typ, FunctionLike) and not typ.is_type_obj():
883881
call_type = typ
884882
elif var.is_property:
@@ -888,37 +886,23 @@ def analyze_var(
888886
else:
889887
call_type = typ
890888

889+
# Bound variables with callable types are treated like methods
890+
# (these are usually method aliases like __rmul__ = __mul__).
891891
if isinstance(call_type, FunctionLike) and not call_type.is_type_obj():
892-
if mx.is_lvalue and not mx.suppress_errors:
893-
if var.is_property and not var.is_settable_property:
894-
mx.msg.read_only_property(name, itype.type, mx.context)
895-
elif not var.is_property:
896-
mx.msg.cant_assign_to_method(mx.context)
897-
898-
if not var.is_staticmethod:
899-
# Class-level function objects and classmethods become bound methods:
900-
# the former to the instance, the latter to the class.
901-
functype: FunctionLike = call_type
902-
signature = freshen_all_functions_type_vars(functype)
903-
bound = get_proper_type(expand_self_type(var, signature, mx.original_type))
904-
assert isinstance(bound, FunctionLike)
905-
signature = bound
906-
signature = check_self_arg(
907-
signature, mx.self_type, var.is_classmethod, mx.context, name, mx.msg
908-
)
909-
signature = bind_self(signature, mx.self_type, var.is_classmethod)
910-
expanded_signature = expand_type_by_instance(signature, itype)
911-
freeze_all_type_vars(expanded_signature)
912-
if var.is_property:
913-
# A property cannot have an overloaded type => the cast is fine.
914-
assert isinstance(expanded_signature, CallableType)
915-
if var.is_settable_property and mx.is_lvalue and var.setter_type is not None:
916-
# TODO: use check_call() to infer better type, same as for __set__().
917-
result = expanded_signature.arg_types[0]
918-
else:
919-
result = expanded_signature.ret_type
892+
if mx.is_lvalue and not var.is_property and not mx.suppress_errors:
893+
mx.msg.cant_assign_to_method(mx.context)
894+
895+
# Bind the self type for each callable component (when needed).
896+
if call_type and not var.is_staticmethod:
897+
bound_items = []
898+
for ct in call_type.items if isinstance(call_type, UnionType) else [call_type]:
899+
p_ct = get_proper_type(ct)
900+
if isinstance(p_ct, FunctionLike) and not p_ct.is_type_obj():
901+
item = expand_and_bind_callable(p_ct, var, itype, name, mx)
920902
else:
921-
result = expanded_signature
903+
item = expand_without_binding(ct, var, itype, original_itype, mx)
904+
bound_items.append(item)
905+
result = UnionType.make_union(bound_items)
922906
else:
923907
if not var.is_ready and not mx.no_deferral:
924908
mx.not_ready_callback(var.name, mx.context)
@@ -937,6 +921,37 @@ def analyze_var(
937921
return result
938922

939923

924+
def expand_without_binding(
925+
typ: Type, var: Var, itype: Instance, original_itype: Instance, mx: MemberContext
926+
) -> Type:
927+
typ = freshen_all_functions_type_vars(typ)
928+
typ = expand_self_type_if_needed(typ, mx, var, original_itype)
929+
expanded = expand_type_by_instance(typ, itype)
930+
freeze_all_type_vars(expanded)
931+
return expanded
932+
933+
934+
def expand_and_bind_callable(
935+
functype: FunctionLike, var: Var, itype: Instance, name: str, mx: MemberContext
936+
) -> Type:
937+
functype = freshen_all_functions_type_vars(functype)
938+
typ = get_proper_type(expand_self_type(var, functype, mx.original_type))
939+
assert isinstance(typ, FunctionLike)
940+
typ = check_self_arg(typ, mx.self_type, var.is_classmethod, mx.context, name, mx.msg)
941+
typ = bind_self(typ, mx.self_type, var.is_classmethod)
942+
expanded = expand_type_by_instance(typ, itype)
943+
freeze_all_type_vars(expanded)
944+
if not var.is_property:
945+
return expanded
946+
# TODO: a decorated property can result in Overloaded here.
947+
assert isinstance(expanded, CallableType)
948+
if var.is_settable_property and mx.is_lvalue and var.setter_type is not None:
949+
# TODO: use check_call() to infer better type, same as for __set__().
950+
return expanded.arg_types[0]
951+
else:
952+
return expanded.ret_type
953+
954+
940955
def expand_self_type_if_needed(
941956
t: Type, mx: MemberContext, var: Var, itype: Instance, is_class: bool = False
942957
) -> Type:

test-data/unit/check-classvar.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,12 @@ class C:
334334
c:C
335335
c.foo() # E: Too few arguments \
336336
# N: "foo" is considered instance variable, to make it class variable use ClassVar[...]
337+
338+
[case testClassVarUnionBoundOnInstance]
339+
from typing import Union, Callable, ClassVar
340+
341+
class C:
342+
def f(self) -> int: ...
343+
g: ClassVar[Union[Callable[[C], int], int]] = f
344+
345+
reveal_type(C().g) # N: Revealed type is "Union[def () -> builtins.int, builtins.int]"

0 commit comments

Comments
 (0)