Added support for @deprecated in an __init__ method when constructing a class. This addresses https://github.com/microsoft/pyright/issues/4456.

This commit is contained in:
Eric Traut 2023-03-18 18:30:41 -06:00
parent 575adcb29a
commit 7ad4a8ead5
5 changed files with 74 additions and 29 deletions

View File

@ -3656,6 +3656,40 @@ export class Checker extends ParseTreeWalker {
let errorMessage: string | undefined;
let deprecatedMessage: string | undefined;
function getDeprecatedMessageForOverloadedCall(evaluator: TypeEvaluator, type: Type) {
// Determine if the node is part of a call expression. If so,
// we can determine which overload(s) were used to satisfy
// the call expression and determine whether any of them
// are deprecated.
const callNode = ParseTreeUtils.getCallForName(node);
if (callNode) {
const callTypeResult = evaluator.getTypeResult(callNode);
if (
callTypeResult &&
callTypeResult.overloadsUsedForCall &&
callTypeResult.overloadsUsedForCall.length > 0
) {
callTypeResult.overloadsUsedForCall.forEach((overload) => {
if (overload.details.deprecatedMessage !== undefined) {
if (node.value === overload.details.name) {
deprecatedMessage = overload.details.deprecatedMessage;
errorMessage = Localizer.Diagnostic.deprecatedFunction().format({
name: overload.details.name,
});
} else if (isInstantiableClass(type) && overload.details.name === '__init__') {
deprecatedMessage = overload.details.deprecatedMessage;
errorMessage = Localizer.Diagnostic.deprecatedConstructor().format({
name: type.details.name,
});
}
}
});
}
}
}
doForEachSubtype(type, (subtype) => {
if (isClass(subtype)) {
if (
@ -3664,39 +3698,24 @@ export class Checker extends ParseTreeWalker {
node.value === subtype.details.name
) {
deprecatedMessage = subtype.details.deprecatedMessage;
errorMessage = Localizer.Diagnostic.deprecatedClass();
errorMessage = Localizer.Diagnostic.deprecatedClass().format({ name: subtype.details.name });
} else {
// See if this is part of a call to a constructor.
getDeprecatedMessageForOverloadedCall(this._evaluator, subtype);
}
} else if (isFunction(subtype)) {
if (subtype.details.deprecatedMessage !== undefined && node.value === subtype.details.name) {
deprecatedMessage = subtype.details.deprecatedMessage;
errorMessage = Localizer.Diagnostic.deprecatedFunction();
errorMessage = Localizer.Diagnostic.deprecatedFunction().format({
name: subtype.details.name || '<anonymous>',
});
}
} else if (isOverloadedFunction(subtype)) {
// Determine if the node is part of a call expression. If so,
// we can determine which overload(s) were used to satisfy
// the call expression and determine whether any of them
// are deprecated.
const callNode = ParseTreeUtils.getCallForName(node);
if (callNode) {
const callTypeResult = this._evaluator.getTypeResult(callNode);
if (
callTypeResult &&
callTypeResult.overloadsUsedForCall &&
callTypeResult.overloadsUsedForCall.length > 0
) {
callTypeResult.overloadsUsedForCall.forEach((overload) => {
if (
overload.details.deprecatedMessage !== undefined &&
node.value === overload.details.name
) {
deprecatedMessage = overload.details.deprecatedMessage;
errorMessage = Localizer.Diagnostic.deprecatedFunction();
}
});
}
}
getDeprecatedMessageForOverloadedCall(this._evaluator, subtype);
}
});

View File

@ -328,8 +328,12 @@ export namespace Localizer {
export const declaredReturnTypeUnknown = () => getRawString('Diagnostic.declaredReturnTypeUnknown');
export const defaultValueContainsCall = () => getRawString('Diagnostic.defaultValueContainsCall');
export const defaultValueNotAllowed = () => getRawString('Diagnostic.defaultValueNotAllowed');
export const deprecatedClass = () => getRawString('Diagnostic.deprecatedClass');
export const deprecatedFunction = () => getRawString('Diagnostic.deprecatedFunction');
export const deprecatedClass = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.deprecatedClass'));
export const deprecatedConstructor = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.deprecatedConstructor'));
export const deprecatedFunction = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.deprecatedFunction'));
export const deprecatedType = () =>
new ParameterizedString<{ version: string; replacement: string }>(
getRawString('Diagnostic.deprecatedType')

View File

@ -91,8 +91,9 @@
"declaredReturnTypeUnknown": "Declared return type is unknown",
"defaultValueContainsCall": "Function calls and mutable objects not allowed within parameter default value expression",
"defaultValueNotAllowed": "Parameter with \"*\" or \"**\" cannot have default value",
"deprecatedClass": "This class is deprecated",
"deprecatedFunction": "This function is deprecated",
"deprecatedClass": "The class \"{name}\" is deprecated",
"deprecatedConstructor": "The constructor for class \"{name}\" is deprecated",
"deprecatedFunction": "This function \"{name}\" is deprecated",
"deprecatedType": "This type is deprecated as of Python {version}; use \"{replacement}\" instead",
"delTargetExpr": "Expression cannot be deleted",
"dictExpandIllegalInComprehension": "Dictionary expansion not allowed in comprehension",

View File

@ -504,11 +504,11 @@ test('Deprecated2', () => {
const configOptions = new ConfigOptions('.');
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated2.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 5);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 6);
configOptions.diagnosticRuleSet.reportDeprecated = 'error';
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['deprecated2.py'], configOptions);
TestUtils.validateResults(analysisResults2, 5);
TestUtils.validateResults(analysisResults2, 6);
});
test('Deprecated3', () => {

View File

@ -1,5 +1,6 @@
# This sample tests the @typing.deprecated decorator introduced in PEP 702.
from typing import Self
from typing_extensions import deprecated, overload
@ -69,3 +70,23 @@ func2("hi")
# This should generate an error if reportDeprecated is enabled.
func2(3)
class ClassD:
@overload
def __init__(self, x: int) -> None:
...
@overload
@deprecated("str no longer supported")
def __init__(self, x: str) -> None:
...
def __init__(self, x: int | str) -> None:
...
ClassD(3)
# This should generate an error if reportDeprecated is enabled.
ClassD("")