Added provisional support for draft PEP 702 (marking deprecations using the type system).

This commit is contained in:
Eric Traut 2023-01-13 21:16:07 -08:00
parent 761fabc9c3
commit 53e8cd4145
17 changed files with 366 additions and 37 deletions

View File

@ -35,6 +35,7 @@ Pyright supports [configuration files](/docs/configuration.md) that provide gran
* [PEP 695](https://www.python.org/dev/peps/pep-0695/) (draft) Type parameter syntax
* [PEP 696](https://www.python.org/dev/peps/pep-0696/) (draft) Type defaults for TypeVarLikes
* [PEP 698](https://www.python.org/dev/peps/pep-0698/) (draft) Override decorator for static typing
* [PEP 702](https://www.python.org/dev/peps/pep-0702/) (draft) Marking deprecations
* Type inference for function return values, instance variables, class variables, and globals
* Type guards that understand conditional code flow constructs like if/else statements

View File

@ -112,6 +112,8 @@ The following settings control pyrights diagnostic output (warnings or errors
<a name="reportConstantRedefinition"></a> **reportConstantRedefinition** [boolean or string, optional]: Generate or suppress diagnostics for attempts to redefine variables whose names are all-caps with underscores and numerals. The default value for this setting is 'none'.
<a name="reportDeprecated"></a> **reportDeprecated** [boolean or string, optional]: Generate or suppress diagnostics for use of a class or function that has been marked as deprecated. The default value for this setting is 'none'.
<a name="reportIncompatibleMethodOverride"></a> **reportIncompatibleMethodOverride** [boolean or string, optional]: Generate or suppress diagnostics for methods that override a method of the same name in a base class in an incompatible manner (wrong number of parameters, incompatible parameter types, or incompatible return type). The default value for this setting is 'none'.
<a name="reportIncompatibleVariableOverride"></a> **reportIncompatibleVariableOverride** [boolean or string, optional]: Generate or suppress diagnostics for class variable declarations that override a symbol of the same name in a base class with a type that is incompatible with the base class symbol type. The default value for this setting is 'none'.
@ -321,6 +323,7 @@ The following table lists the default severity levels for each diagnostic rule w
| reportUnboundVariable | "none" | "error" | "error" |
| reportUnusedCoroutine | "none" | "error" | "error" |
| reportConstantRedefinition | "none" | "none" | "error" |
| reportDeprecated | "none" | "none" | "error" |
| reportDuplicateImport | "none" | "none" | "error" |
| reportFunctionMemberAccess | "none" | "none" | "error" |
| reportImportCycles | "none" | "none" | "error" |

View File

@ -1374,9 +1374,9 @@ export class Checker extends ParseTreeWalker {
this._reportUnboundName(node);
}
// Report the use of a deprecated symbol. For now, this functionality
// is disabled. We'll leave it in place for the future.
// this._reportDeprecatedUse(node);
// Report the use of a deprecated symbol.
const type = this._evaluator.getType(node);
this._reportDeprecatedUse(node, type);
return true;
}
@ -1392,7 +1392,9 @@ export class Checker extends ParseTreeWalker {
}
override visitMemberAccess(node: MemberAccessNode) {
this._evaluator.getType(node);
const type = this._evaluator.getType(node);
this._reportDeprecatedUse(node.memberName, type);
this._conditionallyReportPrivateUsage(node.memberName);
// Walk the leftExpression but not the memberName.
@ -1475,6 +1477,9 @@ export class Checker extends ParseTreeWalker {
break;
}
const type = this._evaluator.getType(node.alias ?? node.name);
this._reportDeprecatedUse(node.name, type);
return false;
}
@ -3562,31 +3567,109 @@ export class Checker extends ParseTreeWalker {
return false;
}
private _reportDeprecatedUse(node: NameNode) {
const deprecatedForm = deprecatedAliases.get(node.value) ?? deprecatedSpecialForms.get(node.value);
if (!deprecatedForm) {
return;
}
const type = this._evaluator.getType(node);
private _reportDeprecatedUse(node: NameNode, type: Type | undefined) {
if (!type) {
return;
}
if (!isInstantiableClass(type) || type.details.fullName !== deprecatedForm.fullName) {
return;
let errorMessage: string | undefined;
let deprecatedMessage: string | undefined;
doForEachSubtype(type, (subtype) => {
if (isClass(subtype)) {
if (
!subtype.includeSubclasses &&
subtype.details.deprecatedMessage !== undefined &&
node.value === subtype.details.name
) {
deprecatedMessage = subtype.details.deprecatedMessage;
errorMessage = Localizer.Diagnostic.deprecatedClass();
}
} else if (isFunction(subtype)) {
if (subtype.details.deprecatedMessage !== undefined && node.value === subtype.details.name) {
deprecatedMessage = subtype.details.deprecatedMessage;
errorMessage = Localizer.Diagnostic.deprecatedFunction();
}
} 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.
let callTypeResult: TypeResult | undefined;
if (node.parent?.nodeType === ParseNodeType.Call && node.parent.leftExpression === node) {
callTypeResult = this._evaluator.getTypeResult(node.parent);
} else if (
node.parent?.nodeType === ParseNodeType.MemberAccess &&
node.parent.memberName === node &&
node.parent.parent?.nodeType === ParseNodeType.Call &&
node.parent.parent.leftExpression === node.parent
) {
callTypeResult = this._evaluator.getTypeResult(node.parent.parent);
}
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();
}
});
}
}
});
if (errorMessage) {
const diag = new DiagnosticAddendum();
if (deprecatedMessage) {
diag.addMessage(deprecatedMessage);
}
if (this._fileInfo.diagnosticRuleSet.reportDeprecated === 'none') {
this._evaluator.addDeprecated(errorMessage + diag.getString(), node);
} else {
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportDeprecated,
DiagnosticRule.reportDeprecated,
errorMessage + diag.getString(),
node
);
}
}
if (this._fileInfo.executionEnvironment.pythonVersion >= deprecatedForm.version) {
this._evaluator.addDeprecated(
Localizer.Diagnostic.deprecatedType().format({
version: versionToString(deprecatedForm.version),
replacement: deprecatedForm.replacementText,
}),
node
);
// We'll leave this disabled for now because this would be too noisy for most
// code bases. We may want to add it at some future date.
if (0) {
const deprecatedForm = deprecatedAliases.get(node.value) ?? deprecatedSpecialForms.get(node.value);
if (deprecatedForm) {
if (isInstantiableClass(type) && type.details.fullName === deprecatedForm.fullName) {
if (this._fileInfo.executionEnvironment.pythonVersion >= deprecatedForm.version) {
if (this._fileInfo.diagnosticRuleSet.reportDeprecated === 'none') {
this._evaluator.addDeprecated(
Localizer.Diagnostic.deprecatedType().format({
version: versionToString(deprecatedForm.version),
replacement: deprecatedForm.replacementText,
}),
node
);
} else {
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportDeprecated,
DiagnosticRule.reportDeprecated,
Localizer.Diagnostic.deprecatedType().format({
version: versionToString(deprecatedForm.version),
replacement: deprecatedForm.replacementText,
}),
node
);
}
}
}
}
}
}

