Skip to content

Commit 79d0cb0

Browse files
committed
implement reporting on type[ClassWithDeprecatedConstructor]()
1 parent 4be62c6 commit 79d0cb0

File tree

2 files changed

+142
-18
lines changed

2 files changed

+142
-18
lines changed

mypy/checkexpr.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,7 +1420,11 @@ def is_generic_decorator_overload_call(
14201420
return None
14211421

14221422
def handle_decorator_overload_call(
1423-
self, callee_type: CallableType, overloaded: Overloaded, ctx: Context
1423+
self,
1424+
callee_type: CallableType,
1425+
overloaded: Overloaded,
1426+
ctx: Context,
1427+
callee_is_overload_item: bool,
14241428
) -> tuple[Type, Type] | None:
14251429
"""Type-check application of a generic callable to an overload.
14261430
@@ -1432,7 +1436,9 @@ def handle_decorator_overload_call(
14321436
for item in overloaded.items:
14331437
arg = TempNode(typ=item)
14341438
with self.msg.filter_errors() as err:
1435-
item_result, inferred_arg = self.check_call(callee_type, [arg], [ARG_POS], ctx)
1439+
item_result, inferred_arg = self.check_call(
1440+
callee_type, [arg], [ARG_POS], ctx, is_overload_item=callee_is_overload_item
1441+
)
14361442
if err.has_new_errors():
14371443
# This overload doesn't match.
14381444
continue
@@ -1538,6 +1544,7 @@ def check_call(
15381544
callable_name: str | None = None,
15391545
object_type: Type | None = None,
15401546
original_type: Type | None = None,
1547+
is_overload_item: bool = False,
15411548
) -> tuple[Type, Type]:
15421549
"""Type check a call.
15431550
@@ -1558,6 +1565,7 @@ def check_call(
15581565
or None if unavailable (examples: 'builtins.open', 'typing.Mapping.get')
15591566
object_type: If callable_name refers to a method, the type of the object
15601567
on which the method is being called
1568+
is_overload_item: Whether this check is for an individual overload item
15611569
"""
15621570
callee = get_proper_type(callee)
15631571

@@ -1568,7 +1576,7 @@ def check_call(
15681576
# Special casing for inline application of generic callables to overloads.
15691577
# Supporting general case would be tricky, but this should cover 95% of cases.
15701578
overloaded_result = self.handle_decorator_overload_call(
1571-
callee, overloaded, context
1579+
callee, overloaded, context, is_overload_item
15721580
)
15731581
if overloaded_result is not None:
15741582
return overloaded_result
@@ -1582,6 +1590,7 @@ def check_call(
15821590
callable_node,
15831591
callable_name,
15841592
object_type,
1593+
is_overload_item,
15851594
)
15861595
elif isinstance(callee, Overloaded):
15871596
return self.check_overload_call(
@@ -1659,33 +1668,26 @@ def check_callable_call(
16591668
callable_node: Expression | None,
16601669
callable_name: str | None,
16611670
object_type: Type | None,
1671+
is_overload_item: bool = False,
16621672
) -> tuple[Type, Type]:
16631673
"""Type check a call that targets a callable value.
16641674
16651675
See the docstring of check_call for more information.
16661676
"""
1677+
# Check implicit calls to deprecated class constructors.
1678+
# Only the non-overload case is handled here. Overloaded constructors are handled
1679+
# separately during overload resolution.
1680+
if (not is_overload_item) and callee.is_type_obj():
1681+
self.chk.warn_deprecated(callee.definition, context)
1682+
16671683
# Always unpack **kwargs before checking a call.
16681684
callee = callee.with_unpacked_kwargs().with_normalized_var_args()
16691685
if callable_name is None and callee.name:
16701686
callable_name = callee.name
16711687
ret_type = get_proper_type(callee.ret_type)
16721688
if callee.is_type_obj() and isinstance(ret_type, Instance):
16731689
callable_name = ret_type.type.fullname
1674-
if isinstance(callable_node, RefExpr):
1675-
# Check implicit calls to deprecated class constructors.
1676-
# Only the non-overload case is handled here. Overloaded constructors are handled
1677-
# separately during overload resolution. `callable_node` is `None` for an overload
1678-
# item so deprecation checks are not duplicated.
1679-
callable_info: TypeInfo | None = None
1680-
if isinstance(callable_node.node, TypeInfo):
1681-
callable_info = callable_node.node
1682-
elif isinstance(callable_node.node, TypeAlias):
1683-
alias_target = get_proper_type(callable_node.node.target)
1684-
if isinstance(alias_target, Instance) and isinstance(alias_target.type, TypeInfo):
1685-
callable_info = alias_target.type
1686-
if callable_info is not None:
1687-
self.chk.check_deprecated(callee.definition, context)
1688-
1690+
if isinstance(callable_node, RefExpr) and (callable_node.fullname in ENUM_BASES):
16891691
if callable_node.fullname in ENUM_BASES:
16901692
# An Enum() call that failed SemanticAnalyzerPass2.check_enum_call().
16911693
return callee.ret_type, callee
@@ -2925,6 +2927,7 @@ def infer_overload_return_type(
29252927
context=context,
29262928
callable_name=callable_name,
29272929
object_type=object_type,
2930+
is_overload_item=True,
29282931
)
29292932
is_match = not w.has_new_errors()
29302933
if is_match:

test-data/unit/check-deprecated.test

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,127 @@ B2_explicit_alias
416416
[builtins fixtures/tuple.pyi]
417417

418418

419+
[case testDeprecatedClassConstructorCalledFromTypeType]
420+
# flags: --enable-error-code=deprecated
421+
422+
from typing_extensions import deprecated
423+
424+
class A:
425+
@deprecated("do not use")
426+
def __init__(self) -> None: ...
427+
428+
class B1(A): ...
429+
430+
class B2(A):
431+
def __init__(self) -> None: ...
432+
433+
def get_class_A() -> type[A]: ...
434+
def get_class_B1() -> type[B1]: ...
435+
def get_class_B2() -> type[B2]: ...
436+
437+
A_type_obj: type[A]
438+
B1_type_obj: type[B1]
439+
B2_type_obj: type[B2]
440+
A_type_obj() # E: function __main__.A.__init__ is deprecated: do not use
441+
B1_type_obj() # E: function __main__.A.__init__ is deprecated: do not use
442+
B2_type_obj()
443+
get_class_A()() # E: function __main__.A.__init__ is deprecated: do not use
444+
get_class_B1()() # E: function __main__.A.__init__ is deprecated: do not use
445+
get_class_B2()()
446+
447+
def call_class_A(cls: type[A]) -> None:
448+
cls() # E: function __main__.A.__init__ is deprecated: do not use
449+
450+
def call_class_B1(cls: type[B1]) -> None:
451+
cls() # E: function __main__.A.__init__ is deprecated: do not use
452+
453+
def call_class_B2(cls: type[B2]) -> None:
454+
cls()
455+
456+
[builtins fixtures/tuple.pyi]
457+
458+
459+
[case testDeprecatedClassConstructorTypeNarrowing]
460+
# flags: --enable-error-code=deprecated
461+
462+
from typing import TypeVar
463+
from typing_extensions import TypeAlias, TypeIs, deprecated
464+
465+
T = TypeVar("T")
466+
467+
class Dummy: ...
468+
469+
class A:
470+
@deprecated("do not use")
471+
def __init__(self) -> None: ...
472+
473+
class B(A):
474+
def __init__(self) -> None: ...
475+
476+
def maybe() -> bool: ...
477+
478+
# `builtins.issubclass()` does not type-narrow properly
479+
def is_subclass(c1: type, c2: type[T]) -> TypeIs[type[T]]: ...
480+
481+
A_alias: TypeAlias = A
482+
A_type_obj: type[A]
483+
B_alias: TypeAlias = B
484+
B_type_obj: type[B]
485+
486+
maybe_A = A if maybe() else None
487+
maybe_A_alias = A_alias if maybe() else None
488+
maybe_A_type_obj = A_type_obj if maybe() else None
489+
if maybe_A is not None:
490+
maybe_A() # E: function __main__.A.__init__ is deprecated: do not use
491+
else:
492+
maybe_A() # E: "None" not callable
493+
if maybe_A_alias is not None:
494+
maybe_A_alias() # E: function __main__.A.__init__ is deprecated: do not use
495+
else:
496+
maybe_A_alias() # E: "None" not callable
497+
if maybe_A_type_obj is not None:
498+
maybe_A_type_obj() # E: function __main__.A.__init__ is deprecated: do not use
499+
else:
500+
maybe_A_type_obj() # E: "None" not callable
501+
502+
A_or_Dummy = A if maybe() else Dummy
503+
A_type_obj_or_Dummy = A_type_obj if maybe() else Dummy
504+
if is_subclass(A_or_Dummy, A):
505+
A_or_Dummy() # E: function __main__.A.__init__ is deprecated: do not use
506+
else:
507+
A_or_Dummy()
508+
if is_subclass(A_or_Dummy, Dummy):
509+
A_or_Dummy()
510+
else:
511+
A_or_Dummy() # E: function __main__.A.__init__ is deprecated: do not use
512+
if is_subclass(A_type_obj_or_Dummy, A):
513+
A_type_obj_or_Dummy() # E: function __main__.A.__init__ is deprecated: do not use
514+
else:
515+
A_type_obj_or_Dummy()
516+
if is_subclass(A_type_obj_or_Dummy, Dummy):
517+
A_type_obj_or_Dummy()
518+
else:
519+
A_type_obj_or_Dummy() # E: function __main__.A.__init__ is deprecated: do not use
520+
521+
A_or_B = A if maybe() else B
522+
A_or_B_alias = A if maybe() else B_alias
523+
A_or_B_type_obj = A if maybe() else B_type_obj
524+
if is_subclass(A_or_B, B):
525+
A_or_B()
526+
else:
527+
A_or_B() # E: function __main__.A.__init__ is deprecated: do not use
528+
if is_subclass(A_or_B_alias, B):
529+
A_or_B_alias()
530+
else:
531+
A_or_B_alias() # E: function __main__.A.__init__ is deprecated: do not use
532+
if is_subclass(A_or_B_type_obj, B):
533+
A_or_B_type_obj()
534+
else:
535+
A_or_B_type_obj() # E: function __main__.A.__init__ is deprecated: do not use
536+
537+
[builtins fixtures/tuple.pyi]
538+
539+
419540
[case testDeprecatedSpecialMethods]
420541
# flags: --enable-error-code=deprecated
421542

0 commit comments

Comments
 (0)