Skip to content

PEP 702 (@deprecated): consider all possible type positions #17926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 4 additions & 23 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,18 +287,6 @@ class PartialTypeScope(NamedTuple):
is_local: bool


class InstanceDeprecatedVisitor(TypeTraverserVisitor):
"""Visitor that recursively checks for deprecations in nested instances."""

def __init__(self, typechecker: TypeChecker, context: Context) -> None:
self.typechecker = typechecker
self.context = context

def visit_instance(self, t: Instance) -> None:
super().visit_instance(t)
self.typechecker.check_deprecated(t.type, self.context)


class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
"""Mypy type checker.

Expand Down Expand Up @@ -2942,15 +2930,6 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
Handle all kinds of assignment statements (simple, indexed, multiple).
"""

if s.unanalyzed_type is not None:
for lvalue in s.lvalues:
if (
isinstance(lvalue, NameExpr)
and isinstance(var := lvalue.node, Var)
and (var.type is not None)
):
var.type.accept(InstanceDeprecatedVisitor(typechecker=self, context=s))

# Avoid type checking type aliases in stubs to avoid false
# positives about modern type syntax available in stubs such
# as X | Y.
Expand Down Expand Up @@ -7602,8 +7581,10 @@ def warn_deprecated(self, node: SymbolNode | None, context: Context) -> None:
"""Warn if deprecated."""
if isinstance(node, Decorator):
node = node.func
if isinstance(node, (FuncDef, OverloadedFuncDef, TypeInfo)) and (
(deprecated := node.deprecated) is not None
if (
isinstance(node, (FuncDef, OverloadedFuncDef, TypeInfo))
and ((deprecated := node.deprecated) is not None)
and not self.is_typeshed_stub
):
warn = self.msg.fail if self.options.report_deprecated_as_error else self.msg.note
warn(deprecated, context, code=codes.DEPRECATED)
Expand Down
2 changes: 2 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3859,6 +3859,7 @@ def analyze_alias(
self.tvar_scope,
self.plugin,
self.options,
self.cur_mod_node,
self.is_typeshed_stub_file,
allow_placeholder=allow_placeholder,
in_dynamic_func=dynamic,
Expand Down Expand Up @@ -7267,6 +7268,7 @@ def type_analyzer(
tvar_scope,
self.plugin,
self.options,
self.cur_mod_node,
self.is_typeshed_stub_file,
allow_unbound_tvars=allow_unbound_tvars,
allow_tuple_literal=allow_tuple_literal,
Expand Down
1 change: 1 addition & 0 deletions mypy/server/astdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb
[snapshot_type(base) for base in node.bases],
[snapshot_type(p) for p in node._promote],
dataclass_transform_spec.serialize() if dataclass_transform_spec is not None else None,
node.deprecated,
)
prefix = node.fullname
symbol_table = snapshot_symbol_table(prefix, node.names)
Expand Down
22 changes: 22 additions & 0 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
ArgKind,
Context,
Decorator,
ImportFrom,
MypyFile,
ParamSpecExpr,
PlaceholderNode,
Expand Down Expand Up @@ -148,6 +149,7 @@ def analyze_type_alias(
tvar_scope: TypeVarLikeScope,
plugin: Plugin,
options: Options,
cur_mod_node: MypyFile,
is_typeshed_stub: bool,
allow_placeholder: bool = False,
in_dynamic_func: bool = False,
Expand All @@ -167,6 +169,7 @@ def analyze_type_alias(
tvar_scope,
plugin,
options,
cur_mod_node,
is_typeshed_stub,
defining_alias=True,
allow_placeholder=allow_placeholder,
Expand Down Expand Up @@ -213,6 +216,7 @@ def __init__(
tvar_scope: TypeVarLikeScope,
plugin: Plugin,
options: Options,
cur_mod_node: MypyFile,
is_typeshed_stub: bool,
*,
defining_alias: bool = False,
Expand Down Expand Up @@ -266,6 +270,7 @@ def __init__(
self.report_invalid_types = report_invalid_types
self.plugin = plugin
self.options = options
self.cur_mod_node = cur_mod_node
self.is_typeshed_stub = is_typeshed_stub
# Names of type aliases encountered while analysing a type will be collected here.
self.aliases_used: set[str] = set()
Expand Down Expand Up @@ -771,6 +776,21 @@ def get_omitted_any(self, typ: Type, fullname: str | None = None) -> AnyType:
disallow_any = not self.is_typeshed_stub and self.options.disallow_any_generics
return get_omitted_any(disallow_any, self.fail, self.note, typ, self.options, fullname)

def check_and_warn_deprecated(self, info: TypeInfo, ctx: Context) -> None:
"""Similar logic to `TypeChecker.check_deprecated` and `TypeChecker.warn_deprecated."""

if (
(deprecated := info.deprecated)
and not self.is_typeshed_stub
and not (self.api.type and (self.api.type.fullname == info.fullname))
):
for imp in self.cur_mod_node.imports:
if isinstance(imp, ImportFrom) and any(info.name == n[0] for n in imp.names):
break
else:
warn = self.fail if self.options.report_deprecated_as_error else self.note
warn(deprecated, ctx, code=codes.DEPRECATED)

def analyze_type_with_type_info(
self, info: TypeInfo, args: Sequence[Type], ctx: Context, empty_tuple_index: bool
) -> Type:
Expand All @@ -779,6 +799,8 @@ def analyze_type_with_type_info(
This handles simple cases like 'int', 'modname.UserClass[str]', etc.
"""

self.check_and_warn_deprecated(info, ctx)

if len(args) > 0 and info.fullname == "builtins.tuple":
fallback = Instance(info, [AnyType(TypeOfAny.special_form)], ctx.line)
return TupleType(self.anal_array(args, allow_unpack=True), fallback, ctx.line)
Expand Down
123 changes: 120 additions & 3 deletions test-data/unit/check-deprecated.test
Original file line number Diff line number Diff line change
Expand Up @@ -142,23 +142,129 @@ x9: Callable[[int], C] # N: class __main__.C is deprecated: use C2 instead
x10: Callable[[int, C, int], int] # N: class __main__.C is deprecated: use C2 instead

T = TypeVar("T")
A1: TypeAlias = Optional[C] # ToDo
A1: TypeAlias = Optional[C] # N: class __main__.C is deprecated: use C2 instead
x11: A1
A2: TypeAlias = List[Union[A2, C]] # ToDo
A2: TypeAlias = List[Union[A2, C]] # N: class __main__.C is deprecated: use C2 instead
x12: A2
A3: TypeAlias = List[Optional[T]]
x13: A3[C] # N: class __main__.C is deprecated: use C2 instead

[builtins fixtures/tuple.pyi]


[case testDeprecatedBaseClass]

from typing_extensions import deprecated

@deprecated("use C2 instead")
class C: ...

class D(C): ... # N: class __main__.C is deprecated: use C2 instead
class E(D): ...
class F(D, C): ... # N: class __main__.C is deprecated: use C2 instead

[builtins fixtures/tuple.pyi]


[case testDeprecatedClassInTypeVar]

from typing import Generic, TypeVar
from typing_extensions import deprecated

class B: ...
@deprecated("use C2 instead")
class C: ...

T = TypeVar("T", bound=C) # N: class __main__.C is deprecated: use C2 instead
def f(x: T) -> T: ...
class D(Generic[T]): ...

V = TypeVar("V", B, C) # N: class __main__.C is deprecated: use C2 instead
def g(x: V) -> V: ...
class E(Generic[V]): ...

[builtins fixtures/tuple.pyi]


[case testDeprecatedClassInCast]

from typing import cast, Generic
from typing_extensions import deprecated

class B: ...
@deprecated("use C2 instead")
class C: ...

c = C() # N: class __main__.C is deprecated: use C2 instead
b = cast(B, c)

[builtins fixtures/tuple.pyi]


[case testDeprecatedInstanceInFunctionDefinition]

from typing import Generic, List, Optional, TypeVar
from typing_extensions import deprecated

@deprecated("use C2 instead")
class C: ...

def f1(c: C) -> None: # N: class __main__.C is deprecated: use C2 instead
def g1() -> None: ...

def f2(c: List[Optional[C]]) -> None: # N: class __main__.C is deprecated: use C2 instead
def g2() -> None: ...

def f3() -> C: # N: class __main__.C is deprecated: use C2 instead
def g3() -> None: ...
return C() # N: class __main__.C is deprecated: use C2 instead

def f4() -> List[Optional[C]]: # N: class __main__.C is deprecated: use C2 instead
def g4() -> None: ...
return []

def f5() -> None:
def g5(c: C) -> None: ... # N: class __main__.C is deprecated: use C2 instead

def f6() -> None:
def g6() -> C: ... # N: class __main__.C is deprecated: use C2 instead


@deprecated("use D2 instead")
class D:

def f1(self, c: C) -> None: # N: class __main__.C is deprecated: use C2 instead
def g1() -> None: ...

def f2(self, c: List[Optional[C]]) -> None: # N: class __main__.C is deprecated: use C2 instead
def g2() -> None: ...

def f3(self) -> None:
def g3(c: C) -> None: ... # N: class __main__.C is deprecated: use C2 instead

def f4(self) -> None:
def g4() -> C: ... # N: class __main__.C is deprecated: use C2 instead

T = TypeVar("T")

@deprecated("use E2 instead")
class E(Generic[T]):

def f1(self: E[C]) -> None: ... # N: class __main__.C is deprecated: use C2 instead
def f2(self, e: E[C]) -> None: ... # N: class __main__.C is deprecated: use C2 instead
def f3(self) -> E[C]: ... # N: class __main__.C is deprecated: use C2 instead

[builtins fixtures/tuple.pyi]


[case testDeprecatedClassDifferentModule]

import m
import p.s
import m as n
import p.s as ps
from m import C # N: class m.C is deprecated: use C2 instead
from m import B, C # N: class m.B is deprecated: use B2 instead \
# N: class m.C is deprecated: use C2 instead
from p.s import D # N: class p.s.D is deprecated: use D2 instead
from k import *

Expand All @@ -170,9 +276,20 @@ C()
D()
E() # N: class k.E is deprecated: use E2 instead

x1: m.A # N: class m.A is deprecated: use A2 instead
x2: m.A = m.A() # N: class m.A is deprecated: use A2 instead
y1: B
y2: B = B()

[file m.py]
from typing_extensions import deprecated

@deprecated("use A2 instead")
class A: ...

@deprecated("use B2 instead")
class B: ...

@deprecated("use C2 instead")
class C: ...

Expand Down
Loading
Loading