mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-17 11:17:17 +03:00
Added support for Concatenate as described in latest version of PEP 612. Added ParamSpec and Concatenate to typing.pyi.
This commit is contained in:
parent
e7e1f79b7f
commit
9e1539dba9
@ -46,6 +46,12 @@ if sys.version_info >= (3, 8):
|
||||
# TypedDict is a (non-subscriptable) special form.
|
||||
TypedDict: object
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
class ParamSpec:
|
||||
__name__: str
|
||||
def __init__(self, name: str) -> None: ...
|
||||
Concatenate: _SpecialForm = ...
|
||||
|
||||
if sys.version_info < (3, 7):
|
||||
class GenericMeta(type): ...
|
||||
|
||||
|
@ -2573,6 +2573,7 @@ export class Binder extends ParseTreeWalker {
|
||||
Annotated: true,
|
||||
TypeAlias: true,
|
||||
OrderedDict: true,
|
||||
Concatenate: true,
|
||||
};
|
||||
|
||||
const assignedName = assignedNameNode.value;
|
||||
|
@ -141,6 +141,7 @@ import {
|
||||
NoneType,
|
||||
ObjectType,
|
||||
OverloadedFunctionType,
|
||||
ParamSpecEntry,
|
||||
removeNoneFromUnion,
|
||||
removeUnboundFromUnion,
|
||||
Type,
|
||||
@ -4352,6 +4353,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
className === 'Protocol' ||
|
||||
className === 'Generic' ||
|
||||
className === 'Callable' ||
|
||||
className === 'Concatenate' ||
|
||||
className === 'Type'
|
||||
) {
|
||||
const fileInfo = getFileInfo(errorNode);
|
||||
@ -5345,7 +5347,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
requiresTypeVarMatching: requiresSpecialization(paramType),
|
||||
argument: funcArg,
|
||||
errorNode: argList[argIndex].valueExpression || errorNode,
|
||||
paramName: paramName,
|
||||
paramName: typeParams[paramIndex].isNameSynthesized ? undefined : paramName,
|
||||
});
|
||||
|
||||
trySetActive(argList[argIndex], typeParams[paramIndex]);
|
||||
@ -5380,7 +5382,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
requiresTypeVarMatching: requiresSpecialization(paramType),
|
||||
argument: argList[argIndex],
|
||||
errorNode: argList[argIndex].valueExpression || errorNode,
|
||||
paramName: paramName,
|
||||
paramName: typeParams[paramIndex].isNameSynthesized ? undefined : paramName,
|
||||
});
|
||||
trySetActive(argList[argIndex], typeParams[paramIndex]);
|
||||
|
||||
@ -5505,7 +5507,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
type: param.defaultType,
|
||||
},
|
||||
errorNode: errorNode,
|
||||
paramName: param.name,
|
||||
paramName: param.isNameSynthesized ? undefined : param.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -7548,10 +7550,30 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
} else if (isEllipsisType(typeArgs[0].type)) {
|
||||
FunctionType.addDefaultParameters(functionType);
|
||||
} else if (isParamSpecType(typeArgs[0].type)) {
|
||||
FunctionType.addDefaultParameters(functionType);
|
||||
functionType.details.paramSpec = typeArgs[0].type as TypeVarType;
|
||||
} else {
|
||||
addError(Localizer.Diagnostic.callableFirstArg(), typeArgs[0].node);
|
||||
if (isClass(typeArgs[0].type) && ClassType.isBuiltIn(typeArgs[0].type, 'Concatenate')) {
|
||||
const concatTypeArgs = typeArgs[0].type.typeArguments;
|
||||
if (concatTypeArgs && concatTypeArgs.length > 0) {
|
||||
concatTypeArgs.forEach((typeArg, index) => {
|
||||
if (index === concatTypeArgs.length - 1) {
|
||||
if (isParamSpecType(typeArg)) {
|
||||
functionType.details.paramSpec = typeArg as TypeVarType;
|
||||
}
|
||||
} else {
|
||||
FunctionType.addParameter(functionType, {
|
||||
category: ParameterCategory.Simple,
|
||||
name: `__p${index}`,
|
||||
isNameSynthesized: true,
|
||||
hasDeclaredType: true,
|
||||
type: typeArg,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
addError(Localizer.Diagnostic.callableFirstArg(), typeArgs[0].node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FunctionType.addDefaultParameters(functionType, /* useUnknown */ true);
|
||||
@ -7737,6 +7759,30 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
return typeArgs[0].type;
|
||||
}
|
||||
|
||||
function createConcatenateType(
|
||||
errorNode: ParseNode,
|
||||
classType: ClassType,
|
||||
typeArgs: TypeResult[] | undefined
|
||||
): Type {
|
||||
if (!typeArgs || typeArgs.length === 0) {
|
||||
addError(Localizer.Diagnostic.concatenateTypeArgsMissing(), errorNode);
|
||||
} else {
|
||||
typeArgs.forEach((typeArg, index) => {
|
||||
if (index === typeArgs.length - 1) {
|
||||
if (!isParamSpecType(typeArg.type)) {
|
||||
addError(Localizer.Diagnostic.concatenateParamSpecMissing(), typeArg.node);
|
||||
}
|
||||
} else {
|
||||
if (isParamSpecType(typeArg.type)) {
|
||||
addError(Localizer.Diagnostic.paramSpecContext(), typeArg.node);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return createSpecialType(classType, typeArgs, /* paramLimit */ undefined, /* allowParamSpec */ true);
|
||||
}
|
||||
|
||||
function createAnnotatedType(errorNode: ParseNode, typeArgs: TypeResult[] | undefined): Type {
|
||||
if (!typeArgs || typeArgs.length < 1) {
|
||||
addError(Localizer.Diagnostic.annotatedTypeArgMissing(), errorNode);
|
||||
@ -7996,6 +8042,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
Optional: { alias: '', module: 'builtins' },
|
||||
Annotated: { alias: '', module: 'builtins' },
|
||||
TypeAlias: { alias: '', module: 'builtins' },
|
||||
Concatenate: { alias: '', module: 'builtins' },
|
||||
};
|
||||
|
||||
const aliasMapEntry = specialTypes[assignedName];
|
||||
@ -11465,6 +11512,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
case 'Annotated': {
|
||||
return createAnnotatedType(errorNode, typeArgs);
|
||||
}
|
||||
|
||||
case 'Concatenate': {
|
||||
return createConcatenateType(errorNode, classType, typeArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13875,13 +13926,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
const nonDefaultSrcParamCount = srcParams.filter((param) => !!param.name && !param.hasDefault).length;
|
||||
|
||||
if (destParamCount < nonDefaultSrcParamCount) {
|
||||
diag.addMessage(
|
||||
Localizer.DiagnosticAddendum.functionTooFewParams().format({
|
||||
expected: nonDefaultSrcParamCount,
|
||||
received: destParamCount,
|
||||
})
|
||||
);
|
||||
canAssign = false;
|
||||
if (!destType.details.paramSpec) {
|
||||
diag.addMessage(
|
||||
Localizer.DiagnosticAddendum.functionTooFewParams().format({
|
||||
expected: nonDefaultSrcParamCount,
|
||||
received: destParamCount,
|
||||
})
|
||||
);
|
||||
canAssign = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (destParamCount > srcParamCount) {
|
||||
@ -13922,7 +13975,18 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
|
||||
// Are we assigning to a function with a ParamSpec?
|
||||
if (destType.details.paramSpec && typeVarMap && !typeVarMap.isLocked()) {
|
||||
typeVarMap.setParamSpec(destType.details.paramSpec.name, srcType);
|
||||
typeVarMap.setParamSpec(
|
||||
destType.details.paramSpec.name,
|
||||
srcType.details.parameters
|
||||
.map((p, index) => {
|
||||
const paramSpecEntry: ParamSpecEntry = {
|
||||
name: p.name || `__p${index}`,
|
||||
type: p.type,
|
||||
};
|
||||
return paramSpecEntry;
|
||||
})
|
||||
.slice(destType.details.parameters.length, srcType.details.parameters.length)
|
||||
);
|
||||
}
|
||||
|
||||
return canAssign;
|
||||
@ -14638,6 +14702,13 @@ export function createTypeEvaluator(importLookup: ImportLookup, printTypeFlags:
|
||||
// Callable notation.
|
||||
const parts = printFunctionParts(type, recursionCount);
|
||||
if (type.details.paramSpec) {
|
||||
if (type.details.parameters.length > 0) {
|
||||
// Remove the args and kwargs parameters from the end.
|
||||
const paramTypes = type.details.parameters.map((param) => printType(param.type));
|
||||
return `Callable[Concatenate[${paramTypes.join(', ')}, ${type.details.paramSpec.name}], ${
|
||||
parts[1]
|
||||
}]`;
|
||||
}
|
||||
return `Callable[${type.details.paramSpec.name}, ${parts[1]}]`;
|
||||
}
|
||||
return `(${parts[0].join(', ')}) -> ${parts[1]}`;
|
||||
|
@ -1427,8 +1427,16 @@ function _specializeFunctionType(
|
||||
|
||||
// Handle functions with a parameter specification in a special manner.
|
||||
if (functionType.details.paramSpec) {
|
||||
const paramSpec = typeVarMap?.getParamSpec(functionType.details.paramSpec.name);
|
||||
functionType = FunctionType.cloneForParamSpec(functionType, paramSpec);
|
||||
let paramSpec = typeVarMap?.getParamSpec(functionType.details.paramSpec.name);
|
||||
if (!paramSpec && makeConcrete) {
|
||||
paramSpec = [
|
||||
{ name: 'args', type: AnyType.create() },
|
||||
{ name: 'kwargs', type: AnyType.create() },
|
||||
];
|
||||
}
|
||||
if (paramSpec) {
|
||||
functionType = FunctionType.cloneForParamSpec(functionType, paramSpec);
|
||||
}
|
||||
}
|
||||
|
||||
const declaredReturnType =
|
||||
|
@ -10,17 +10,17 @@
|
||||
*/
|
||||
|
||||
import { assert } from '../common/debug';
|
||||
import { ClassType, FunctionType, maxTypeRecursionCount, Type, TypeCategory } from './types';
|
||||
import { ClassType, maxTypeRecursionCount, ParamSpecEntry, Type, TypeCategory } from './types';
|
||||
|
||||
export class TypeVarMap {
|
||||
private _typeVarMap: Map<string, Type>;
|
||||
private _paramSpecMap: Map<string, FunctionType>;
|
||||
private _paramSpecMap: Map<string, ParamSpecEntry[]>;
|
||||
private _isNarrowableMap: Map<string, boolean>;
|
||||
private _isLocked = false;
|
||||
|
||||
constructor() {
|
||||
this._typeVarMap = new Map<string, Type>();
|
||||
this._paramSpecMap = new Map<string, FunctionType>();
|
||||
this._paramSpecMap = new Map<string, ParamSpecEntry[]>();
|
||||
this._isNarrowableMap = new Map<string, boolean>();
|
||||
}
|
||||
|
||||
@ -64,11 +64,7 @@ export class TypeVarMap {
|
||||
score += this._getComplexityScoreForType(value);
|
||||
});
|
||||
|
||||
// Do the same for the param spec map.
|
||||
this._paramSpecMap.forEach((value) => {
|
||||
score += 1;
|
||||
score += this._getComplexityScoreForType(value);
|
||||
});
|
||||
score += this._paramSpecMap.size;
|
||||
|
||||
return score;
|
||||
}
|
||||
@ -91,11 +87,11 @@ export class TypeVarMap {
|
||||
return this._paramSpecMap.has(name);
|
||||
}
|
||||
|
||||
getParamSpec(name: string): FunctionType | undefined {
|
||||
getParamSpec(name: string): ParamSpecEntry[] | undefined {
|
||||
return this._paramSpecMap.get(name);
|
||||
}
|
||||
|
||||
setParamSpec(name: string, type: FunctionType) {
|
||||
setParamSpec(name: string, type: ParamSpecEntry[]) {
|
||||
assert(!this._isLocked);
|
||||
this._paramSpecMap.set(name, type);
|
||||
}
|
||||
|
@ -788,6 +788,11 @@ export interface FunctionType extends TypeBase {
|
||||
inferredReturnType?: Type;
|
||||
}
|
||||
|
||||
export interface ParamSpecEntry {
|
||||
name: string;
|
||||
type: Type;
|
||||
}
|
||||
|
||||
export namespace FunctionType {
|
||||
export function createInstance(
|
||||
name: string,
|
||||
@ -918,9 +923,8 @@ export namespace FunctionType {
|
||||
return newFunction;
|
||||
}
|
||||
|
||||
// Creates a new function based on the parameters of another function. If
|
||||
// paramTemplate is undefined, use default (generic) parameters.
|
||||
export function cloneForParamSpec(type: FunctionType, paramTemplate: FunctionType | undefined) {
|
||||
// Creates a new function based on the parameters of another function.
|
||||
export function cloneForParamSpec(type: FunctionType, paramTypes: ParamSpecEntry[] | undefined) {
|
||||
const newFunction = create(
|
||||
type.details.name,
|
||||
type.details.moduleName,
|
||||
@ -936,10 +940,16 @@ export namespace FunctionType {
|
||||
// since we're replacing it.
|
||||
delete newFunction.details.paramSpec;
|
||||
|
||||
if (paramTemplate) {
|
||||
newFunction.details.parameters = paramTemplate.details.parameters;
|
||||
} else {
|
||||
FunctionType.addDefaultParameters(newFunction);
|
||||
if (paramTypes) {
|
||||
newFunction.details.parameters = paramTypes.map((specEntry, index) => {
|
||||
return {
|
||||
category: ParameterCategory.Simple,
|
||||
name: specEntry.name,
|
||||
isNameSynthesized: true,
|
||||
hasDeclaredType: true,
|
||||
type: specEntry.type,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return newFunction;
|
||||
|
@ -214,6 +214,8 @@ export namespace Localizer {
|
||||
export const classVarTooManyArgs = () => getRawString('Diagnostic.classVarTooManyArgs');
|
||||
export const comprehensionInDict = () => getRawString('Diagnostic.comprehensionInDict');
|
||||
export const comprehensionInSet = () => getRawString('Diagnostic.comprehensionInSet');
|
||||
export const concatenateParamSpecMissing = () => getRawString('Diagnostic.concatenateParamSpecMissing');
|
||||
export const concatenateTypeArgsMissing = () => getRawString('Diagnostic.concatenateTypeArgsMissing');
|
||||
export const constantRedefinition = () =>
|
||||
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.constantRedefinition'));
|
||||
export const constructorNoArgs = () =>
|
||||
|
@ -42,6 +42,8 @@
|
||||
"classVarTooManyArgs": "Expected only one type argument after \"ClassVar\"",
|
||||
"comprehensionInDict": "Comprehension cannot be used with other dictionary entries",
|
||||
"comprehensionInSet": "Comprehension cannot be used with other set entries",
|
||||
"concatenateParamSpecMissing": "Last type argument for \"Concatenate\" must be a ParamSpec",
|
||||
"concatenateTypeArgsMissing": "\"Concatenate\" requires at least two type arguments",
|
||||
"constantRedefinition": "\"{name}\" is constant and cannot be redefined",
|
||||
"continueInFinally": "\"continue\" cannot be used within a finally clause",
|
||||
"continueOutsideLoop": "\"continue\" can be used only within a loop",
|
||||
|
@ -1929,9 +1929,6 @@ test('Unions2', () => {
|
||||
validateResults(analysisResults38, 0);
|
||||
});
|
||||
|
||||
// Skip ParamSpec tests until they are added back in to the
|
||||
// specification.
|
||||
/*
|
||||
test('ParamSpec1', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
|
||||
@ -1959,7 +1956,14 @@ test('ParamSpec3', () => {
|
||||
const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec3.py'], configOptions);
|
||||
validateResults(results, 1);
|
||||
});
|
||||
*/
|
||||
|
||||
test('ParamSpec4', () => {
|
||||
const configOptions = new ConfigOptions('.');
|
||||
|
||||
configOptions.defaultPythonVersion = PythonVersion.V3_10;
|
||||
const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec4.py'], configOptions);
|
||||
validateResults(results, 5);
|
||||
});
|
||||
|
||||
test('ClassVar1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['classVar1.py']);
|
||||
|
52
server/src/tests/samples/paramSpec4.py
Normal file
52
server/src/tests/samples/paramSpec4.py
Normal file
@ -0,0 +1,52 @@
|
||||
# This sample tests the type checker's handling of ParamSpec
|
||||
# and Concatenate as described in PEP 612.
|
||||
|
||||
from typing import Callable, Concatenate, ParamSpec, TypeVar
|
||||
|
||||
P = ParamSpec("P")
|
||||
R = TypeVar("R")
|
||||
|
||||
|
||||
class Request:
|
||||
...
|
||||
|
||||
|
||||
def with_request(f: Callable[Concatenate[Request, P], R]) -> Callable[P, R]:
|
||||
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
return f(Request(), *args, **kwargs)
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@with_request
|
||||
def takes_int_str(request: Request, x: int, y: str) -> int:
|
||||
# use request
|
||||
return x + 7
|
||||
|
||||
|
||||
takes_int_str(1, "A")
|
||||
|
||||
# This should generate an error because the first arg
|
||||
# is the incorrect type.
|
||||
takes_int_str("B", "A")
|
||||
|
||||
# This should generate an error because there are too
|
||||
# many parameters.
|
||||
takes_int_str(1, "A", 2)
|
||||
|
||||
# This should generate an error because a ParamSpec can appear
|
||||
# only within the last type arg for Concatenate
|
||||
def decorator1(f: Callable[Concatenate[P, P]]) -> Callable[P, R]:
|
||||
...
|
||||
|
||||
|
||||
# This should generate an error because the last type arg
|
||||
# for Concatenate should be a ParamSpec.
|
||||
def decorator2(f: Callable[Concatenate[int, int]]) -> Callable[P, R]:
|
||||
...
|
||||
|
||||
|
||||
# This should generate an error because Concatenate is missing
|
||||
# its type arguments.
|
||||
def decorator3(f: Callable[Concatenate]) -> Callable[P, R]:
|
||||
...
|
Loading…
Reference in New Issue
Block a user