Skip to content

Commit 80db966

Browse files
committed
Faster algorithm for deprecations and bind_self
1 parent 0f78f9c commit 80db966

File tree

7 files changed

+37
-82
lines changed

7 files changed

+37
-82
lines changed

mypy/checker.py

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
726726
assert isinstance(item, Decorator)
727727
item_type = self.extract_callable_type(item.var.type, item)
728728
if item_type is not None:
729+
item_type.definition = item
729730
item_types.append(item_type)
730731
if item_types:
731732
defn.type = Overloaded(item_types)
@@ -4927,17 +4928,7 @@ def visit_operator_assignment_stmt(self, s: OperatorAssignmentStmt) -> None:
49274928
inplace, method = infer_operator_assignment_method(lvalue_type, s.op)
49284929
if inplace:
49294930
# There is __ifoo__, treat as x = x.__ifoo__(y)
4930-
rvalue_type, method_type = self.expr_checker.check_op(method, lvalue_type, s.rvalue, s)
4931-
if isinstance(inst := get_proper_type(lvalue_type), Instance) and isinstance(
4932-
defn := inst.type.get_method(method), OverloadedFuncDef
4933-
):
4934-
for item in defn.items:
4935-
if (
4936-
isinstance(item, Decorator)
4937-
and isinstance(typ := item.func.type, CallableType)
4938-
and (bind_self(typ) == method_type)
4939-
):
4940-
self.warn_deprecated(item.func, s)
4931+
rvalue_type, _ = self.expr_checker.check_op(method, lvalue_type, s.rvalue, s)
49414932
if not is_subtype(rvalue_type, lvalue_type):
49424933
self.msg.incompatible_operator_assignment(s.op, s)
49434934
else:
@@ -7962,7 +7953,7 @@ def warn_deprecated(self, node: Node | None, context: Context) -> None:
79627953
node = node.func
79637954
if (
79647955
isinstance(node, (FuncDef, OverloadedFuncDef, TypeInfo))
7965-
and ((deprecated := node.deprecated) is not None)
7956+
and (deprecated := node.deprecated) is not None
79667957
and not self.is_typeshed_stub
79677958
and not any(
79687959
node.fullname == p or node.fullname.startswith(f"{p}.")
@@ -7972,21 +7963,6 @@ def warn_deprecated(self, node: Node | None, context: Context) -> None:
79727963
warn = self.msg.note if self.options.report_deprecated_as_note else self.msg.fail
79737964
warn(deprecated, context, code=codes.DEPRECATED)
79747965

7975-
def warn_deprecated_overload_item(
7976-
self, node: Node | None, context: Context, *, target: Type, selftype: Type | None = None
7977-
) -> None:
7978-
"""Warn if the overload item corresponding to the given callable is deprecated."""
7979-
target = get_proper_type(target)
7980-
if isinstance(node, OverloadedFuncDef) and isinstance(target, CallableType):
7981-
for item in node.items:
7982-
if isinstance(item, Decorator) and isinstance(
7983-
candidate := item.func.type, CallableType
7984-
):
7985-
if selftype is not None and not node.is_static:
7986-
candidate = bind_self(candidate, selftype)
7987-
if candidate == target:
7988-
self.warn_deprecated(item.func, context)
7989-
79907966
# leafs
79917967

79927968
def visit_pass_stmt(self, o: PassStmt, /) -> None:

mypy/checker_shared.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,6 @@ def check_deprecated(self, node: Node | None, context: Context) -> None:
253253
def warn_deprecated(self, node: Node | None, context: Context) -> None:
254254
raise NotImplementedError
255255

256-
@abstractmethod
257-
def warn_deprecated_overload_item(
258-
self, node: Node | None, context: Context, *, target: Type, selftype: Type | None = None
259-
) -> None:
260-
raise NotImplementedError
261-
262256
@abstractmethod
263257
def type_is_iterable(self, type: Type) -> bool:
264258
raise NotImplementedError

mypy/checkexpr.py

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@
129129
validate_instance,
130130
)
131131
from mypy.typeops import (
132-
bind_self,
133132
callable_type,
134133
custom_special_method,
135134
erase_to_union_or_bound,
@@ -1517,15 +1516,6 @@ def check_call_expr_with_callee_type(
15171516
object_type=object_type,
15181517
)
15191518
proper_callee = get_proper_type(callee_type)
1520-
if isinstance(e.callee, (NameExpr, MemberExpr)):
1521-
node = e.callee.node
1522-
if node is None and member is not None and isinstance(object_type, Instance):
1523-
if (symbol := object_type.type.get(member)) is not None:
1524-
node = symbol.node
1525-
self.chk.check_deprecated(node, e)
1526-
self.chk.warn_deprecated_overload_item(
1527-
node, e, target=callee_type, selftype=object_type
1528-
)
15291519
if isinstance(e.callee, RefExpr) and isinstance(proper_callee, CallableType):
15301520
# Cache it for find_isinstance_check()
15311521
if proper_callee.type_guard is not None:
@@ -2943,6 +2933,8 @@ def infer_overload_return_type(
29432933
# check for ambiguity due to 'Any' below.
29442934
if not args_contain_any:
29452935
self.chk.store_types(m)
2936+
if isinstance(infer_type, ProperType) and isinstance(infer_type, CallableType):
2937+
self.chk.check_deprecated(infer_type.definition, context)
29462938
return ret_type, infer_type
29472939
p_infer_type = get_proper_type(infer_type)
29482940
if isinstance(p_infer_type, CallableType):
@@ -2979,6 +2971,11 @@ def infer_overload_return_type(
29792971
else:
29802972
# Success! No ambiguity; return the first match.
29812973
self.chk.store_types(type_maps[0])
2974+
inferred_callable = inferred_types[0]
2975+
if isinstance(inferred_callable, ProperType) and isinstance(
2976+
inferred_callable, CallableType
2977+
):
2978+
self.chk.check_deprecated(inferred_callable.definition, context)
29822979
return return_types[0], inferred_types[0]
29832980

29842981
def overload_erased_call_targets(
@@ -4103,16 +4100,6 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
41034100
errors.append(local_errors.filtered_errors())
41044101
results.append(result)
41054102
else:
4106-
if isinstance(obj, Instance) and isinstance(
4107-
defn := obj.type.get_method(name), OverloadedFuncDef
4108-
):
4109-
for item in defn.items:
4110-
if (
4111-
isinstance(item, Decorator)
4112-
and isinstance(typ := item.func.type, CallableType)
4113-
and bind_self(typ) == result[1]
4114-
):
4115-
self.chk.check_deprecated(item.func, context)
41164103
return result
41174104

41184105
# We finish invoking above operators and no early return happens. Therefore,

mypy/checkmember.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
freeze_all_type_vars,
4646
function_type,
4747
get_all_type_vars,
48+
is_valid_self_type_best_effort,
4849
make_simplified_union,
4950
supported_self_type,
5051
tuple_fallback,
@@ -745,10 +746,8 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type:
745746
callable_name=callable_name,
746747
)
747748

748-
mx.chk.check_deprecated(dunder_get, mx.context)
749-
mx.chk.warn_deprecated_overload_item(
750-
dunder_get, mx.context, target=inferred_dunder_get_type, selftype=descriptor_type
751-
)
749+
# Search for possible deprecations:
750+
mx.chk.warn_deprecated(dunder_get, mx.context)
752751

753752
inferred_dunder_get_type = get_proper_type(inferred_dunder_get_type)
754753
if isinstance(inferred_dunder_get_type, AnyType):
@@ -825,10 +824,7 @@ def analyze_descriptor_assign(descriptor_type: Instance, mx: MemberContext) -> T
825824
)
826825

827826
# Search for possible deprecations:
828-
mx.chk.check_deprecated(dunder_set, mx.context)
829-
mx.chk.warn_deprecated_overload_item(
830-
dunder_set, mx.context, target=inferred_dunder_set_type, selftype=descriptor_type
831-
)
827+
mx.chk.warn_deprecated(dunder_set, mx.context)
832828

833829
# In the following cases, a message already will have been recorded in check_call.
834830
if (not isinstance(inferred_dunder_set_type, CallableType)) or (
@@ -1054,6 +1050,23 @@ def f(self: S) -> T: ...
10541050
if is_classmethod:
10551051
dispatched_arg_type = TypeType.make_normalized(dispatched_arg_type)
10561052

1053+
if isinstance(functype, Overloaded):
1054+
p_dispatched_arg_type = get_proper_type(dispatched_arg_type)
1055+
filtered_items = []
1056+
for c in items:
1057+
if isinstance(p_dispatched_arg_type, Instance):
1058+
# Filter based on whether declared self type can match actual object type.
1059+
# For example, if self has type C[int] and method is accessed on a C[str] value,
1060+
# omit this item. This is best effort first pass filter obvious mismatches for
1061+
# performance reasons.
1062+
keep = is_valid_self_type_best_effort(c, p_dispatched_arg_type)
1063+
else:
1064+
keep = True
1065+
if keep:
1066+
filtered_items.append(c)
1067+
if len(filtered_items) != 0:
1068+
items = filtered_items
1069+
10571070
for item in items:
10581071
if not item.arg_types or item.arg_kinds[0] not in (ARG_POS, ARG_STAR):
10591072
# No positional first (self) argument (*args is okay).

mypy/fixup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ def visit_func_def(self, func: FuncDef) -> None:
165165
func.info = self.current_info
166166
if func.type is not None:
167167
func.type.accept(self.type_fixer)
168+
if isinstance(func.type, CallableType):
169+
func.type.definition = func
168170

169171
def visit_overloaded_func_def(self, o: OverloadedFuncDef) -> None:
170172
if self.current_info is not None:

mypy/typeops.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -421,27 +421,9 @@ class B(A): pass
421421
422422
"""
423423
if isinstance(method, Overloaded):
424-
items = []
425-
original_type = get_proper_type(original_type)
426-
for c in method.items:
427-
if isinstance(original_type, Instance):
428-
# Filter based on whether declared self type can match actual object type.
429-
# For example, if self has type C[int] and method is accessed on a C[str] value,
430-
# omit this item. This is best effort since bind_self can be called in many
431-
# contexts, and doing complete validation might trigger infinite recursion.
432-
#
433-
# Note that overload item filtering normally happens elsewhere. This is needed
434-
# at least during constraint inference.
435-
keep = is_valid_self_type_best_effort(c, original_type)
436-
else:
437-
keep = True
438-
if keep:
439-
items.append(bind_self(c, original_type, is_classmethod, ignore_instances))
440-
if len(items) == 0:
441-
# If no item matches, returning all items helps avoid some spurious errors
442-
items = [
443-
bind_self(c, original_type, is_classmethod, ignore_instances) for c in method.items
444-
]
424+
items = [
425+
bind_self(c, original_type, is_classmethod, ignore_instances) for c in method.items
426+
]
445427
return cast(F, Overloaded(items))
446428
assert isinstance(method, CallableType)
447429
func: CallableType = method

test-data/unit/check-serialize.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ def f(__x: int) -> None: pass
158158
[out2]
159159
tmp/a.py:3: error: Argument 1 to "f" has incompatible type "str"; expected "int"
160160
tmp/a.py:4: error: Unexpected keyword argument "__x" for "f"
161+
tmp/b.py: note: "f" defined here
161162

162163
[case testSerializeArgumentKindsErrors]
163164
import a

0 commit comments

Comments
 (0)