Skip to content

Commit 39e752f

Browse files
committed
Warn usage of types with deprecated constructors in callable-like contexts
1 parent 00897ab commit 39e752f

File tree

2 files changed

+262
-1
lines changed

2 files changed

+262
-1
lines changed

mypy/checkexpr.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
YieldExpr,
103103
YieldFromExpr,
104104
)
105-
from mypy.options import PRECISE_TUPLE_TYPES
105+
from mypy.options import PRECISE_TUPLE_TYPES, Options
106106
from mypy.plugin import (
107107
FunctionContext,
108108
FunctionSigContext,
@@ -376,6 +376,16 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
376376
# generating extra type errors.
377377
result = AnyType(TypeOfAny.from_error)
378378
if isinstance(node, TypeInfo):
379+
if self.type_context[-1] is not None:
380+
proper_result = get_proper_type(result)
381+
if isinstance(proper_result, (CallableType, Overloaded)):
382+
ctor_type = constructor_type_in_callable_context(
383+
proper_result,
384+
get_proper_type(self.type_context[-1]),
385+
self.chk.options,
386+
)
387+
if ctor_type is not None:
388+
self.chk.check_deprecated(ctor_type.definition, e)
379389
if isinstance(result, CallableType) and isinstance( # type: ignore[misc]
380390
result.ret_type, Instance
381391
):
@@ -6782,3 +6792,53 @@ def is_type_type_context(context: Type | None) -> bool:
67826792
if isinstance(context, UnionType):
67836793
return any(is_type_type_context(item) for item in context.items)
67846794
return False
6795+
6796+
6797+
def constructor_type_in_callable_context(
6798+
constructor_type: CallableType | Overloaded,
6799+
context: ProperType,
6800+
options: Options,
6801+
/,
6802+
*,
6803+
_check_subtyping: bool = False,
6804+
) -> CallableType | None:
6805+
"""
6806+
Gets a class constructor type if it's used in a valid callable type context.
6807+
Considers the following cases as valid contexts:
6808+
6809+
* A plain `Callable` context is always treated as a valid context.
6810+
* A union type context requires at least one of the union items to be a supertype of
6811+
the class type, in addition to being a `Callable` or callable `Protocol`.
6812+
* A callable `Protocol` context is only treated as a valid context if the
6813+
constructor type is a subtype of the protocol or overloaded type.
6814+
6815+
If the class type is overloaded, use the first overload which is in a valid context.
6816+
"""
6817+
6818+
item: Type
6819+
if isinstance(constructor_type, Overloaded):
6820+
for item in constructor_type.items:
6821+
result = constructor_type_in_callable_context(
6822+
item, context, options, _check_subtyping=True
6823+
)
6824+
if result is not None:
6825+
return result
6826+
elif isinstance(context, CallableType):
6827+
if (not _check_subtyping) or is_subtype(constructor_type, context, options=options):
6828+
return constructor_type
6829+
elif isinstance(context, UnionType):
6830+
for item in context.items:
6831+
result = constructor_type_in_callable_context(
6832+
constructor_type, get_proper_type(item), options, _check_subtyping=True
6833+
)
6834+
if result is not None:
6835+
return result
6836+
elif isinstance(context, Instance):
6837+
if (
6838+
context.type.is_protocol
6839+
and ("__call__" in context.type.protocol_members)
6840+
and is_subtype(constructor_type, context, options=options)
6841+
):
6842+
return constructor_type
6843+
6844+
return None

test-data/unit/check-deprecated.test

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,207 @@ B_explicit_alias
399399
[builtins fixtures/tuple.pyi]
400400

401401

402+
[case testDeprecatedClassConstructorInCallableTypeContext]
403+
# flags: --enable-error-code=deprecated
404+
405+
from typing import Any, Callable, ClassVar, TypeVar, Generic
406+
from typing_extensions import deprecated
407+
408+
class A:
409+
@deprecated("do not use")
410+
def __init__(self) -> None: ...
411+
412+
def receives_callable(c: Callable[..., Any], /) -> None: ...
413+
callable_receiver: Callable[[Callable[..., Any]], None]
414+
415+
T = TypeVar("T")
416+
417+
class CallableAttr(Generic[T]):
418+
def __get__(self, instance: Settable[T], owner: type[Settable[T]], /) -> T: ...
419+
def __set__(self, instance: Settable[T], value: T, /) -> None: ...
420+
421+
class Settable(Generic[T]):
422+
instance: T
423+
attr = CallableAttr[T]()
424+
425+
@property
426+
def prop(self) -> T: ...
427+
@prop.setter
428+
def prop(self, c: T, /) -> None: ...
429+
430+
# Simple assignment
431+
A_callable: Callable[..., Any] = (
432+
A # E: function __main__.A.__init__ is deprecated: do not use
433+
)
434+
435+
# Multiple assignments
436+
A_multi_callable: Callable[..., Any]
437+
A_multi_callable, var = (
438+
A, # E: function __main__.A.__init__ is deprecated: do not use
439+
1,
440+
)
441+
442+
# Function argument
443+
receives_callable(
444+
A # E: function __main__.A.__init__ is deprecated: do not use
445+
)
446+
447+
# Callable type argument
448+
callable_receiver(
449+
A # E: function __main__.A.__init__ is deprecated: do not use
450+
)
451+
452+
# Function return type
453+
def func_returns_callable(arg: int) -> Callable[..., Any]:
454+
return A # E: function __main__.A.__init__ is deprecated: do not use
455+
456+
# Typed lambda return type
457+
lambda_returns_callable_1: Callable[[], Callable[..., Any]] = (
458+
lambda: A # E: function __main__.A.__init__ is deprecated: do not use
459+
)
460+
lambda_returns_callable_2: Callable[[], Callable[..., Any]]
461+
lambda_returns_callable_2 = lambda: (
462+
A # E: function __main__.A.__init__ is deprecated: do not use
463+
)
464+
465+
# Class and instance attributes
466+
settable: Settable[Callable[..., Any]]
467+
settable.instance = (
468+
A # E: function __main__.A.__init__ is deprecated: do not use
469+
)
470+
settable.attr = (
471+
A # E: function __main__.A.__init__ is deprecated: do not use
472+
)
473+
settable.prop = (
474+
A # E: function __main__.A.__init__ is deprecated: do not use
475+
)
476+
class SettableChild(Settable[Callable[..., Any]]):
477+
class_: ClassVar[Callable[..., Any]] = (
478+
A # E: function __main__.A.__init__ is deprecated: do not use
479+
)
480+
481+
# Checks for false positives
482+
483+
def receives_type(t: type[A], /) -> None: ...
484+
def receives_object(o: object) -> None: ...
485+
def receives_any(o: Any) -> None: ...
486+
type_receiver: Callable[[type[A]], None]
487+
object_receiver: Callable[[object], None]
488+
any_receiver: Callable[[Any], None]
489+
490+
A_type: type[A] = A
491+
A_object: object = A
492+
A_any: Any = A
493+
receives_type(A_type)
494+
receives_object(A_object)
495+
receives_any(A_any)
496+
type_receiver(A_type)
497+
object_receiver(A_object)
498+
any_receiver(A_any)
499+
500+
def func_returns_type(arg: int) -> type[A]: return A
501+
def func_returns_object(arg: int) -> object: return A
502+
def func_returns_any(arg: int) -> Any: return A
503+
lambda_returns_type: Callable[[], type[A]] = lambda: A
504+
lambda_returns_object: Callable[[], object] = lambda: A
505+
lambda_returns_any: Callable[[], Any] = lambda: A
506+
507+
settable2: Settable[type[A]]
508+
settable2.instance = A
509+
settable2.attr = A
510+
settable2.prop = A
511+
512+
class SettableChild2(Settable[type[A]]):
513+
class_: ClassVar[type[A]] = A
514+
515+
[builtins fixtures/property.pyi]
516+
517+
518+
[case testDeprecatedClassConstructorInUnionTypeContext]
519+
# flags: --enable-error-code=deprecated
520+
521+
from typing import Any, Callable, Union, Optional
522+
from typing_extensions import deprecated
523+
524+
class Dummy: ...
525+
526+
class A:
527+
@deprecated("do not use")
528+
def __init__(self) -> None: ...
529+
530+
callable_or_dummy: Union[Callable[..., Any], Dummy] = A # E: function __main__.A.__init__ is deprecated: do not use
531+
maybe_callable: Optional[Callable[..., Any]] = A # E: function __main__.A.__init__ is deprecated: do not use
532+
533+
type_or_dummy: Union[type[A], Dummy] = A
534+
maybe_type: Optional[type[A]] = A
535+
536+
[builtins fixtures/tuple.pyi]
537+
538+
539+
[case testDeprecatedClassConstructorInProtocolTypeContext]
540+
# flags: --enable-error-code=deprecated
541+
542+
from typing import Protocol, Union
543+
from typing_extensions import deprecated
544+
545+
class CompatibleProto(Protocol):
546+
def __call__(self) -> A: ...
547+
548+
class IncompatibleProto1(Protocol):
549+
def __call__(self, a: int, /) -> A: ...
550+
551+
class IncompatibleProto2(Protocol):
552+
var: int
553+
def __call__(self) -> A: ...
554+
555+
class A:
556+
@deprecated("do not use")
557+
def __init__(self) -> None: ...
558+
559+
CallableA: Union[CompatibleProto, type[A]] = (
560+
A # E: function __main__.A.__init__ is deprecated: do not use
561+
)
562+
AType1: Union[IncompatibleProto1, type[A]] = A
563+
AType2: Union[IncompatibleProto2, type[A]] = A
564+
565+
[builtins fixtures/tuple.pyi]
566+
567+
568+
[case testDeprecatedOverloadedClassConstructorInCallableTypeContext]
569+
# flags: --enable-error-code=deprecated --disable-error-code=no-overload-impl
570+
571+
from typing import Callable, overload
572+
from typing_extensions import deprecated
573+
574+
class A:
575+
@overload
576+
@deprecated("use `make_a` instead")
577+
def __init__(self) -> None: ...
578+
@overload
579+
@deprecated("use `make_a_with_int()` instead")
580+
def __init__(self, a: int) -> None: ...
581+
@overload
582+
def __init__(self, a: str) -> None: ...
583+
@classmethod
584+
def make_a(cls) -> A: ...
585+
@classmethod
586+
def make_a_with_int(cls, a: int) -> A: ...
587+
588+
AFactory: Callable[[], A] = (
589+
A # E: overload def (self: __main__.A) of function __main__.A.__init__ is deprecated: use `make_a` instead
590+
)
591+
AFactoryWithInt: Callable[[int], A] = (
592+
A # E: overload def (self: __main__.A, a: builtins.int) of function __main__.A.__init__ is deprecated: use `make_a_with_int()` instead
593+
)
594+
595+
AFactoryWithStr: Callable[[str], A] = A
596+
IncompatibleFactory: Callable[[bytes], A] = (
597+
A # E: Incompatible types in assignment (expression has type "type[A]", variable has type "Callable[[bytes], A]")
598+
)
599+
600+
[builtins fixtures/tuple.pyi]
601+
602+
402603
[case testDeprecatedSpecialMethods]
403604
# flags: --enable-error-code=deprecated
404605

0 commit comments

Comments
 (0)