Skip to content

Commit 53d071c

Browse files
ilevkivskyiericmarkmartin
authored andcommitted
Delete old meet hack from checkmember.py (python#18662)
The hack to use `meet_types(original_type, itype)` to select a correct element from a union appeared before we added proper handling of unions in various places related to `checkmember.py`. This is error prone, since `meet_types()` is one of least precise type ops (for good and bad reasons), and results in obscure bugs, see e.g. python#15600 This hack should not be needed anymore, now we have three-level information available everywhere we needed it: * `original_type` - as the name says, a type from which everything started. This is used for error messages and for plugin hooks. * `self_type` - a specific element of the union is the original type is a union. The name is because this is what will be ultimately used by `bind_self()` * `itype` the actual instance type where we look up the attribute (this will be e.g. a fallback if the `self_type` is not an instance)
1 parent da7909b commit 53d071c

File tree

2 files changed

+21
-57
lines changed

2 files changed

+21
-57
lines changed

mypy/checker.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -4600,10 +4600,8 @@ def check_member_assignment(
46004600
bound_method = analyze_decorator_or_funcbase_access(
46014601
defn=dunder_set,
46024602
itype=attribute_type,
4603-
info=attribute_type.type,
4604-
self_type=attribute_type,
46054603
name="__set__",
4606-
mx=mx,
4604+
mx=mx.copy_modified(self_type=attribute_type),
46074605
)
46084606
typ = map_instance_to_supertype(attribute_type, dunder_set.info)
46094607
dunder_set_type = expand_type_by_instance(bound_method, typ)

mypy/checkmember.py

+20-54
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from collections.abc import Sequence
66
from typing import TYPE_CHECKING, Callable, cast
77

8-
from mypy import meet, message_registry, subtypes
8+
from mypy import message_registry, subtypes
99
from mypy.erasetype import erase_typevars
1010
from mypy.expandtype import (
1111
expand_self_type,
@@ -267,7 +267,9 @@ def may_be_awaitable_attribute(
267267
aw_type = mx.chk.get_precise_awaitable_type(typ, local_errors)
268268
if aw_type is None:
269269
return False
270-
_ = _analyze_member_access(name, aw_type, mx, override_info)
270+
_ = _analyze_member_access(
271+
name, aw_type, mx.copy_modified(self_type=aw_type), override_info
272+
)
271273
return not local_errors.has_new_errors()
272274

273275

@@ -323,7 +325,7 @@ def analyze_instance_member_access(
323325
assert isinstance(getter, Decorator)
324326
if mx.is_lvalue and (len(items := method.items) > 1):
325327
mx.chk.warn_deprecated(items[1], mx.context)
326-
return analyze_var(name, getter.var, typ, info, mx)
328+
return analyze_var(name, getter.var, typ, mx)
327329

328330
if mx.is_lvalue:
329331
mx.msg.cant_assign_to_method(mx.context)
@@ -340,11 +342,8 @@ def analyze_instance_member_access(
340342
signature = method.type
341343
signature = freshen_all_functions_type_vars(signature)
342344
if not method.is_static:
343-
# TODO: use proper treatment of special methods on unions instead
344-
# of this hack here and below (i.e. mx.self_type).
345-
dispatched_type = meet.meet_types(mx.original_type, typ)
346345
signature = check_self_arg(
347-
signature, dispatched_type, method.is_class, mx.context, name, mx.msg
346+
signature, mx.self_type, method.is_class, mx.context, name, mx.msg
348347
)
349348
signature = bind_self(signature, mx.self_type, is_classmethod=method.is_class)
350349
# TODO: should we skip these steps for static methods as well?
@@ -536,7 +535,7 @@ def analyze_member_var_access(
536535
if mx.is_lvalue and not mx.chk.get_final_context():
537536
check_final_member(name, info, mx.msg, mx.context)
538537

539-
return analyze_var(name, v, itype, info, mx, implicit=implicit)
538+
return analyze_var(name, v, itype, mx, implicit=implicit)
540539
elif isinstance(v, FuncDef):
541540
assert False, "Did not expect a function"
542541
elif isinstance(v, MypyFile):
@@ -560,12 +559,7 @@ def analyze_member_var_access(
560559
# that the attribute exists
561560
if method and method.info.fullname != "builtins.object":
562561
bound_method = analyze_decorator_or_funcbase_access(
563-
defn=method,
564-
itype=itype,
565-
info=info,
566-
self_type=mx.self_type,
567-
name=method_name,
568-
mx=mx,
562+
defn=method, itype=itype, name=method_name, mx=mx
569563
)
570564
typ = map_instance_to_supertype(itype, method.info)
571565
getattr_type = get_proper_type(expand_type_by_instance(bound_method, typ))
@@ -592,12 +586,7 @@ def analyze_member_var_access(
592586
setattr_meth = info.get_method("__setattr__")
593587
if setattr_meth and setattr_meth.info.fullname != "builtins.object":
594588
bound_type = analyze_decorator_or_funcbase_access(
595-
defn=setattr_meth,
596-
itype=itype,
597-
info=info,
598-
self_type=mx.self_type,
599-
name=name,
600-
mx=mx.copy_modified(is_lvalue=False),
589+
defn=setattr_meth, itype=itype, name=name, mx=mx.copy_modified(is_lvalue=False)
601590
)
602591
typ = map_instance_to_supertype(itype, setattr_meth.info)
603592
setattr_type = get_proper_type(expand_type_by_instance(bound_type, typ))
@@ -683,10 +672,8 @@ def analyze_descriptor_access(
683672
bound_method = analyze_decorator_or_funcbase_access(
684673
defn=dunder_get,
685674
itype=descriptor_type,
686-
info=descriptor_type.type,
687-
self_type=descriptor_type,
688675
name="__get__",
689-
mx=mx,
676+
mx=mx.copy_modified(self_type=descriptor_type),
690677
)
691678

692679
typ = map_instance_to_supertype(descriptor_type, dunder_get.info)
@@ -762,13 +749,7 @@ def is_instance_var(var: Var) -> bool:
762749

763750

764751
def analyze_var(
765-
name: str,
766-
var: Var,
767-
itype: Instance,
768-
info: TypeInfo,
769-
mx: MemberContext,
770-
*,
771-
implicit: bool = False,
752+
name: str, var: Var, itype: Instance, mx: MemberContext, *, implicit: bool = False
772753
) -> Type:
773754
"""Analyze access to an attribute via a Var node.
774755
@@ -807,7 +788,9 @@ def analyze_var(
807788
if isinstance(typ, FunctionLike) and not typ.is_type_obj():
808789
call_type = typ
809790
elif var.is_property:
810-
call_type = get_proper_type(_analyze_member_access("__call__", typ, mx))
791+
call_type = get_proper_type(
792+
_analyze_member_access("__call__", typ, mx.copy_modified(self_type=typ))
793+
)
811794
else:
812795
call_type = typ
813796

@@ -823,20 +806,12 @@ def analyze_var(
823806
# Class-level function objects and classmethods become bound methods:
824807
# the former to the instance, the latter to the class.
825808
functype: FunctionLike = call_type
826-
# Use meet to narrow original_type to the dispatched type.
827-
# For example, assume
828-
# * A.f: Callable[[A1], None] where A1 <: A (maybe A1 == A)
829-
# * B.f: Callable[[B1], None] where B1 <: B (maybe B1 == B)
830-
# * x: Union[A1, B1]
831-
# In `x.f`, when checking `x` against A1 we assume x is compatible with A
832-
# and similarly for B1 when checking against B
833-
dispatched_type = meet.meet_types(mx.original_type, itype)
834809
signature = freshen_all_functions_type_vars(functype)
835810
bound = get_proper_type(expand_self_type(var, signature, mx.original_type))
836811
assert isinstance(bound, FunctionLike)
837812
signature = bound
838813
signature = check_self_arg(
839-
signature, dispatched_type, var.is_classmethod, mx.context, name, mx.msg
814+
signature, mx.self_type, var.is_classmethod, mx.context, name, mx.msg
840815
)
841816
signature = bind_self(signature, mx.self_type, var.is_classmethod)
842817
expanded_signature = expand_type_by_instance(signature, itype)
@@ -946,13 +921,9 @@ def check_self_arg(
946921
For example if the method is defined as:
947922
class A:
948923
def f(self: S) -> T: ...
949-
then for 'x.f' we check that meet(type(x), A) <: S. If the method is overloaded, we
950-
select only overloads items that satisfy this requirement. If there are no matching
924+
then for 'x.f' we check that type(x) <: S. If the method is overloaded, we select
925+
only overloads items that satisfy this requirement. If there are no matching
951926
overloads, an error is generated.
952-
953-
Note: dispatched_arg_type uses a meet to select a relevant item in case if the
954-
original type of 'x' is a union. This is done because several special methods
955-
treat union types in ad-hoc manner, so we can't use MemberContext.self_type yet.
956927
"""
957928
items = functype.items
958929
if not items:
@@ -1436,22 +1407,17 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
14361407

14371408

14381409
def analyze_decorator_or_funcbase_access(
1439-
defn: Decorator | FuncBase,
1440-
itype: Instance,
1441-
info: TypeInfo,
1442-
self_type: Type | None,
1443-
name: str,
1444-
mx: MemberContext,
1410+
defn: Decorator | FuncBase, itype: Instance, name: str, mx: MemberContext
14451411
) -> Type:
14461412
"""Analyzes the type behind method access.
14471413
14481414
The function itself can possibly be decorated.
14491415
See: https://github.com/python/mypy/issues/10409
14501416
"""
14511417
if isinstance(defn, Decorator):
1452-
return analyze_var(name, defn.var, itype, info, mx)
1418+
return analyze_var(name, defn.var, itype, mx)
14531419
return bind_self(
1454-
function_type(defn, mx.chk.named_type("builtins.function")), original_type=self_type
1420+
function_type(defn, mx.chk.named_type("builtins.function")), original_type=mx.self_type
14551421
)
14561422

14571423

0 commit comments

Comments
 (0)