Added support for @deprecation messages for decorators that wrap a function in a callable object. The deprecated message is now propagated through the ParamSpec and the __call__ method. This addresses #7732.

This commit is contained in:
Eric Traut 2024-04-21 21:22:07 -07:00
parent 0127fd67ad
commit c7168d7068
5 changed files with 86 additions and 5 deletions

View File

@ -4187,6 +4187,11 @@ export class Checker extends ParseTreeWalker {
errorMessage = LocMessage.deprecatedConstructor().format({
name: type.details.name,
});
} else if (isClassInstance(type) && overload.details.name === '__call__') {
deprecatedMessage = overload.details.deprecatedMessage;
errorMessage = LocMessage.deprecatedFunction().format({
name: node.value,
});
}
}
});
@ -4202,13 +4207,20 @@ export class Checker extends ParseTreeWalker {
) {
deprecatedMessage = subtype.details.deprecatedMessage;
errorMessage = LocMessage.deprecatedClass().format({ name: subtype.details.name });
} else {
// See if this is part of a call to a constructor.
getDeprecatedMessageForOverloadedCall(this._evaluator, subtype);
return;
}
} else if (isFunction(subtype)) {
getDeprecatedMessageForOverloadedCall(this._evaluator, subtype);
return;
}
if (isFunction(subtype)) {
if (subtype.details.deprecatedMessage !== undefined) {
if (!subtype.details.name || node.value === subtype.details.name) {
if (
!subtype.details.name ||
subtype.details.name === '__call__' ||
node.value === subtype.details.name
) {
deprecatedMessage = subtype.details.deprecatedMessage;
errorMessage = getDeprecatedMessageForFunction(subtype);
}

View File

@ -3420,6 +3420,8 @@ export function convertTypeToParamSpecValue(type: Type): FunctionType {
type.details.docString
);
newFunction.details.deprecatedMessage = type.details.deprecatedMessage;
type.details.parameters.forEach((param, index) => {
FunctionType.addParameter(newFunction, {
category: param.category,
@ -3482,6 +3484,7 @@ export function convertParamSpecValueToType(paramSpecValue: FunctionType, omitPa
functionType.details.paramSpec = paramSpecValue.details.paramSpec;
}
functionType.details.docString = paramSpecValue.details.docString;
functionType.details.deprecatedMessage = paramSpecValue.details.deprecatedMessage;
return functionType;
}

View File

@ -1934,10 +1934,15 @@ export namespace FunctionType {
});
newFunction.details.paramSpec = paramSpecValue.details.paramSpec;
if (!newFunction.details.docString) {
newFunction.details.docString = paramSpecValue.details.docString;
}
if (!newFunction.details.deprecatedMessage) {
newFunction.details.deprecatedMessage = paramSpecValue.details.deprecatedMessage;
}
FunctionType.addHigherOrderTypeVarScopeIds(newFunction, paramSpecValue.details.typeVarScopeId);
return newFunction;

View File

@ -585,3 +585,14 @@ test('Deprecated5', () => {
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['deprecated5.py'], configOptions);
TestUtils.validateResults(analysisResults2, 2);
});
test('Deprecated6', () => {
const configOptions = new ConfigOptions(Uri.empty());
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated6.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 3);
configOptions.diagnosticRuleSet.reportDeprecated = 'error';
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['deprecated6.py'], configOptions);
TestUtils.validateResults(analysisResults2, 3);
});

View File

@ -0,0 +1,50 @@
# This sample tests the case where a __call__ is marked deprecated.
from typing import Callable, Generic, ParamSpec, TypeVar
from typing_extensions import deprecated # pyright: ignore[reportMissingModuleSource]
class A:
@deprecated("Use ClassB instead")
def __call__(self) -> None: ...
a = A()
# This should generate an error if reportDeprecated is enabled.
a()
P = ParamSpec("P")
R = TypeVar("R")
class B(Generic[P, R]):
def __init__(self, cb: Callable[P, R]) -> None:
self.cb = cb
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
return self.cb(*args, **kwargs)
@B
@deprecated("Don't use this.")
def func1(x: int) -> None:
pass
# This should generate an error if reportDeprecated is enabled.
func1(3)
def deco1(cb: Callable[P, R]) -> B[P, R]:
return B(cb)
@deco1
@deprecated("Don't use this.")
def func2(x: int) -> None:
pass
# This should generate an error if reportDeprecated is enabled.
func2(3)