Skip to content

Commit f8f8d73

Browse files
committed
search_deprecated -> InstanceDeprecatedVisitor (and additionally CallableType and TypeAliasType)
1 parent 55b4126 commit f8f8d73

File tree

2 files changed

+93
-18
lines changed

2 files changed

+93
-18
lines changed

mypy/checker.py

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@
196196
LiteralType,
197197
NoneType,
198198
Overloaded,
199+
Parameters,
200+
ParamSpecType,
199201
PartialType,
200202
ProperType,
201203
TupleType,
@@ -224,6 +226,7 @@
224226
from mypy.types_utils import is_overlapping_none, remove_optional, store_argument_type, strip_type
225227
from mypy.typetraverser import TypeTraverserVisitor
226228
from mypy.typevars import fill_typevars, fill_typevars_with_any, has_no_typevars
229+
from mypy.type_visitor import TypeVisitor
227230
from mypy.util import is_dunder, is_sunder
228231
from mypy.visitor import NodeVisitor
229232

@@ -287,6 +290,83 @@ class PartialTypeScope(NamedTuple):
287290
is_local: bool
288291

289292

293+
class InstanceDeprecatedVisitor(TypeVisitor[None]):
294+
"""Visitor that recursively checks for deprecations in nested instances."""
295+
296+
def __init__(self, typechecker: TypeChecker, context: Context) -> None:
297+
self.typechecker = typechecker
298+
self.context = context
299+
300+
def visit_any(self, t: AnyType) -> None:
301+
pass
302+
303+
def visit_callable_type(self, t: CallableType) -> None:
304+
for arg_type in t.arg_types:
305+
arg_type.accept(self)
306+
t.ret_type.accept(self)
307+
308+
def visit_deleted_type(self, t: DeletedType) -> None:
309+
pass
310+
311+
def visit_erased_type(self, t: ErasedType) -> None:
312+
pass
313+
314+
def visit_instance(self, t: Instance) -> None:
315+
self.typechecker.check_deprecated(t.type, self.context)
316+
for arg in t.args:
317+
arg.accept(self)
318+
319+
def visit_literal_type(self, t: LiteralType) -> None:
320+
pass
321+
322+
def visit_none_type(self, t: NoneType) -> None:
323+
pass
324+
325+
def visit_overloaded(self, t: Overloaded) -> None:
326+
pass
327+
328+
def visit_param_spec(self, t: ParamSpecType) -> None:
329+
pass
330+
331+
def visit_parameters(self, t: Parameters) -> None:
332+
pass
333+
334+
def visit_partial_type(self, t: PartialType) -> None:
335+
pass
336+
337+
def visit_tuple_type(self, t: TupleType) -> None:
338+
for item in t.items:
339+
item.accept(self)
340+
341+
def visit_type_alias_type(self, t: TypeAliasType) -> None:
342+
t.alias.target.accept(self)
343+
344+
def visit_type_type(self, t: TypeType) -> None:
345+
pass
346+
347+
def visit_type_var(self, t: TypeVarType) -> None:
348+
pass
349+
350+
def visit_type_var_tuple(self, t: TypeVarTupleType) -> None:
351+
pass
352+
353+
def visit_typeddict_type(self, t: TypedDictType) -> None:
354+
pass
355+
356+
def visit_unbound_type(self, t: UnboundType) -> None:
357+
pass
358+
359+
def visit_uninhabited_type(self, t: UninhabitedType) -> None:
360+
pass
361+
362+
def visit_union_type(self, t: UnionType) -> None:
363+
for item in t.items:
364+
item.accept(self)
365+
366+
def visit_unpack_type(self, t: UnpackType) -> None:
367+
pass
368+
369+
290370
class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
291371
"""Mypy type checker.
292372
@@ -2932,8 +3012,12 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
29323012

29333013
if s.unanalyzed_type is not None:
29343014
for lvalue in s.lvalues:
2935-
if isinstance(lvalue, NameExpr) and isinstance(var := lvalue.node, Var):
2936-
self.search_deprecated(var.type, s, set())
3015+
if (
3016+
isinstance(lvalue, NameExpr)
3017+
and isinstance(var := lvalue.node, Var)
3018+
and (var.type is not None)
3019+
):
3020+
var.type.accept(InstanceDeprecatedVisitor(typechecker=self, context=s))
29373021

29383022
# Avoid type checking type aliases in stubs to avoid false
29393023
# positives about modern type syntax available in stubs such
@@ -7577,20 +7661,6 @@ def warn_deprecated(self, node: SymbolNode | None, context: Context) -> None:
75777661
warn = self.msg.fail if self.options.report_deprecated_as_error else self.msg.note
75787662
warn(deprecated, context, code=codes.DEPRECATED)
75797663

7580-
def search_deprecated(
7581-
self, typ: Type | None, s: AssignmentStmt, visited: set[Type | None]
7582-
) -> None:
7583-
7584-
if typ not in visited:
7585-
visited.add(typ)
7586-
if isinstance(typ := get_proper_type(typ), Instance):
7587-
self.check_deprecated(typ.type, s)
7588-
for arg in typ.args:
7589-
self.search_deprecated(arg, s, visited)
7590-
elif isinstance(typ, (UnionType, TupleType)):
7591-
for item in typ.items:
7592-
self.search_deprecated(item, s, visited)
7593-
75947664

75957665
class CollectArgTypeVarTypes(TypeTraverserVisitor):
75967666
"""Collects the non-nested argument types in a set."""

test-data/unit/check-deprecated.test

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ def h() -> None: ...
102102

103103
[case testDeprecatedClass]
104104

105-
from typing import List, Optional, Tuple, Union
106-
from typing_extensions import deprecated
105+
from typing import Callable, List, Optional, Tuple, Union
106+
from typing_extensions import deprecated, TypeAlias
107107

108108
@deprecated("use C2 instead")
109109
class C: ...
@@ -138,6 +138,11 @@ x5: Tuple[Tuple[D, C], E] # N: class __main__.C is deprecated: use C2 instead
138138
x6: List[C] # N: class __main__.C is deprecated: use C2 instead
139139
x7: List[List[C]] # N: class __main__.C is deprecated: use C2 instead
140140
x8: List[Optional[Tuple[Union[List[C], int]]]] # N: class __main__.C is deprecated: use C2 instead
141+
x9: Callable[[int], C] # N: class __main__.C is deprecated: use C2 instead
142+
x10: Callable[[int, C, int], int] # N: class __main__.C is deprecated: use C2 instead
143+
144+
A: TypeAlias = Optional[C] # ToDo
145+
x11: A # N: class __main__.C is deprecated: use C2 instead
141146

142147
[builtins fixtures/tuple.pyi]
143148

0 commit comments

Comments
 (0)