mirror of
https://github.com/microsoft/pyright.git
synced 2024-10-26 10:55:06 +03:00
Added support for deprecated
objects that are instantiated prior to being used as a decorator. This allows for a factory usage pattern. This addresses #5953.
This commit is contained in:
parent
6baa5b1368
commit
63e0876cff
@ -34,7 +34,7 @@ import {
|
||||
validatePropertyMethod,
|
||||
} from './properties';
|
||||
import { EvaluatorFlags, FunctionArgument, TypeEvaluator } from './typeEvaluatorTypes';
|
||||
import { isBuiltInDeprecatedType, isPartlyUnknown, isProperty } from './typeUtils';
|
||||
import { isPartlyUnknown, isProperty } from './typeUtils';
|
||||
import {
|
||||
ClassType,
|
||||
ClassTypeFlags,
|
||||
@ -43,7 +43,9 @@ import {
|
||||
FunctionTypeFlags,
|
||||
OverloadedFunctionType,
|
||||
Type,
|
||||
TypeBase,
|
||||
UnknownType,
|
||||
isClass,
|
||||
isClassInstance,
|
||||
isFunction,
|
||||
isInstantiableClass,
|
||||
@ -86,17 +88,6 @@ export function getFunctionInfoFromDecorators(
|
||||
let evaluatorFlags = fileInfo.isStubFile ? EvaluatorFlags.AllowForwardReferences : EvaluatorFlags.None;
|
||||
if (decoratorNode.expression.nodeType !== ParseNodeType.Call) {
|
||||
evaluatorFlags |= EvaluatorFlags.CallBaseDefaults;
|
||||
} else {
|
||||
if (decoratorNode.expression.nodeType === ParseNodeType.Call) {
|
||||
const decoratorCallType = evaluator.getTypeOfExpression(
|
||||
decoratorNode.expression.leftExpression,
|
||||
evaluatorFlags | EvaluatorFlags.CallBaseDefaults
|
||||
).type;
|
||||
|
||||
if (isBuiltInDeprecatedType(decoratorCallType)) {
|
||||
deprecationMessage = getCustomDeprecationMessage(decoratorNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const decoratorTypeResult = evaluator.getTypeOfExpression(decoratorNode.expression, evaluatorFlags);
|
||||
@ -118,7 +109,8 @@ export function getFunctionInfoFromDecorators(
|
||||
} else if (decoratorType.details.builtInName === 'overload') {
|
||||
flags |= FunctionTypeFlags.Overloaded;
|
||||
}
|
||||
} else if (isInstantiableClass(decoratorType)) {
|
||||
} else if (isClass(decoratorType)) {
|
||||
if (TypeBase.isInstantiable(decoratorType)) {
|
||||
if (ClassType.isBuiltIn(decoratorType, 'staticmethod')) {
|
||||
if (isInClass) {
|
||||
flags |= FunctionTypeFlags.StaticMethod;
|
||||
@ -128,10 +120,11 @@ export function getFunctionInfoFromDecorators(
|
||||
flags |= FunctionTypeFlags.ClassMethod;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ClassType.isBuiltIn(decoratorType, 'deprecated')) {
|
||||
deprecationMessage = decoratorType.deprecatedInstanceMessage;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBuiltInDeprecatedType(decoratorType)) {
|
||||
deprecationMessage = getCustomDeprecationMessage(decoratorNode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,10 +182,6 @@ export function applyFunctionDecorator(
|
||||
return inputFunctionType;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBuiltInDeprecatedType(decoratorCallType)) {
|
||||
return inputFunctionType;
|
||||
}
|
||||
}
|
||||
|
||||
let returnType = getTypeOfDecorator(evaluator, decoratorNode, inputFunctionType);
|
||||
@ -260,12 +249,12 @@ export function applyFunctionDecorator(
|
||||
|
||||
return inputFunctionType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isBuiltInDeprecatedType(decoratorType)) {
|
||||
case 'decorator': {
|
||||
return inputFunctionType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle properties and subclasses of properties specially.
|
||||
if (ClassType.isPropertyClass(decoratorType)) {
|
||||
@ -332,11 +321,6 @@ export function applyClassDecorator(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBuiltInDeprecatedType(decoratorCallType)) {
|
||||
originalClassType.details.deprecatedMessage = getCustomDeprecationMessage(decoratorNode);
|
||||
return inputClassType;
|
||||
}
|
||||
}
|
||||
|
||||
if (isOverloadedFunction(decoratorType)) {
|
||||
@ -395,6 +379,11 @@ export function applyClassDecorator(
|
||||
applyDataClassDecorator(evaluator, decoratorNode, originalClassType, dataclassBehaviors, callNode);
|
||||
return inputClassType;
|
||||
}
|
||||
} else if (isClassInstance(decoratorType)) {
|
||||
if (ClassType.isBuiltIn(decoratorType, 'deprecated')) {
|
||||
originalClassType.details.deprecatedMessage = decoratorType.deprecatedInstanceMessage;
|
||||
return inputClassType;
|
||||
}
|
||||
}
|
||||
|
||||
return getTypeOfDecorator(evaluator, decoratorNode, inputClassType);
|
||||
@ -588,16 +577,15 @@ export function addOverloadsToFunctionType(evaluator: TypeEvaluator, node: Funct
|
||||
return type;
|
||||
}
|
||||
|
||||
// Given a @typing.deprecated decorator node, returns either '' or a custom
|
||||
// Given a @typing.deprecated call node, returns either '' or a custom
|
||||
// deprecation message if one is provided.
|
||||
function getCustomDeprecationMessage(decorator: DecoratorNode): string {
|
||||
export function getDeprecatedMessageFromCall(node: CallNode): 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
|
||||
node.arguments.length > 0 &&
|
||||
node.arguments[0].argumentCategory === ArgumentCategory.Simple &&
|
||||
node.arguments[0].valueExpression.nodeType === ParseNodeType.StringList
|
||||
) {
|
||||
const stringListNode = decorator.expression.arguments[0].valueExpression;
|
||||
const stringListNode = node.arguments[0].valueExpression;
|
||||
const message = stringListNode.strings.map((s) => s.value).join('');
|
||||
return convertDocStringToPlainText(message);
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ import {
|
||||
addOverloadsToFunctionType,
|
||||
applyClassDecorator,
|
||||
applyFunctionDecorator,
|
||||
getDeprecatedMessageFromCall,
|
||||
getFunctionInfoFromDecorators,
|
||||
} from './decorators';
|
||||
import {
|
||||
@ -9980,6 +9981,17 @@ export function createTypeEvaluator(
|
||||
returnType = convertToInstance(unexpandedCallType);
|
||||
}
|
||||
|
||||
// If we instantiated the "deprecated" class, attach the deprecation
|
||||
// message to the instance.
|
||||
if (
|
||||
errorNode.nodeType === ParseNodeType.Call &&
|
||||
returnType &&
|
||||
isClassInstance(returnType) &&
|
||||
ClassType.isBuiltIn(returnType, 'deprecated')
|
||||
) {
|
||||
returnType = ClassType.cloneForDeprecatedInstance(returnType, getDeprecatedMessageFromCall(errorNode));
|
||||
}
|
||||
|
||||
// If we instantiated a type, transform it into a class.
|
||||
// This can happen if someone directly instantiates a metaclass
|
||||
// deriving from type.
|
||||
|
@ -388,25 +388,6 @@ export function isTypeVarSame(type1: TypeVarType, type2: Type) {
|
||||
return isCompatible;
|
||||
}
|
||||
|
||||
// The `deprecated` type has been defined as a function, an overloaded function,
|
||||
// and a class in various versions of typeshed. This function checks for all of
|
||||
// these variants to determine whether the type is the built-in `deprecated` type.
|
||||
export function isBuiltInDeprecatedType(type: Type) {
|
||||
if (isFunction(type)) {
|
||||
return type.details.builtInName === 'deprecated';
|
||||
}
|
||||
|
||||
if (isOverloadedFunction(type)) {
|
||||
return type.overloads.length > 0 && type.overloads[0].details.builtInName === 'deprecated';
|
||||
}
|
||||
|
||||
if (isInstantiableClass(type)) {
|
||||
return ClassType.isBuiltIn(type, 'deprecated');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function makeInferenceContext(expectedType: undefined, isTypeIncomplete?: boolean): undefined;
|
||||
export function makeInferenceContext(expectedType: Type, isTypeIncomplete?: boolean): InferenceContext;
|
||||
export function makeInferenceContext(expectedType?: Type, isTypeIncomplete?: boolean): InferenceContext | undefined;
|
||||
|
@ -614,12 +614,16 @@ interface ClassDetails {
|
||||
typeParameters: TypeVarType[];
|
||||
typeVarScopeId?: TypeVarScopeId | undefined;
|
||||
docString?: string | undefined;
|
||||
deprecatedMessage?: string | undefined;
|
||||
dataClassEntries?: DataClassEntry[] | undefined;
|
||||
dataClassBehaviors?: DataClassBehaviors | undefined;
|
||||
typedDictEntries?: TypedDictEntries | undefined;
|
||||
localSlotsNames?: string[];
|
||||
|
||||
// If the class is decorated with a @deprecated decorator, this
|
||||
// string provides the message to be displayed when the class
|
||||
// is used.
|
||||
deprecatedMessage?: string | undefined;
|
||||
|
||||
// A cache of protocol classes (indexed by the class full name)
|
||||
// that have been determined to be compatible or incompatible
|
||||
// with this class. We use "object" here to avoid a circular dependency.
|
||||
@ -750,6 +754,11 @@ export interface ClassType extends TypeBase {
|
||||
fgetInfo?: PropertyMethodInfo | undefined;
|
||||
fsetInfo?: PropertyMethodInfo | undefined;
|
||||
fdelInfo?: PropertyMethodInfo | undefined;
|
||||
|
||||
// Provides the deprecated message specifically for instances of
|
||||
// the "deprecated" class. This allows these instances to be used
|
||||
// as decorators for other classes or functions.
|
||||
deprecatedInstanceMessage?: string | undefined;
|
||||
}
|
||||
|
||||
export namespace ClassType {
|
||||
@ -867,6 +876,12 @@ export namespace ClassType {
|
||||
return newClassType;
|
||||
}
|
||||
|
||||
export function cloneForDeprecatedInstance(type: ClassType, deprecatedMessage?: string): ClassType {
|
||||
const newClassType = TypeBase.cloneType(type);
|
||||
newClassType.deprecatedInstanceMessage = deprecatedMessage;
|
||||
return newClassType;
|
||||
}
|
||||
|
||||
export function cloneForTypingAlias(classType: ClassType, aliasName: string): ClassType {
|
||||
const newClassType = TypeBase.cloneType(classType);
|
||||
newClassType.aliasName = aliasName;
|
||||
|
@ -596,3 +596,14 @@ test('Deprecated6', () => {
|
||||
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['deprecated6.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults2, 3);
|
||||
});
|
||||
|
||||
test('Deprecated7', () => {
|
||||
const configOptions = new ConfigOptions(Uri.empty());
|
||||
|
||||
const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['deprecated7.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults1, 0, 0, 0, undefined, undefined, 2);
|
||||
|
||||
configOptions.diagnosticRuleSet.reportDeprecated = 'error';
|
||||
const analysisResults2 = TestUtils.typeAnalyzeSampleFiles(['deprecated7.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults2, 2);
|
||||
});
|
||||
|
30
packages/pyright-internal/src/tests/samples/deprecated7.py
Normal file
30
packages/pyright-internal/src/tests/samples/deprecated7.py
Normal file
@ -0,0 +1,30 @@
|
||||
# This sample tests the case where a "deprecated" instance is instantiated
|
||||
# prior to being used as a decorator.
|
||||
|
||||
# pyright: reportMissingModuleSource=false
|
||||
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
todo = deprecated("This needs to be implemented!!")
|
||||
|
||||
|
||||
@todo
|
||||
class ClassA: ...
|
||||
|
||||
|
||||
# This should generate an error if reportDeprecated is enabled.
|
||||
ClassA()
|
||||
|
||||
|
||||
@todo
|
||||
def func1() -> None:
|
||||
pass
|
||||
|
||||
|
||||
# This should generate an error if reportDeprecated is enabled.
|
||||
func1()
|
||||
|
||||
|
||||
def func2() -> None:
|
||||
pass
|
Loading…
Reference in New Issue
Block a user