View File

@ -7287,6 +7287,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
if (callResult.argumentErrors) {
typeResult.typeErrors = true;
} else {
typeResult.overloadsUsedForCall = callResult.overloadsUsedForCall;
}
if (callResult.isTypeIncomplete) {
@ -7698,6 +7700,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
typeVarContext: TypeVarContext;
}[] = [];
let isTypeIncomplete = false;
const overloadsUsedForCall: FunctionType[] = [];
for (let expandedTypesIndex = 0; expandedTypesIndex < expandedArgTypes.length; expandedTypesIndex++) {
let matchedOverload: FunctionType | undefined;
@ -7751,6 +7754,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
if (!callResult.argumentErrors && callResult.returnType) {
overloadsUsedForCall.push(overload);
matchedOverload = overload;
matchedOverloads.push({
overload: matchedOverload,
@ -7804,7 +7809,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
if (!matchedOverload) {
return { argumentErrors: true, isTypeIncomplete };
return { argumentErrors: true, isTypeIncomplete, overloadsUsedForCall };
}
}
@ -7839,6 +7844,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
returnType: combineTypes(returnTypes),
isTypeIncomplete,
specializedInitSelfType: finalCallResult.specializedInitSelfType,
overloadsUsedForCall,
};
}
@ -7952,7 +7958,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
);
}
return { argumentErrors: true, isTypeIncomplete: false };
return { argumentErrors: true, isTypeIncomplete: false, overloadsUsedForCall: [] };
}
// Create a helper lambda that evaluates the overload that matches
@ -8049,7 +8055,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return { ...result, argumentErrors: true };
}
return { argumentErrors: true, isTypeIncomplete: false };
return { argumentErrors: true, isTypeIncomplete: false, overloadsUsedForCall: [] };
}
// Replaces each item in the expandedArgTypes with n items where n is
@ -8124,6 +8130,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
let reportedErrors = false;
let isTypeIncomplete = false;
let usedMetaclassCallMethod = false;
const overloadsUsedForCall: FunctionType[] = [];
// Create a helper function that determines whether we should skip argument
// validation for either __init__ or __new__. This is required for certain
@ -8166,6 +8173,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
if (expectedCallResult.isTypeIncomplete) {
isTypeIncomplete = true;
}
overloadsUsedForCall.push(...expectedCallResult.overloadsUsedForCall);
}
}
@ -8202,6 +8211,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
if (callResult.isTypeIncomplete) {
isTypeIncomplete = true;
}
overloadsUsedForCall.push(...callResult.overloadsUsedForCall);
} else {
reportedErrors = true;
}
@ -8425,7 +8436,12 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
}
const result: CallResult = { argumentErrors: reportedErrors, returnType, isTypeIncomplete };
const result: CallResult = {
argumentErrors: reportedErrors,
returnType,
isTypeIncomplete,
overloadsUsedForCall,
};
return result;
}
@ -8443,6 +8459,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
): CallResult | undefined {
let isTypeIncomplete = false;
let argumentErrors = false;
const overloadsUsedForCall: FunctionType[] = [];
const returnType = mapSubtypes(expectedType, (expectedSubType) => {
expectedSubType = transformPossibleRecursiveTypeAlias(expectedSubType);
@ -8486,6 +8503,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
argumentErrors = true;
}
overloadsUsedForCall.push(...callResult.overloadsUsedForCall);
return applyExpectedSubtypeForConstructor(type, expectedSubType, typeVarContext);
}
}
@ -8497,7 +8516,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return undefined;
}
return { returnType, isTypeIncomplete, argumentErrors };
return { returnType, isTypeIncomplete, argumentErrors, overloadsUsedForCall };
}
function applyExpectedSubtypeForConstructor(
@ -8584,9 +8603,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
let argumentErrors = false;
let isTypeIncomplete = false;
let specializedInitSelfType: Type | undefined;
const overloadsUsedForCall: FunctionType[] = [];
if (recursionCount > maxTypeRecursionCount) {
return { returnType: UnknownType.create(), argumentErrors: true };
return { returnType: UnknownType.create(), argumentErrors: true, overloadsUsedForCall };
}
recursionCount++;
@ -8601,7 +8621,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}),
exprNode
);
return { returnType: UnknownType.create(), argumentErrors: true };
return { returnType: UnknownType.create(), argumentErrors: true, overloadsUsedForCall };
}
const returnType = mapSubtypesExpandTypeVars(
@ -8675,6 +8695,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
isTypeIncomplete = true;
}
overloadsUsedForCall.push(...functionResult.overloadsUsedForCall);
if (functionResult.argumentErrors) {
argumentErrors = true;
} else {
@ -8747,6 +8769,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
expectedType
);
overloadsUsedForCall.push(...functionResult.overloadsUsedForCall);
if (functionResult.isTypeIncomplete) {
isTypeIncomplete = true;
}
@ -8953,6 +8977,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
expectedType
);
overloadsUsedForCall.push(...constructorResult.overloadsUsedForCall);
if (constructorResult.argumentErrors) {
argumentErrors = true;
}
@ -9023,6 +9049,9 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
expectedType,
recursionCount
);
overloadsUsedForCall.push(...functionResult.overloadsUsedForCall);
if (functionResult.argumentErrors) {
argumentErrors = true;
}
@ -9082,6 +9111,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
recursionCount
);
overloadsUsedForCall.push(...callResult.overloadsUsedForCall);
if (callResult.argumentErrors) {
argumentErrors = true;
}
@ -9109,6 +9140,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
returnType: isNever(returnType) && !returnType.isNoReturn ? undefined : returnType,
isTypeIncomplete,
specializedInitSelfType,
overloadsUsedForCall,
};
}
@ -10507,6 +10539,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
isTypeIncomplete,
activeParam: matchResults.activeParam,
specializedInitSelfType,
overloadsUsedForCall: argumentErrors ? [] : [type],
};
}
@ -10551,6 +10584,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return {
argumentErrors: true,
activeParam: matchResults.activeParam,
overloadsUsedForCall: [],
};
}
@ -16176,6 +16210,16 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
);
}
}
if (isOverloadedFunction(decoratorCallType)) {
if (
decoratorCallType.overloads.length > 0 &&
decoratorCallType.overloads[0].details.builtInName === 'deprecated'
) {
originalClassType.details.deprecatedMessage = getCustomDeprecationMessage(decoratorNode);
return inputClassType;
}
}
}
if (isOverloadedFunction(decoratorType)) {
@ -16190,6 +16234,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
);
return inputClassType;
}
if (decoratorType.overloads.length > 0 && decoratorType.overloads[0].details.builtInName === 'deprecated') {
originalClassType.details.deprecatedMessage = getCustomDeprecationMessage(decoratorNode);
return inputClassType;
}
} else if (isFunction(decoratorType)) {
if (decoratorType.details.builtInName === 'final') {
originalClassType.details.flags |= ClassTypeFlags.Final;
@ -16198,7 +16247,9 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
// behavior because its function definition results in a cyclical
// dependency between builtins, typing and _typeshed stubs.
return inputClassType;
} else if (decoratorType.details.builtInName === 'runtime_checkable') {
}
if (decoratorType.details.builtInName === 'runtime_checkable') {
originalClassType.details.flags |= ClassTypeFlags.RuntimeCheckable;
// Don't call getTypeOfDecorator for runtime_checkable. It appears
@ -16238,6 +16289,22 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return getTypeOfDecorator(decoratorNode, inputClassType);
}
// Given a @typing.deprecated decorator node, returns either '' or a custom
// deprecation message if one is provided.
function getCustomDeprecationMessage(decorator: DecoratorNode): string {
if (
decorator.expression.nodeType === ParseNodeType.Call &&
decorator.expression.arguments.length > 0 &&
decorator.expression.arguments[0].argumentCategory === ArgumentCategory.Simple &&
decorator.expression.arguments[0].valueExpression.nodeType === ParseNodeType.StringList &&
decorator.expression.arguments[0].valueExpression.strings.length === 1
) {
return decorator.expression.arguments[0].valueExpression.strings[0].value;
}
return '';
}
// Runs any registered "callback hooks" that depend on the specified class type.
// This allows us to complete any work that requires dependent classes to be
// completed.
@ -17203,6 +17270,16 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return inputFunctionType;
}
}
if (isOverloadedFunction(decoratorCallType)) {
if (
decoratorCallType.overloads.length > 0 &&
decoratorCallType.overloads[0].details.builtInName === 'deprecated'
) {
undecoratedType.details.deprecatedMessage = getCustomDeprecationMessage(decoratorNode);
return inputFunctionType;
}
}
}
let returnType = getTypeOfDecorator(decoratorNode, inputFunctionType);
@ -17249,6 +17326,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
}
}
} else if (isOverloadedFunction(decoratorType)) {
if (decoratorType.overloads.length > 0 && decoratorType.overloads[0].details.builtInName === 'deprecated') {
undecoratedType.details.deprecatedMessage = getCustomDeprecationMessage(decoratorNode);
return inputFunctionType;
}
} else if (isInstantiableClass(decoratorType)) {
if (ClassType.isBuiltIn(decoratorType)) {
switch (decoratorType.details.name) {
@ -18695,17 +18777,13 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
// don't bother doing additional work.
let cacheEntry = readTypeCacheEntry(subnode);
if (cacheEntry && !cacheEntry.typeResult.isIncomplete) {
return { type: cacheEntry.typeResult.type };
return cacheEntry.typeResult;
}
callback();
cacheEntry = readTypeCacheEntry(subnode);
if (cacheEntry) {
return {
type: cacheEntry.typeResult.type,
isIncomplete: cacheEntry.typeResult.isIncomplete,
expectedTypeDiagAddendum: cacheEntry.typeResult.expectedTypeDiagAddendum,
};
return cacheEntry.typeResult;
}
return undefined;

View File

@ -182,6 +182,9 @@ export interface TypeResult {
// Is the type wrapped in a "Required" or "NotRequired" class?
isRequired?: boolean;
isNotRequired?: boolean;
// If a call expression, which overloads were used to satisfy it?
overloadsUsedForCall?: FunctionType[];
}
export interface TypeResultWithNode extends TypeResult {
@ -319,6 +322,11 @@ export interface CallResult {
// is used for overloaded constructors where the arguments to the
// constructor influence the specialized type of the constructed object.
specializedInitSelfType?: Type | undefined;
// The overload or overloads used to satisfy the call. There can
// be multiple overloads in the case where the call type is a union
// or we have used union expansion for arguments.
overloadsUsedForCall: FunctionType[];
}
export interface PrintTypeOptions {

View File

@ -497,6 +497,7 @@ interface ClassDetails {
typeParameters: TypeVarType[];
typeVarScopeId?: TypeVarScopeId | undefined;
docString?: string | undefined;
deprecatedMessage?: string | undefined;
dataClassEntries?: DataClassEntry[] | undefined;
dataClassBehaviors?: DataClassBehaviors | undefined;
typedDictEntries?: Map<string, TypedDictEntry> | undefined;
@ -1212,6 +1213,7 @@ interface FunctionDetails {
constructorTypeVarScopeId?: TypeVarScopeId | undefined;
builtInName?: string | undefined;
docString?: string | undefined;
deprecatedMessage?: string | undefined;
// Transforms to apply if this function is used
// as a decorator.

View File

@ -187,6 +187,9 @@ export interface DiagnosticRuleSet {
// Report attempts to redefine variables that are in all-caps.
reportConstantRedefinition: DiagnosticLevel;
// Report use of deprecated classes or functions.
reportDeprecated: DiagnosticLevel;
// Report usage of method override that is incompatible with
// the base class method of the same name?
reportIncompatibleMethodOverride: DiagnosticLevel;
@ -361,6 +364,7 @@ export function getDiagLevelDiagnosticRules() {
DiagnosticRule.reportTypeCommentUsage,
DiagnosticRule.reportPrivateImportUsage,
DiagnosticRule.reportConstantRedefinition,
DiagnosticRule.reportDeprecated,
DiagnosticRule.reportIncompatibleMethodOverride,
DiagnosticRule.reportIncompatibleVariableOverride,
DiagnosticRule.reportInconsistentConstructor,
@ -445,6 +449,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet {
reportTypeCommentUsage: 'none',
reportPrivateImportUsage: 'none',
reportConstantRedefinition: 'none',
reportDeprecated: 'none',
reportIncompatibleMethodOverride: 'none',
reportIncompatibleVariableOverride: 'none',
reportInconsistentConstructor: 'none',
@ -525,6 +530,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet {
reportTypeCommentUsage: 'none',
reportPrivateImportUsage: 'error',
reportConstantRedefinition: 'none',
reportDeprecated: 'none',
reportIncompatibleMethodOverride: 'none',
reportIncompatibleVariableOverride: 'none',
reportInconsistentConstructor: 'none',
@ -605,6 +611,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet {
reportTypeCommentUsage: 'error',
reportPrivateImportUsage: 'error',
reportConstantRedefinition: 'error',
reportDeprecated: 'error',
reportIncompatibleMethodOverride: 'error',
reportIncompatibleVariableOverride: 'error',
reportInconsistentConstructor: 'error',

View File

@ -46,6 +46,7 @@ export enum DiagnosticRule {
reportTypeCommentUsage = 'reportTypeCommentUsage',
reportPrivateImportUsage = 'reportPrivateImportUsage',
reportConstantRedefinition = 'reportConstantRedefinition',
reportDeprecated = 'reportDeprecated',
reportIncompatibleMethodOverride = 'reportIncompatibleMethodOverride',
reportIncompatibleVariableOverride = 'reportIncompatibleVariableOverride',
reportInconsistentConstructor = 'reportInconsistentConstructor',

View File

@ -329,6 +329,8 @@ 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 deprecatedType = () =>
new ParameterizedString<{ version: string; replacement: string }>(
getRawString('Diagnostic.deprecatedType')

View File

@ -89,6 +89,8 @@
"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",
"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

@ -492,3 +492,25 @@ test('RegionComments1', () => {
// const analysisResults3 = TestUtils.typeAnalyzeSampleFiles(['deprecated1.py'], configOptions);
// TestUtils.validateResults(analysisResults3, 0, 0, 0, 0, 13);
// });
test('Deprecated2', () => {
const configOptions = new ConfigOptions('.');
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated2.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 5);
configOptions.diagnosticRuleSet.reportDeprecated = 'error';
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['deprecated2.py'], configOptions);
TestUtils.validateResults(analysisResults2, 5);
});
test('Deprecated3', () => {
const configOptions = new ConfigOptions('.');
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated3.py'], configOptions);
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 5);
configOptions.diagnosticRuleSet.reportDeprecated = 'error';
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['deprecated3.py'], configOptions);
TestUtils.validateResults(analysisResults2, 5);
});

View File

@ -0,0 +1,71 @@
# This sample tests the @typing.deprecated decorator introduced in PEP 702.
from typing_extensions import deprecated, overload
@deprecated("Use ClassB instead")
class ClassA:
...
# This should generate an error if reportDeprecated is enabled.
ClassA()
class ClassC:
@deprecated("Don't temp me")
def method1(self) -> None:
...
@overload
@deprecated("Int is no longer supported")
def method2(self, a: int) -> None:
...
@overload
def method2(self, a: None = None) -> None:
...
def method2(self, a: int | None = None) -> None:
...
c1 = ClassC()
# This should generate an error if reportDeprecated is enabled.
c1.method1()
c1.method2()
# This should generate an error if reportDeprecated is enabled.
c1.method2(2)
@deprecated("Test")
def func1() -> None:
...
# This should generate an error if reportDeprecated is enabled.
func1()
@overload
def func2(a: str) -> None:
...
@overload
@deprecated("int no longer supported")
def func2(a: int) -> int:
...
def func2(a: str | int) -> int | None:
...
func2("hi")
# This should generate an error if reportDeprecated is enabled.
func2(3)

View File

@ -0,0 +1,25 @@
# This sample tests the @typing.deprecated decorator introduced in PEP 702.
# This should generate an error if reportDeprecated is enabled.
from .deprecated2 import func1
# This should generate an error if reportDeprecated is enabled.
from .deprecated2 import ClassA as A
from .deprecated2 import func2
from .deprecated2 import ClassC as C
func2("hi")
# This should generate an error if reportDeprecated is enabled.
func2(1)
# This should generate an error if reportDeprecated is enabled.
c1 = C.method1
c2 = C()
c2.method2()
# This should generate an error if reportDeprecated is enabled.
c2.method2(3)

View File

@ -313,3 +313,9 @@ def override(__arg: _F) -> _F: ...
# Proposed extension to PEP 647
StrictTypeGuard: _SpecialForm = ...
# Proposed PEP 702
@overload
def deprecated(__f: _F) -> _F: ...
@overload
def deprecated(__msg: str) -> Callable[[_F], _F]: ...

View File

@ -36,6 +36,7 @@ Pyright supports [configuration files](/docs/configuration.md) that provide gran
* [PEP 695](https://www.python.org/dev/peps/pep-0695/) (draft) Type parameter syntax
* [PEP 696](https://www.python.org/dev/peps/pep-0696/) (draft) Type defaults for TypeVarLikes
* [PEP 698](https://www.python.org/dev/peps/pep-0698/) (draft) Override decorator for static typing
* [PEP 702](https://www.python.org/dev/peps/pep-0702/) (draft) Marking deprecations
* Type inference for function return values, instance variables, class variables, and globals
* Type guards that understand conditional code flow constructs like if/else statements

View File

@ -473,6 +473,17 @@
"error"
]
},
"reportDeprecated": {
"type": "string",
"description": "Diagnostics for use of deprecated classes or functions.",
"default": "none",
"enum": [
"none",
"information",
"warning",
"error"
]
},
"reportIncompatibleMethodOverride": {
"type": "string",
"description": "Diagnostics for methods that override a method of the same name in a base class in an incompatible manner (wrong number of parameters, incompatible parameter types, or incompatible return type).",

View File

@ -316,6 +316,12 @@
"title": "Controls reporting of attempts to redefine variables that are in all-caps",
"default": "none"
},
"reportDeprecated": {
"$id": "#/properties/reportDeprecated",
"$ref": "#/definitions/diagnostic",
"title": "Controls reporting of use of deprecated class or function",
"default": "none"
},
"reportIncompatibleMethodOverride": {
"$id": "#/properties/reportIncompatibleMethodOverride",
"$ref": "#/definitions/diagnostic",