diff --git a/mypy/checker.py b/mypy/checker.py index 04a286beef5e..26371e27754f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4648,9 +4648,6 @@ def check_member_assignment( # Search for possible deprecations: mx.chk.check_deprecated(dunder_set, mx.context) - mx.chk.warn_deprecated_overload_item( - dunder_set, mx.context, target=inferred_dunder_set_type, selftype=attribute_type - ) # In the following cases, a message already will have been recorded in check_call. if (not isinstance(inferred_dunder_set_type, CallableType)) or ( @@ -7894,21 +7891,6 @@ def warn_deprecated(self, node: Node | None, context: Context) -> None: warn = self.msg.note if self.options.report_deprecated_as_note else self.msg.fail warn(deprecated, context, code=codes.DEPRECATED) - def warn_deprecated_overload_item( - self, node: Node | None, context: Context, *, target: Type, selftype: Type | None = None - ) -> None: - """Warn if the overload item corresponding to the given callable is deprecated.""" - target = get_proper_type(target) - if isinstance(node, OverloadedFuncDef) and isinstance(target, CallableType): - for item in node.items: - if isinstance(item, Decorator) and isinstance( - candidate := item.func.type, CallableType - ): - if selftype is not None and not node.is_static: - candidate = bind_self(candidate, selftype) - if candidate == target: - self.warn_deprecated(item.func, context) - # leafs def visit_pass_stmt(self, o: PassStmt, /) -> None: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4078d447dab8..b84d2b35d46e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -132,7 +132,6 @@ validate_instance, ) from mypy.typeops import ( - bind_self, callable_type, custom_special_method, erase_to_union_or_bound, @@ -1476,15 +1475,6 @@ def check_call_expr_with_callee_type( object_type=object_type, ) proper_callee = get_proper_type(callee_type) - if isinstance(e.callee, (NameExpr, MemberExpr)): - node = e.callee.node - if node is None and member is not None and isinstance(object_type, Instance): - if (symbol := object_type.type.get(member)) is not None: - node = symbol.node - self.chk.check_deprecated(node, e) - self.chk.warn_deprecated_overload_item( - node, e, target=callee_type, selftype=object_type - ) if isinstance(e.callee, RefExpr) and isinstance(proper_callee, CallableType): # Cache it for find_isinstance_check() if proper_callee.type_guard is not None: @@ -2652,6 +2642,24 @@ def check_overload_call( context: Context, ) -> tuple[Type, Type]: """Checks a call to an overloaded function.""" + + # The following hack tries to update the `definition` attribute of the given callable + # type's items with the related decorator symbols to allow checking for deprecations: + funcdef = None + if callable_name is not None: + if isinstance(inst := get_proper_type(object_type), Instance): + if (sym := inst.type.get(callable_name.rpartition(".")[-1])) is not None: + funcdef = sym.node + else: + name_module, _, name = callable_name.rpartition(".") + if (module := self.chk.modules.get(name_module)) is not None and ( + sym := module.names.get(name) + ) is not None: + funcdef = sym.node + if isinstance(funcdef, OverloadedFuncDef): + for typ, defn in zip(callee.items, funcdef.items): + typ.definition = defn + # Normalize unpacked kwargs before checking the call. callee = callee.with_unpacked_kwargs() arg_types = self.infer_arg_types_in_empty_context(args) @@ -2693,7 +2701,8 @@ def check_overload_call( # Record if we succeeded. Next we need to see if maybe normal procedure # gives a narrower type. if unioned_return: - returns, inferred_types = zip(*unioned_return) + returns = tuple(u[0] for u in unioned_return) + inferred_types = tuple(u[1] for u in unioned_return) # Note that we use `combine_function_signatures` instead of just returning # a union of inferred callables because for example a call # Union[int -> int, str -> str](Union[int, str]) is invalid and @@ -2714,7 +2723,7 @@ def check_overload_call( object_type, context, ) - # If any of checks succeed, stop early. + # If any of checks succeed, perform deprecation tests and stop early. if inferred_result is not None and unioned_result is not None: # Both unioned and direct checks succeeded, choose the more precise type. if ( @@ -2722,11 +2731,17 @@ def check_overload_call( and not isinstance(get_proper_type(inferred_result[0]), AnyType) and not none_type_var_overlap ): - return inferred_result - return unioned_result - elif unioned_result is not None: + unioned_result = None + else: + inferred_result = None + if unioned_result is not None: + for inferred_type in inferred_types: + if isinstance(c := get_proper_type(inferred_type), CallableType): + self.chk.warn_deprecated(c.definition, context) return unioned_result - elif inferred_result is not None: + if inferred_result is not None: + if isinstance(c := get_proper_type(inferred_result[1]), CallableType): + self.chk.warn_deprecated(c.definition, context) return inferred_result # Step 4: Failure. At this point, we know there is no match. We fall back to trying @@ -4077,21 +4092,11 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None: results = [] for name, method, obj, arg in variants: with self.msg.filter_errors(save_filtered_errors=True) as local_errors: - result = self.check_method_call(op_name, obj, method, [arg], [ARG_POS], context) + result = self.check_method_call(name, obj, method, [arg], [ARG_POS], context) if local_errors.has_new_errors(): errors.append(local_errors.filtered_errors()) results.append(result) else: - if isinstance(obj, Instance) and isinstance( - defn := obj.type.get_method(name), OverloadedFuncDef - ): - for item in defn.items: - if ( - isinstance(item, Decorator) - and isinstance(typ := item.func.type, CallableType) - and bind_self(typ) == result[1] - ): - self.chk.check_deprecated(item.func, context) return result # We finish invoking above operators and no early return happens. Therefore, diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 0994d0df400b..759ed077ac46 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -5,6 +5,7 @@ from collections.abc import Sequence from typing import TYPE_CHECKING, Callable, cast +import mypy.errorcodes as codes from mypy import message_registry, subtypes from mypy.erasetype import erase_typevars from mypy.expandtype import ( @@ -701,6 +702,10 @@ def analyze_descriptor_access( object_type=descriptor_type, ) + deprecated_disabled = False + if assignment and codes.DEPRECATED in mx.chk.options.enabled_error_codes: + mx.chk.options.enabled_error_codes.remove(codes.DEPRECATED) + deprecated_disabled = True _, inferred_dunder_get_type = mx.chk.expr_checker.check_call( dunder_get_type, [ @@ -712,12 +717,10 @@ def analyze_descriptor_access( object_type=descriptor_type, callable_name=callable_name, ) - + if deprecated_disabled: + mx.chk.options.enabled_error_codes.add(codes.DEPRECATED) if not assignment: mx.chk.check_deprecated(dunder_get, mx.context) - mx.chk.warn_deprecated_overload_item( - dunder_get, mx.context, target=inferred_dunder_get_type, selftype=descriptor_type - ) inferred_dunder_get_type = get_proper_type(inferred_dunder_get_type) if isinstance(inferred_dunder_get_type, AnyType): diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 1b0cc218ed16..82b521405515 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -252,6 +252,18 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb setter_type = snapshot_optional_type(first_item.var.setter_type) is_trivial_body = impl.is_trivial_body if impl else False dataclass_transform_spec = find_dataclass_transform_spec(node) + + deprecated = None + if isinstance(node, FuncDef): + deprecated = node.deprecated + elif isinstance(node, OverloadedFuncDef): + deprecated_list = [node.deprecated] + [ + i.func.deprecated for i in node.items if isinstance(i, Decorator) + ] + deprecated_list_cleaned = [d for d in deprecated_list if d is not None] + if deprecated_list_cleaned: + deprecated = ",".join(deprecated_list_cleaned) + return ( "Func", common, @@ -262,7 +274,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb signature, is_trivial_body, dataclass_transform_spec.serialize() if dataclass_transform_spec is not None else None, - node.deprecated if isinstance(node, FuncDef) else None, + deprecated, setter_type, # multi-part properties are stored as OverloadedFuncDef ) elif isinstance(node, Var): diff --git a/test-data/unit/check-deprecated.test b/test-data/unit/check-deprecated.test index 6cc160fad81f..a5681134bda4 100644 --- a/test-data/unit/check-deprecated.test +++ b/test-data/unit/check-deprecated.test @@ -671,9 +671,11 @@ C().g = "x" # E: function __main__.C.g is deprecated: use g2 instead \ [case testDeprecatedDescriptor] # flags: --enable-error-code=deprecated -from typing import Any, Optional, Union, overload +from typing import Any, Generic, Optional, overload, TypeVar, Union from typing_extensions import deprecated +T = TypeVar("T") + @deprecated("use E1 instead") class D1: def __get__(self, obj: Optional[C], objtype: Any) -> Union[D1, int]: ... @@ -701,10 +703,19 @@ class D3: def __set__(self, obj: C, value: str) -> None: ... def __set__(self, obj: C, value: Union[int, str]) -> None: ... +class D4(Generic[T]): + @overload + def __get__(self, obj: None, objtype: Any) -> T: ... + @overload + @deprecated("deprecated instance access") + def __get__(self, obj: C, objtype: Any) -> T: ... + def __get__(self, obj: Optional[C], objtype: Any) -> T: ... + class C: d1 = D1() # E: class __main__.D1 is deprecated: use E1 instead d2 = D2() d3 = D3() + d4 = D4[int]() c: C C.d1 @@ -719,15 +730,21 @@ C.d3 # E: overload def (self: __main__.D3, obj: None, objtype: Any) -> __main__ c.d3 # E: overload def (self: __main__.D3, obj: __main__.C, objtype: Any) -> builtins.int of function __main__.D3.__get__ is deprecated: use E3.__get__ instead c.d3 = 1 c.d3 = "x" # E: overload def (self: __main__.D3, obj: __main__.C, value: builtins.str) of function __main__.D3.__set__ is deprecated: use E3.__set__ instead + +C.d4 +c.d4 # E: overload def (self: __main__.D4[T`1], obj: __main__.C, objtype: Any) -> T`1 of function __main__.D4.__get__ is deprecated: deprecated instance access [builtins fixtures/property.pyi] [case testDeprecatedOverloadedFunction] # flags: --enable-error-code=deprecated -from typing import Union, overload +from typing import Any, overload, Union from typing_extensions import deprecated +int_or_str: Union[int, str] +any: Any + @overload def f(x: int) -> int: ... @overload @@ -738,6 +755,8 @@ def f(x: Union[int, str]) -> Union[int, str]: ... f # E: function __main__.f is deprecated: use f2 instead f(1) # E: function __main__.f is deprecated: use f2 instead f("x") # E: function __main__.f is deprecated: use f2 instead +f(int_or_str) # E: function __main__.f is deprecated: use f2 instead +f(any) # E: function __main__.f is deprecated: use f2 instead f(1.0) # E: function __main__.f is deprecated: use f2 instead \ # E: No overload variant of "f" matches argument type "float" \ # N: Possible overload variants: \ @@ -754,6 +773,8 @@ def g(x: Union[int, str]) -> Union[int, str]: ... g g(1) # E: overload def (x: builtins.int) -> builtins.int of function __main__.g is deprecated: work with str instead g("x") +g(int_or_str) # E: overload def (x: builtins.int) -> builtins.int of function __main__.g is deprecated: work with str instead +g(any) g(1.0) # E: No overload variant of "g" matches argument type "float" \ # N: Possible overload variants: \ # N: def g(x: int) -> int \ @@ -769,14 +790,63 @@ def h(x: Union[int, str]) -> Union[int, str]: ... h h(1) h("x") # E: overload def (x: builtins.str) -> builtins.str of function __main__.h is deprecated: work with int instead +h(int_or_str) # E: overload def (x: builtins.str) -> builtins.str of function __main__.h is deprecated: work with int instead +h(any) h(1.0) # E: No overload variant of "h" matches argument type "float" \ # N: Possible overload variants: \ # N: def h(x: int) -> int \ # N: def h(x: str) -> str +@overload +def i(x: int) -> int: ... +@overload +@deprecated("work with int instead") +def i(x: str) -> str: ... +@overload +def i(x: Any) -> Any: ... +def i(x: Union[int, str]) -> Union[int, str]: ... + +i +i(1) +i("x") # E: overload def (x: builtins.str) -> builtins.str of function __main__.i is deprecated: work with int instead +i(int_or_str) # E: overload def (x: builtins.str) -> builtins.str of function __main__.i is deprecated: work with int instead +i(any) +i(1.0) + [builtins fixtures/tuple.pyi] +@overload +def j(x: int) -> int: ... +@overload +def j(x: str) -> str: ... +@overload +@deprecated("work with int or str instead") +def j(x: Any) -> Any: ... +def j(x: Union[int, str]) -> Union[int, str]: ... + +j +j(1) +j("x") +j(int_or_str) +j(any) # E: overload def (x: builtins.str) -> builtins.str of function __main__.i is deprecated: work with int or str instead +j(1.0) +@overload +@deprecated("work with str instead") +def k(x: int) -> int: ... +@overload +def k(x: str) -> str: ... +@overload +@deprecated("work with str instead") +def k(x: object) -> Any: ... +def k(x: Union[int, str]) -> Union[int, str]: ... + +k +k(1) +k("x") +k(int_or_str) +k(any) # E: overload def (x: builtins.str) -> builtins.str of function __main__.i is deprecated: work with int or str instead +k(1.0) [case testDeprecatedImportedOverloadedFunction] # flags: --enable-error-code=deprecated diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index d2b1a8a92b80..f8e8dcca31ef 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -11003,6 +11003,313 @@ b.py:1: error: class a.C is deprecated: use C2 instead b.py:2: error: class a.D is deprecated: use D2 instead +[case testDeprecatedAddKeepChangeAndRemoveOverloadedFunctionDeprecation] +# flags: --enable-error-code=deprecated + +from a import f +f(1) +f("y") +import a +a.f(1) +a.f("y") + +[file a.py] +from typing import overload, Union +@overload +def f(x: int) -> int: ... +@overload +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[file a.py.2] +from typing import overload, Union +from typing_extensions import deprecated +@overload +def f(x: int) -> int: ... +@overload +@deprecated("pass int") +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[file a.py.3] +from typing import overload, Union +from typing_extensions import deprecated +@overload +def f(x: int) -> int: ... +@overload +@deprecated("pass int") +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[file a.py.4] +from typing import overload, Union +from typing_extensions import deprecated +@overload +def f(x: int) -> int: ... +@overload +@deprecated("pass int, please") +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[file a.py.5] +from typing import overload, Union +@overload +def f(x: int) -> int: ... +@overload +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[builtins fixtures/tuple.pyi] +[out] +== +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +== +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +== +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int, please +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int, please +== + + +[case testDeprecatedRemoveOverloadedFunctionDeprecation] +# flags: --enable-error-code=deprecated + +from a import f +f(1) +f("y") +import a +a.f(1) +a.f("y") + +[file a.py] +from typing import overload, Union +from typing_extensions import deprecated +@overload +def f(x: int) -> int: ... +@overload +@deprecated("pass int") +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[file a.py.2] +from typing import overload, Union +@overload +def f(x: int) -> int: ... +@overload +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[builtins fixtures/tuple.pyi] +[out] +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +== + + +[case testDeprecatedKeepOverloadedFunctionDeprecation] +# flags: --enable-error-code=deprecated + +from a import f +f(1) +f("y") +import a +a.f(1) +a.f("y") + +[file a.py] +from typing import overload, Union +from typing_extensions import deprecated +@overload +def f(x: int) -> int: ... +@overload +@deprecated("pass int") +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[file a.py.2] +from typing import overload, Union +from typing_extensions import deprecated +@overload +def f(x: int) -> int: ... +@overload +@deprecated("pass int") +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[builtins fixtures/tuple.pyi] +[out] +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +== +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int + + +[case testDeprecatedAddOverloadedFunctionDeprecationIndirectImport] +# flags: --enable-error-code=deprecated + +from b import f +f(1) +f("y") +import b +b.f(1) +b.f("y") + +[file b.py] +from a import f + +[file a.py] +from typing import overload, Union +@overload +def f(x: int) -> int: ... +@overload +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[file a.py.2] +from typing import overload, Union +from typing_extensions import deprecated +@overload +def f(x: int) -> int: ... +@overload +@deprecated("pass int") +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[builtins fixtures/tuple.pyi] +[out] +== +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int + + +[case testDeprecatedChangeOverloadedFunctionDeprecationIndirectImport] +# flags: --enable-error-code=deprecated + +from b import f +f(1) +f("y") +import b +b.f(1) +b.f("y") + +[file b.py] +from a import f + +[file a.py] +from typing import overload, Union +from typing_extensions import deprecated +@overload +def f(x: int) -> int: ... +@overload +@deprecated("pass int") +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[file a.py.2] +from typing import overload, Union +from typing_extensions import deprecated +@overload +def f(x: int) -> int: ... +@overload +@deprecated("pass int, please") +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[builtins fixtures/tuple.pyi] +[out] +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +== +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int, please +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int, please + + +[case testDeprecatedRemoveOverloadedFunctionDeprecationIndirectImport] +# flags: --enable-error-code=deprecated + +from b import f +f(1) +f("y") +import b +b.f(1) +b.f("y") + +[file b.py] +from a import f + +[file a.py] +from typing import overload, Union +from typing_extensions import deprecated +@overload +def f(x: int) -> int: ... +@overload +@deprecated("pass int") +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[file a.py.2] +from typing import overload, Union +@overload +def f(x: int) -> int: ... +@overload +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[builtins fixtures/tuple.pyi] +[out] +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: pass int +== + + +[case testDeprecatedOverloadedFunctionAlreadyDecorated] +# flags: --enable-error-code=deprecated + +from b import f +f(1) +f("y") +import b +b.f(1) +b.f("y") + +[file b.py] +from a import f + +[file a.py] +from typing import Callable, overload, Union + +def d(t: Callable[[str], str]) -> Callable[[str], str]: ... + +@overload +def f(x: int) -> int: ... +@overload +@d +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[file a.py.2] +from typing import Callable, overload, Union +from typing_extensions import deprecated + +def d(t: Callable[[str], str]) -> Callable[[str], str]: ... + +@overload +def f(x: int) -> int: ... +@overload +@deprecated("deprecated decorated overload") +@d +def f(x: str) -> str: ... +def f(x: Union[int, str]) -> Union[int, str]: ... + +[builtins fixtures/tuple.pyi] +[out] +== +main:5: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: deprecated decorated overload +main:8: error: overload def (x: builtins.str) -> builtins.str of function a.f is deprecated: deprecated decorated overload + [case testDeprecatedChangeClassDeprecationIndirectImport] # flags: --enable-error-code=deprecated from b import C