Skip to content

Commit 9674679

Browse files
authored
Merge branch 'master' into master
2 parents 0952ddd + 715b982 commit 9674679

File tree

6 files changed

+107
-40
lines changed

6 files changed

+107
-40
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:

mypy/semanal.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3464,8 +3464,9 @@ def record_special_form_lvalue(self, s: AssignmentStmt) -> None:
34643464
def analyze_enum_assign(self, s: AssignmentStmt) -> bool:
34653465
"""Check if s defines an Enum."""
34663466
if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, EnumCallExpr):
3467-
# Already analyzed enum -- nothing to do here.
3468-
return True
3467+
# This is an analyzed enum definition.
3468+
# It is valid iff it can be stored correctly, failures were already reported.
3469+
return self._is_single_name_assignment(s)
34693470
return self.enum_call_analyzer.process_enum_call(s, self.is_func_scope())
34703471

34713472
def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
@@ -3474,7 +3475,9 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
34743475
if s.rvalue.analyzed.info.tuple_type and not has_placeholder(
34753476
s.rvalue.analyzed.info.tuple_type
34763477
):
3477-
return True # This is a valid and analyzed named tuple definition, nothing to do here.
3478+
# This is an analyzed named tuple definition.
3479+
# It is valid iff it can be stored correctly, failures were already reported.
3480+
return self._is_single_name_assignment(s)
34783481
if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
34793482
return False
34803483
lvalue = s.lvalues[0]
@@ -3515,8 +3518,9 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool:
35153518
if s.rvalue.analyzed.info.typeddict_type and not has_placeholder(
35163519
s.rvalue.analyzed.info.typeddict_type
35173520
):
3518-
# This is a valid and analyzed typed dict definition, nothing to do here.
3519-
return True
3521+
# This is an analyzed typed dict definition.
3522+
# It is valid iff it can be stored correctly, failures were already reported.
3523+
return self._is_single_name_assignment(s)
35203524
if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
35213525
return False
35223526
lvalue = s.lvalues[0]
@@ -3540,6 +3544,9 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool:
35403544
self.setup_alias_type_vars(defn)
35413545
return True
35423546

3547+
def _is_single_name_assignment(self, s: AssignmentStmt) -> bool:
3548+
return len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr)
3549+
35433550
def analyze_lvalues(self, s: AssignmentStmt) -> None:
35443551
# We cannot use s.type, because analyze_simple_literal_type() will set it.
35453552
explicit = s.unanalyzed_type is not None

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]"

test-data/unit/check-enum.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,3 +2512,15 @@ def list_vals(e: Type[T]) -> list[T]:
25122512

25132513
reveal_type(list_vals(Choices)) # N: Revealed type is "builtins.list[__main__.Choices]"
25142514
[builtins fixtures/enum.pyi]
2515+
2516+
[case testEnumAsClassMemberNoCrash]
2517+
# https://github.com/python/mypy/issues/18736
2518+
from enum import Enum
2519+
2520+
class Base:
2521+
def __init__(self, namespace: tuple[str, ...]) -> None:
2522+
# Not a bug: trigger defer
2523+
names = [name for name in namespace if fail] # E: Name "fail" is not defined
2524+
self.o = Enum("o", names) # E: Enum type as attribute is not supported \
2525+
# E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
2526+
[builtins fixtures/tuple.pyi]

test-data/unit/check-namedtuple.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,3 +1519,14 @@ class C(T):
15191519
c: Union[C, Any]
15201520
reveal_type(c.f()) # N: Revealed type is "Union[builtins.bool, Any]"
15211521
[builtins fixtures/tuple.pyi]
1522+
1523+
[case testNamedTupleAsClassMemberNoCrash]
1524+
# https://github.com/python/mypy/issues/18736
1525+
from collections import namedtuple
1526+
1527+
class Base:
1528+
def __init__(self, namespace: tuple[str, ...]) -> None:
1529+
# Not a bug: trigger defer
1530+
names = [name for name in namespace if fail] # E: Name "fail" is not defined
1531+
self.n = namedtuple("n", names) # E: NamedTuple type as an attribute is not supported
1532+
[builtins fixtures/tuple.pyi]

test-data/unit/check-typeddict.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4138,3 +4138,16 @@ Derived.Params(name="Robert")
41384138
DerivedOverride.Params(name="Robert")
41394139
[builtins fixtures/dict.pyi]
41404140
[typing fixtures/typing-typeddict.pyi]
4141+
4142+
[case testEnumAsClassMemberNoCrash]
4143+
# https://github.com/python/mypy/issues/18736
4144+
from typing import TypedDict
4145+
4146+
class Base:
4147+
def __init__(self, namespace: dict[str, str]) -> None:
4148+
# Not a bug: trigger defer
4149+
names = {n: n for n in namespace if fail} # E: Name "fail" is not defined
4150+
self.d = TypedDict("d", names) # E: TypedDict type as attribute is not supported \
4151+
# E: TypedDict() expects a dictionary literal as the second argument
4152+
[builtins fixtures/dict.pyi]
4153+
[typing fixtures/typing-typeddict.pyi]

0 commit comments

Comments
 (0)