Skip to content

Commit 49ee7a8

Browse files
committed
Move the deprecation attribute from the nodes CallableType and Overloaded to the symbol nodes FuncDef and OverloadedFuncDef
1 parent 6163787 commit 49ee7a8

File tree

8 files changed

+98
-82
lines changed

8 files changed

+98
-82
lines changed

mypy/checker.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2837,10 +2837,7 @@ def check_metaclass_compatibility(self, typ: TypeInfo) -> None:
28372837
def visit_import_from(self, node: ImportFrom) -> None:
28382838
for name, _ in node.names:
28392839
if (sym := self.globals.get(name)) is not None:
2840-
if isinstance(sym.node, TypeInfo):
2841-
self.warn_deprecated(sym.node, node)
2842-
elif isinstance(typ := get_proper_type(sym.type), (CallableType, Overloaded)):
2843-
self.warn_deprecated(typ, node)
2840+
self.warn_deprecated(sym.node, node)
28442841
self.check_import(node)
28452842

28462843
def visit_import_all(self, node: ImportAll) -> None:
@@ -4683,8 +4680,17 @@ def visit_operator_assignment_stmt(self, s: OperatorAssignmentStmt) -> None:
46834680
if inplace:
46844681
# There is __ifoo__, treat as x = x.__ifoo__(y)
46854682
rvalue_type, method_type = self.expr_checker.check_op(method, lvalue_type, s.rvalue, s)
4686-
if isinstance(method_type := get_proper_type(method_type), (CallableType, Overloaded)):
4687-
self.warn_deprecated(method_type, s)
4683+
if (
4684+
isinstance(inst := get_proper_type(lvalue_type), Instance)
4685+
and isinstance(defn := inst.type.get_method(method), OverloadedFuncDef)
4686+
):
4687+
for item in defn.items:
4688+
if (
4689+
isinstance(item, Decorator)
4690+
and isinstance(typ := item.func.type, CallableType)
4691+
and (bind_self(typ) == method_type)
4692+
):
4693+
self.warn_deprecated(item.func, s)
46884694
if not is_subtype(rvalue_type, lvalue_type):
46894695
self.msg.incompatible_operator_assignment(s.op, s)
46904696
else:
@@ -7537,20 +7543,28 @@ def has_valid_attribute(self, typ: Type, name: str) -> bool:
75377543
def get_expression_type(self, node: Expression, type_context: Type | None = None) -> Type:
75387544
return self.expr_checker.accept(node, type_context=type_context)
75397545

7540-
def check_deprecated(
7541-
self, typ: CallableType | Overloaded | TypeInfo, context: Context
7542-
) -> None:
7546+
def check_deprecated(self, typ: SymbolNode | None, context: Context) -> None:
75437547
"""Warn if deprecated and not directly imported with a `from` statement."""
7544-
if typ.deprecated is not None:
7548+
if isinstance(typ, Decorator):
7549+
typ = typ.func
7550+
if (
7551+
isinstance(typ, (FuncDef, OverloadedFuncDef, TypeInfo))
7552+
and (typ.deprecated is not None)
7553+
):
75457554
for imp in self.tree.imports:
75467555
if isinstance(imp, ImportFrom) and any(typ.name == n[0] for n in imp.names):
75477556
break
75487557
else:
75497558
self.warn_deprecated(typ, context)
75507559

7551-
def warn_deprecated(self, typ: CallableType | Overloaded | TypeInfo, context: Context) -> None:
7560+
def warn_deprecated(self, typ: SymbolNode | None, context: Context) -> None:
75527561
"""Warn if deprecated."""
7553-
if (deprecated := typ.deprecated) is not None:
7562+
if isinstance(typ, Decorator):
7563+
typ = typ.func
7564+
if (
7565+
isinstance(typ, (FuncDef, OverloadedFuncDef, TypeInfo))
7566+
and ((deprecated := typ.deprecated) is not None)
7567+
):
75547568
warn = self.msg.fail if self.options.report_deprecated_as_error else self.msg.note
75557569
warn(deprecated, context, code=codes.DEPRECATED)
75567570

mypy/checkexpr.py

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
validate_instance,
127127
)
128128
from mypy.typeops import (
129+
bind_self,
129130
callable_type,
130131
custom_special_method,
131132
erase_to_union_or_bound,
@@ -355,10 +356,7 @@ def visit_name_expr(self, e: NameExpr) -> Type:
355356
self.chk.module_refs.update(extract_refexpr_names(e))
356357
result = self.analyze_ref_expr(e)
357358
narrowed = self.narrow_type_from_binder(e, result)
358-
if isinstance(e.node, TypeInfo):
359-
self.chk.check_deprecated(e.node, e)
360-
elif isinstance(typ := get_proper_type(narrowed), (CallableType, Overloaded)):
361-
self.chk.check_deprecated(typ, e)
359+
self.chk.check_deprecated(e.node, e)
362360
return narrowed
363361

364362
def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
@@ -1480,8 +1478,10 @@ def check_call_expr_with_callee_type(
14801478
object_type=object_type,
14811479
)
14821480
proper_callee = get_proper_type(callee_type)
1483-
if isinstance(proper_callee, (CallableType, Overloaded)):
1484-
self.chk.check_deprecated(proper_callee, e)
1481+
if isinstance(e.callee, NameExpr) and isinstance(e.callee.node, OverloadedFuncDef):
1482+
for item in e.callee.node.items:
1483+
if isinstance(item, Decorator) and (item.func.type == callee_type):
1484+
self.chk.check_deprecated(item.func, e)
14851485
if isinstance(e.callee, RefExpr) and isinstance(proper_callee, CallableType):
14861486
# Cache it for find_isinstance_check()
14871487
if proper_callee.type_guard is not None:
@@ -3260,10 +3260,7 @@ def visit_member_expr(self, e: MemberExpr, is_lvalue: bool = False) -> Type:
32603260
self.chk.module_refs.update(extract_refexpr_names(e))
32613261
result = self.analyze_ordinary_member_access(e, is_lvalue)
32623262
narrowed = self.narrow_type_from_binder(e, result)
3263-
if isinstance(e.node, TypeInfo):
3264-
self.chk.warn_deprecated(e.node, e)
3265-
elif isinstance(typ := get_proper_type(narrowed), (CallableType, Overloaded)):
3266-
self.chk.warn_deprecated(typ, e)
3263+
self.chk.warn_deprecated(e.node, e)
32673264
return narrowed
32683265

32693266
def analyze_ordinary_member_access(self, e: MemberExpr, is_lvalue: bool) -> Type:
@@ -3489,8 +3486,6 @@ def visit_op_expr(self, e: OpExpr) -> Type:
34893486
else:
34903487
assert_never(use_reverse)
34913488
e.method_type = method_type
3492-
if isinstance(mt := get_proper_type(method_type), (CallableType, Overloaded)):
3493-
self.chk.check_deprecated(mt, e)
34943489
return result
34953490
else:
34963491
raise RuntimeError(f"Unknown operator {e.op}")
@@ -3807,8 +3802,6 @@ def check_method_call_by_name(
38073802
chk=self.chk,
38083803
in_literal_context=self.is_literal_context(),
38093804
)
3810-
if isinstance(mt := get_proper_type(method_type), (CallableType, Overloaded)):
3811-
self.chk.warn_deprecated(mt, context)
38123805
return self.check_method_call(method, base_type, method_type, args, arg_kinds, context)
38133806

38143807
def check_union_method_call_by_name(
@@ -3957,7 +3950,7 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
39573950
# This is the case even if the __add__ method is completely missing and the __radd__
39583951
# method is defined.
39593952

3960-
variants_raw = [(left_op, left_type, right_expr)]
3953+
variants_raw = [(op_name, left_op, left_type, right_expr)]
39613954
elif (
39623955
is_subtype(right_type, left_type)
39633956
and isinstance(left_type, Instance)
@@ -3978,19 +3971,25 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
39783971
# As a special case, the alt_promote check makes sure that we don't use the
39793972
# __radd__ method of int if the LHS is a native int type.
39803973

3981-
variants_raw = [(right_op, right_type, left_expr), (left_op, left_type, right_expr)]
3974+
variants_raw = [
3975+
(rev_op_name, right_op, right_type, left_expr),
3976+
(op_name, left_op, left_type, right_expr),
3977+
]
39823978
else:
39833979
# In all other cases, we do the usual thing and call __add__ first and
39843980
# __radd__ second when doing "A() + B()".
39853981

3986-
variants_raw = [(left_op, left_type, right_expr), (right_op, right_type, left_expr)]
3982+
variants_raw = [
3983+
(op_name, left_op, left_type, right_expr),
3984+
(rev_op_name, right_op, right_type, left_expr),
3985+
]
39873986

39883987
# STEP 3:
39893988
# We now filter out all non-existent operators. The 'variants' list contains
39903989
# all operator methods that are actually present, in the order that Python
39913990
# attempts to invoke them.
39923991

3993-
variants = [(op, obj, arg) for (op, obj, arg) in variants_raw if op is not None]
3992+
variants = [(na, op, obj, arg) for (na, op, obj, arg) in variants_raw if op is not None]
39943993

39953994
# STEP 4:
39963995
# We now try invoking each one. If an operation succeeds, end early and return
@@ -3999,13 +3998,24 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
39993998

40003999
errors = []
40014000
results = []
4002-
for method, obj, arg in variants:
4001+
for name, method, obj, arg in variants:
40034002
with self.msg.filter_errors(save_filtered_errors=True) as local_errors:
40044003
result = self.check_method_call(op_name, obj, method, [arg], [ARG_POS], context)
40054004
if local_errors.has_new_errors():
40064005
errors.append(local_errors.filtered_errors())
40074006
results.append(result)
40084007
else:
4008+
if (
4009+
isinstance(obj, Instance)
4010+
and isinstance(defn := obj.type.get_method(name), OverloadedFuncDef)
4011+
):
4012+
for item in defn.items:
4013+
if (
4014+
isinstance(item, Decorator)
4015+
and isinstance(typ := item.func.type, CallableType)
4016+
and bind_self(typ) == result[1]
4017+
):
4018+
self.chk.check_deprecated(item.func, context)
40094019
return result
40104020

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

mypy/checkmember.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -318,16 +318,9 @@ def analyze_instance_member_access(
318318
assert isinstance(method, OverloadedFuncDef)
319319
getter = method.items[0]
320320
assert isinstance(getter, Decorator)
321-
if (
322-
mx.is_lvalue
323-
and (len(items := method.items) > 1)
324-
and isinstance(setter := items[1], Decorator)
325-
):
326-
if isinstance(co := setter.func.type, (CallableType, Overloaded)):
327-
mx.chk.warn_deprecated(co, mx.context)
321+
if mx.is_lvalue and (len(items := method.items) > 1):
322+
mx.chk.warn_deprecated(items[1], mx.context)
328323
return analyze_var(name, getter.var, typ, info, mx)
329-
elif isinstance(co := method.type, (CallableType, Overloaded)):
330-
mx.chk.warn_deprecated(co, mx.context)
331324

332325
if mx.is_lvalue:
333326
mx.msg.cant_assign_to_method(mx.context)
@@ -503,6 +496,8 @@ def analyze_member_var_access(
503496
# It was not a method. Try looking up a variable.
504497
v = lookup_member_var_or_accessor(info, name, mx.is_lvalue)
505498

499+
mx.chk.warn_deprecated(v, mx.context)
500+
506501
vv = v
507502
if isinstance(vv, Decorator):
508503
# The associated Var node of a decorator contains the type.
@@ -783,9 +778,6 @@ def analyze_var(
783778
result = t
784779
typ = get_proper_type(typ)
785780

786-
if var.is_property and isinstance(typ, CallableType):
787-
mx.chk.warn_deprecated(typ, mx.context)
788-
789781
call_type: ProperType | None = None
790782
if var.is_initialized_in_class and (not is_instance_var(var) or mx.is_operator):
791783
if isinstance(typ, FunctionLike) and not typ.is_type_obj():
@@ -1017,6 +1009,8 @@ def analyze_class_attribute_access(
10171009
# on the class object itself rather than the instance.
10181010
return None
10191011

1012+
mx.chk.warn_deprecated(node.node, mx.context)
1013+
10201014
is_decorated = isinstance(node.node, Decorator)
10211015
is_method = is_decorated or isinstance(node.node, FuncBase)
10221016
if mx.is_lvalue:

mypy/errors.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ def on_error(self, file: str, info: ErrorInfo) -> bool:
194194
Return True to filter out the error, preventing it from being seen by other
195195
ErrorWatcher further down the stack and from being recorded by Errors
196196
"""
197+
if info.code == codes.DEPRECATED:
198+
return False
199+
197200
self._has_new_errors = True
198201
if isinstance(self._filter, bool):
199202
should_filter = self._filter

mypy/nodes.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,17 +561,19 @@ class OverloadedFuncDef(FuncBase, SymbolNode, Statement):
561561
Overloaded variants must be consecutive in the source file.
562562
"""
563563

564-
__slots__ = ("items", "unanalyzed_items", "impl")
564+
__slots__ = ("items", "unanalyzed_items", "impl", "deprecated")
565565

566566
items: list[OverloadPart]
567567
unanalyzed_items: list[OverloadPart]
568568
impl: OverloadPart | None
569+
deprecated: str | None
569570

570571
def __init__(self, items: list[OverloadPart]) -> None:
571572
super().__init__()
572573
self.items = items
573574
self.unanalyzed_items = items.copy()
574575
self.impl = None
576+
self.deprecated = None
575577
if items:
576578
# TODO: figure out how to reliably set end position (we don't know the impl here).
577579
self.set_line(items[0].line, items[0].column)
@@ -596,6 +598,7 @@ def serialize(self) -> JsonDict:
596598
"fullname": self._fullname,
597599
"impl": None if self.impl is None else self.impl.serialize(),
598600
"flags": get_flags(self, FUNCBASE_FLAGS),
601+
"deprecated": self.deprecated,
599602
}
600603

601604
@classmethod
@@ -615,6 +618,7 @@ def deserialize(cls, data: JsonDict) -> OverloadedFuncDef:
615618
res.type = typ
616619
res._fullname = data["fullname"]
617620
set_flags(res, data["flags"])
621+
res.deprecated = data["deprecated"]
618622
# NOTE: res.info will be set in the fixup phase.
619623
return res
620624

@@ -781,6 +785,7 @@ class FuncDef(FuncItem, SymbolNode, Statement):
781785
# Present only when a function is decorated with @typing.datasclass_transform or similar
782786
"dataclass_transform_spec",
783787
"docstring",
788+
"deprecated",
784789
)
785790

786791
__match_args__ = ("name", "arguments", "type", "body")
@@ -810,6 +815,7 @@ def __init__(
810815
self.is_mypy_only = False
811816
self.dataclass_transform_spec: DataclassTransformSpec | None = None
812817
self.docstring: str | None = None
818+
self.deprecated: str | None = None
813819

814820
@property
815821
def name(self) -> str:
@@ -840,6 +846,7 @@ def serialize(self) -> JsonDict:
840846
if self.dataclass_transform_spec is None
841847
else self.dataclass_transform_spec.serialize()
842848
),
849+
"deprecated": self.deprecated,
843850
}
844851

845852
@classmethod
@@ -867,6 +874,7 @@ def deserialize(cls, data: JsonDict) -> FuncDef:
867874
if data["dataclass_transform_spec"] is not None
868875
else None
869876
)
877+
ret.deprecated = data["deprecated"]
870878
# Leave these uninitialized so that future uses will trigger an error
871879
del ret.arguments
872880
del ret.max_pos

mypy/semanal.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,14 +1267,12 @@ def process_deprecated_overload(self, defn: OverloadedFuncDef) -> None:
12671267

12681268
if (
12691269
isinstance(impl := defn.impl, Decorator)
1270-
and isinstance(type_ := impl.func.type, CallableType)
1271-
and ((deprecated := type_.deprecated) is not None)
1270+
and ((deprecated := impl.func.deprecated) is not None)
12721271
):
1273-
if isinstance(defn.type, Overloaded):
1274-
defn.type.deprecated = deprecated
1272+
defn.deprecated = deprecated
12751273
for item in defn.items:
1276-
if isinstance(item, Decorator) and isinstance(item.func.type, Overloaded):
1277-
item.func.type.deprecated = deprecated
1274+
if isinstance(item, Decorator):
1275+
item.func.deprecated = deprecated
12781276

12791277
for item in defn.items:
12801278
deprecation = False
@@ -1285,10 +1283,13 @@ def process_deprecated_overload(self, defn: OverloadedFuncDef) -> None:
12851283
elif (deprecated := self.get_deprecated(d)) is not None:
12861284
deprecation = True
12871285
if isinstance(type_ := item.func.type, CallableType):
1288-
type_.deprecated = (
1289-
f"overload {type_} of function {defn.fullname} is deprecated: "
1290-
f"{deprecated}"
1291-
)
1286+
typestr = f" {type_} "
1287+
else:
1288+
typestr = " "
1289+
item.func.deprecated = (
1290+
f"overload{typestr}of function {defn.fullname} is deprecated: "
1291+
f"{deprecated}"
1292+
)
12921293

12931294
@staticmethod
12941295
def get_deprecated(expression: Expression) -> str | None:
@@ -1503,10 +1504,9 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -
15031504
if isinstance(item, Decorator):
15041505
for d in item.decorators:
15051506
if (deprecated := self.get_deprecated(d)) is not None:
1506-
if isinstance(type_ := item.func.type, CallableType):
1507-
type_.deprecated = (
1508-
f"function {item.fullname} is deprecated: {deprecated}"
1509-
)
1507+
item.func.deprecated = (
1508+
f"function {item.fullname} is deprecated: {deprecated}"
1509+
)
15101510

15111511
def add_function_to_symbol_table(self, func: FuncDef | OverloadedFuncDef) -> None:
15121512
if self.is_class_scope():
@@ -1712,8 +1712,7 @@ def visit_decorator(self, dec: Decorator) -> None:
17121712
):
17131713
dec.func.dataclass_transform_spec = self.parse_dataclass_transform_spec(d)
17141714
elif (deprecated := self.get_deprecated(d)) is not None:
1715-
if isinstance(type_ := dec.func.type, CallableType):
1716-
type_.deprecated = f"function {dec.fullname} is deprecated: {deprecated}"
1715+
dec.func.deprecated = f"function {dec.fullname} is deprecated: {deprecated}"
17171716
elif not dec.var.is_property:
17181717
# We have seen a "non-trivial" decorator before seeing @property, if
17191718
# we will see a @property later, give an error, as we don't support this.

0 commit comments

Comments
 (0)