From a1d1b8a1c238c9f59408f9c5ed4580f3ce04ddb0 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 8 Oct 2024 17:19:30 -0700 Subject: [PATCH] Added support for `*args: Unpack[T]` when `T` is a type variable with an upper bound of a tuple. (#9179) --- .../src/analyzer/constraintSolver.ts | 28 +++--- .../src/analyzer/patternMatching.ts | 7 +- .../pyright-internal/src/analyzer/tuples.ts | 7 +- .../src/analyzer/typeEvaluator.ts | 73 ++++++++------ .../src/analyzer/typePrinter.ts | 12 +-- .../src/analyzer/typeUtils.ts | 99 +++++++++++++++++-- .../pyright-internal/src/analyzer/types.ts | 37 +++++-- .../src/tests/samples/tupleUnpack4.py | 30 ++++++ .../src/tests/samples/typeVarTuple8.py | 25 ++--- .../src/tests/typeEvaluator6.test.ts | 2 +- .../src/tests/typeEvaluator8.test.ts | 5 + 11 files changed, 239 insertions(+), 86 deletions(-) create mode 100644 packages/pyright-internal/src/tests/samples/tupleUnpack4.py diff --git a/packages/pyright-internal/src/analyzer/constraintSolver.ts b/packages/pyright-internal/src/analyzer/constraintSolver.ts index aa38571610cc..4a7b5f0af00f 100644 --- a/packages/pyright-internal/src/analyzer/constraintSolver.ts +++ b/packages/pyright-internal/src/analyzer/constraintSolver.ts @@ -60,6 +60,7 @@ import { isEffectivelyInstantiable, isLiteralTypeOrUnion, isPartlyUnknown, + makeUnpacked, mapSubtypes, simplifyFunctionToParamSpec, sortTypes, @@ -138,17 +139,22 @@ export function assignTypeVar( isAssignable = assignParamSpec(evaluator, destType, srcType, diag, constraints, recursionCount); } else { if (isTypeVarTuple(destType) && !destType.priv.isInUnion) { - const tupleClassType = evaluator.getTupleClassType(); - if (!isUnpacked(srcType) && tupleClassType) { - // Package up the type into a tuple. - srcType = convertToInstance( - specializeTupleClass( - tupleClassType, - [{ type: srcType, isUnbounded: false }], - /* isTypeArgExplicit */ true, - /* isUnpacked */ true - ) - ); + if (destType.priv.isUnpacked) { + const tupleClassType = evaluator.getTupleClassType(); + + if (!isUnpacked(srcType) && tupleClassType) { + // Package up the type into a tuple. + srcType = convertToInstance( + specializeTupleClass( + tupleClassType, + [{ type: srcType, isUnbounded: false }], + /* isTypeArgExplicit */ true, + /* isUnpacked */ true + ) + ); + } + } else { + srcType = makeUnpacked(srcType); } } diff --git a/packages/pyright-internal/src/analyzer/patternMatching.ts b/packages/pyright-internal/src/analyzer/patternMatching.ts index 96d71dd77ba0..ccc4ebeafd5c 100644 --- a/packages/pyright-internal/src/analyzer/patternMatching.ts +++ b/packages/pyright-internal/src/analyzer/patternMatching.ts @@ -60,6 +60,7 @@ import { isTypeSame, isTypeVarTuple, isUnknown, + isUnpackedTypeVar, isUnpackedTypeVarTuple, } from './types'; import { @@ -1387,7 +1388,7 @@ function getSequencePatternInfo( ]; const tupleIndeterminateIndex = typeArgs.findIndex( - (t) => t.isUnbounded || isUnpackedTypeVarTuple(t.type) + (t) => t.isUnbounded || isUnpackedTypeVarTuple(t.type) || isUnpackedTypeVar(t.type) ); let tupleDeterminateEntryCount = typeArgs.length; @@ -1417,7 +1418,9 @@ function getSequencePatternInfo( const removedEntries = typeArgs.splice(patternStarEntryIndex, entriesToCombine); typeArgs.splice(patternStarEntryIndex, 0, { type: combineTypes(removedEntries.map((t) => t.type)), - isUnbounded: removedEntries.every((t) => t.isUnbounded || isUnpackedTypeVarTuple(t.type)), + isUnbounded: removedEntries.every( + (t) => t.isUnbounded || isUnpackedTypeVarTuple(t.type) || isUnpackedTypeVar(t.type) + ), }); } diff --git a/packages/pyright-internal/src/analyzer/tuples.ts b/packages/pyright-internal/src/analyzer/tuples.ts index 1cd2bdf7c85b..054afe41a840 100644 --- a/packages/pyright-internal/src/analyzer/tuples.ts +++ b/packages/pyright-internal/src/analyzer/tuples.ts @@ -26,6 +26,7 @@ import { isTypeVar, isTypeVarTuple, isUnion, + isUnpackedTypeVar, isUnpackedTypeVarTuple, TupleTypeArg, Type, @@ -366,9 +367,11 @@ export function adjustTupleTypeArgs( srcTypeArgs: TupleTypeArg[], flags: AssignTypeFlags ): boolean { - const destUnboundedOrVariadicIndex = destTypeArgs.findIndex((t) => t.isUnbounded || isTypeVarTuple(t.type)); + const destUnboundedOrVariadicIndex = destTypeArgs.findIndex( + (t) => t.isUnbounded || isUnpackedTypeVarTuple(t.type) || isUnpackedTypeVar(t.type) + ); const srcUnboundedIndex = srcTypeArgs.findIndex((t) => t.isUnbounded); - const srcVariadicIndex = srcTypeArgs.findIndex((t) => isTypeVarTuple(t.type)); + const srcVariadicIndex = srcTypeArgs.findIndex((t) => isUnpackedTypeVarTuple(t.type) || isUnpackedTypeVar(t.type)); if (srcUnboundedIndex >= 0) { if (isAnyOrUnknown(srcTypeArgs[srcUnboundedIndex].type)) { diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index be933cba822d..5ee54a1036e0 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -284,6 +284,7 @@ import { buildSolutionFromSpecializedClass, ClassMember, combineSameSizedTuples, + combineTupleTypeArgs, combineVariances, computeMroLinearization, containsAnyOrUnknown, @@ -337,6 +338,7 @@ import { lookUpObjectMember, makeFunctionTypeVarsBound, makeInferenceContext, + makePacked, makeTypeVarsBound, makeTypeVarsFree, mapSignatures, @@ -1831,11 +1833,7 @@ export function createTypeEvaluator( // Handle "LiteralString" specially. if (strClass && isInstantiableClass(strClass)) { let strInstance = ClassType.cloneAsInstance(strClass); - - if (subtype.props?.condition) { - strInstance = TypeBase.cloneForCondition(strInstance, getTypeCondition(subtype)); - } - + strInstance = TypeBase.cloneForCondition(strInstance, getTypeCondition(subtype)); return strInstance; } } @@ -4054,6 +4052,10 @@ export function createTypeEvaluator( }); } + if (subtype.priv.isUnpacked && isClass(boundType)) { + boundType = ClassType.cloneForUnpacked(boundType); + } + boundType = TypeBase.isInstantiable(subtype) ? convertToInstantiable(boundType) : boundType; return addConditionToType(boundType, [{ typeVar: subtype, constraintIndex: 0 }]); @@ -7973,7 +7975,7 @@ export function createTypeEvaluator( }; } else if (options?.isAnnotatedClass && argIndex > 0) { // If it's an Annotated[a, b, c], only the first index should be - // treated as a type.The others can be regular(non - type) objects. + // treated as a type. The others can be regular (non-type) objects. adjFlags = EvalFlags.NoParamSpec | EvalFlags.NoTypeVarTuple | EvalFlags.NoSpecialize | EvalFlags.NoClassVar; if (isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(node))) { @@ -8076,11 +8078,7 @@ export function createTypeEvaluator( const upperBound = type.shared.boundType; if (upperBound && isClassInstance(upperBound) && isTupleClass(upperBound)) { - const concrete = makeTopLevelTypeVarsConcrete(type); - - if (isInstantiableClass(concrete)) { - return ClassType.cloneForUnpacked(concrete); - } + return TypeVarType.cloneForUnpacked(type); } return undefined; @@ -10620,7 +10618,7 @@ export function createTypeEvaluator( const paramType = paramInfo.type; const paramName = paramInfo.param.name; - const isParamVariadic = paramInfo.param.category === ParamCategory.ArgsList && isTypeVarTuple(paramType); + const isParamVariadic = paramInfo.param.category === ParamCategory.ArgsList && isUnpacked(paramType); if (argList[argIndex].argCategory === ArgCategory.UnpackedList) { let isArgCompatibleWithVariadic = false; @@ -10786,7 +10784,7 @@ export function createTypeEvaluator( effectiveParamType = paramType.priv.tupleTypeArgs[0].type; } - paramCategory = isTypeVarTuple(effectiveParamType) ? ParamCategory.ArgsList : ParamCategory.Simple; + paramCategory = isUnpacked(effectiveParamType) ? ParamCategory.ArgsList : ParamCategory.Simple; if (remainingArgCount <= remainingParamCount) { if (remainingArgCount < remainingParamCount) { @@ -11368,7 +11366,7 @@ export function createTypeEvaluator( const paramType = paramDetails.params[paramDetails.argsIndex].type; const variadicArgs = validateArgTypeParams.filter((argParam) => argParam.mapsToVarArgList); - if (isTypeVarTuple(paramType) && !paramType.priv.isInUnion) { + if (isUnpacked(paramType) && (!isTypeVarTuple(paramType) || !paramType.priv.isInUnion)) { const tupleTypeArgs: TupleTypeArg[] = variadicArgs.map((argParam) => { const argType = getTypeOfArg(argParam.argument, /* inferenceContext */ undefined).type; @@ -11401,23 +11399,22 @@ export function createTypeEvaluator( }; }); - let specializedTuple: Type; - if ( - tupleTypeArgs.length === 1 && - !tupleTypeArgs[0].isUnbounded && - (isUnpackedClass(tupleTypeArgs[0].type) || isTypeVarTuple(tupleTypeArgs[0].type)) - ) { - // If there is a single unpacked tuple or unpacked variadic type variable - // (including an unpacked TypeVarTuple union) within this tuple, - // simplify the type. - specializedTuple = tupleTypeArgs[0].type; - } else { - specializedTuple = makeTupleObject(evaluatorInterface, tupleTypeArgs, /* isUnpacked */ true); + let specializedTuple: Type | undefined; + if (tupleTypeArgs.length === 1 && !tupleTypeArgs[0].isUnbounded) { + const entryType = tupleTypeArgs[0].type; + + if (isUnpacked(entryType)) { + specializedTuple = makePacked(entryType); + } + } + + if (!specializedTuple) { + specializedTuple = makeTupleObject(evaluatorInterface, tupleTypeArgs, /* isUnpacked */ false); } const combinedArg: ValidateArgTypeParams = { - paramCategory: ParamCategory.ArgsList, - paramType, + paramCategory: ParamCategory.Simple, + paramType: makePacked(paramType), requiresTypeVarMatching: true, argument: { argCategory: ArgCategory.Simple, @@ -11879,7 +11876,7 @@ export function createTypeEvaluator( // If the final return type is an unpacked tuple, turn it into a normal (unpacked) tuple. if (isUnpackedClass(specializedReturnType)) { - specializedReturnType = ClassType.cloneForUnpacked(specializedReturnType, /* isUnpacked */ false); + specializedReturnType = ClassType.cloneForPacked(specializedReturnType); } const liveTypeVarScopes = ParseTreeUtils.getTypeVarScopesForNode(errorNode); @@ -18834,7 +18831,7 @@ export function createTypeEvaluator( } if (isUnpackedClass(type)) { - return ClassType.cloneForUnpacked(type, /* isUnpacked */ false); + return ClassType.cloneForPacked(type); } return makeTupleObject(evaluatorInterface, [{ type, isUnbounded: !isTypeVarTuple(type) }]); @@ -24194,6 +24191,22 @@ export function createTypeEvaluator( let concreteSrcType = makeTopLevelTypeVarsConcrete(srcType); if (isClass(concreteSrcType) && TypeBase.isInstance(concreteSrcType)) { + // Handle the case where the source is an unpacked tuple. + if ( + !destType.priv.isUnpacked && + concreteSrcType.priv.isUnpacked && + concreteSrcType.priv.tupleTypeArgs + ) { + return assignType( + destType, + combineTupleTypeArgs(concreteSrcType.priv.tupleTypeArgs), + diag, + constraints, + flags, + recursionCount + ); + } + // Handle enum literals that are assignable to another (non-Enum) literal. // This can happen for IntEnum and StrEnum members. if ( diff --git a/packages/pyright-internal/src/analyzer/typePrinter.ts b/packages/pyright-internal/src/analyzer/typePrinter.ts index 8f03fd6c6fd3..bfb7758ada6f 100644 --- a/packages/pyright-internal/src/analyzer/typePrinter.ts +++ b/packages/pyright-internal/src/analyzer/typePrinter.ts @@ -615,14 +615,12 @@ function printTypeInternal( (printTypeFlags & PrintTypeFlags.OmitTypeVarScopes) === 0 ); - if (isTypeVarTuple(type)) { - if (type.priv.isUnpacked) { - typeVarName = _printUnpack(typeVarName, printTypeFlags); - } + if (type.priv.isUnpacked) { + typeVarName = _printUnpack(typeVarName, printTypeFlags); + } - if (type.priv.isInUnion) { - typeVarName = `Union[${typeVarName}]`; - } + if (isTypeVarTuple(type) && type.priv.isInUnion) { + typeVarName = `Union[${typeVarName}]`; } if (TypeBase.isInstantiable(type)) { diff --git a/packages/pyright-internal/src/analyzer/typeUtils.ts b/packages/pyright-internal/src/analyzer/typeUtils.ts index f82290e523a0..deae89619157 100644 --- a/packages/pyright-internal/src/analyzer/typeUtils.ts +++ b/packages/pyright-internal/src/analyzer/typeUtils.ts @@ -42,6 +42,7 @@ import { isUnion, isUnknown, isUnpackedClass, + isUnpackedTypeVar, isUnpackedTypeVarTuple, maxTypeRecursionCount, ModuleType, @@ -1385,7 +1386,9 @@ export function isTupleClass(type: ClassType) { // the form tuple[x, ...] where the number of elements // in the tuple is unknown. export function isUnboundedTupleClass(type: ClassType) { - return type.priv.tupleTypeArgs?.some((t) => t.isUnbounded || isUnpackedTypeVarTuple(t.type)); + return type.priv.tupleTypeArgs?.some( + (t) => t.isUnbounded || isUnpackedTypeVarTuple(t.type) || isUnpackedTypeVar(t.type) + ); } // Indicates whether the specified index is within range and its type is unambiguous @@ -1395,7 +1398,9 @@ export function isTupleIndexUnambiguous(type: ClassType, index: number) { return false; } - const unboundedIndex = type.priv.tupleTypeArgs.findIndex((t) => t.isUnbounded || isUnpackedTypeVarTuple(t.type)); + const unboundedIndex = type.priv.tupleTypeArgs.findIndex( + (t) => t.isUnbounded || isUnpackedTypeVarTuple(t.type) || isUnpackedTypeVar(t.type) + ); if (index < 0) { const lowerIndexLimit = unboundedIndex < 0 ? 0 : unboundedIndex; @@ -2680,16 +2685,33 @@ export function combineSameSizedTuples(type: Type, tupleType: Type | undefined): } export function combineTupleTypeArgs(typeArgs: TupleTypeArg[]): Type { - return combineTypes( - typeArgs.map((t) => { - if (isTypeVar(t.type) && isUnpackedTypeVarTuple(t.type)) { + const typesToCombine: Type[] = []; + + typeArgs.forEach((t) => { + if (isTypeVar(t.type)) { + if (isUnpackedTypeVarTuple(t.type)) { // Treat the unpacked TypeVarTuple as a union. - return TypeVarType.cloneForUnpacked(t.type, /* isInUnion */ true); + typesToCombine.push(TypeVarType.cloneForUnpacked(t.type, /* isInUnion */ true)); + return; } - return t.type; - }) - ); + if (isUnpackedTypeVar(t.type)) { + if ( + t.type.shared.boundType && + isClassInstance(t.type.shared.boundType) && + isTupleClass(t.type.shared.boundType) && + t.type.shared.boundType.priv.tupleTypeArgs + ) { + typesToCombine.push(combineTupleTypeArgs(t.type.shared.boundType.priv.tupleTypeArgs)); + } + return; + } + } + + typesToCombine.push(t.type); + }); + + return combineTypes(typesToCombine); } // Tuples require special handling for specialization. This method computes @@ -2724,6 +2746,39 @@ function _expandUnpackedTypeVarTupleUnion(type: Type) { return type; } +// If this is an unpacked type, returns the type as no longer unpacked. +export function makePacked(type: Type): Type { + if (isUnpackedClass(type)) { + return ClassType.cloneForPacked(type); + } + + if (isUnpackedTypeVarTuple(type) && !type.priv.isInUnion) { + return TypeVarType.cloneForPacked(type); + } + + if (isUnpackedTypeVar(type)) { + return TypeVarType.cloneForPacked(type); + } + + return type; +} + +export function makeUnpacked(type: Type): Type { + if (isClass(type)) { + return ClassType.cloneForUnpacked(type); + } + + if (isTypeVarTuple(type) && !type.priv.isInUnion) { + return TypeVarType.cloneForUnpacked(type); + } + + if (isTypeVar(type)) { + return TypeVarType.cloneForUnpacked(type); + } + + return type; +} + // If the declared return type for the function is a Generator or AsyncGenerator, // returns the type arguments for the type. export function getGeneratorTypeArgs(returnType: Type): Type[] | undefined { @@ -2828,6 +2883,16 @@ function _requiresSpecialization(type: Type, options?: RequiresSpecializationOpt return false; } + if (type.priv.tupleTypeArgs) { + if ( + type.priv.tupleTypeArgs.some((typeArg) => + requiresSpecialization(typeArg.type, options, recursionCount) + ) + ) { + return true; + } + } + if (type.priv.typeArgs) { return type.priv.typeArgs.some((typeArg) => requiresSpecialization(typeArg, options, recursionCount)); } @@ -3544,6 +3609,7 @@ export class TypeVarTransformer { if (ClassType.isTupleClass(classType)) { if (classType.priv.tupleTypeArgs) { newTupleTypeArgs = []; + classType.priv.tupleTypeArgs.forEach((oldTypeArgType) => { const newTypeArgType = this.apply(oldTypeArgType.type, recursionCount); @@ -3558,6 +3624,8 @@ export class TypeVarTransformer { newTypeArgType.priv.tupleTypeArgs ) { appendArray(newTupleTypeArgs!, newTypeArgType.priv.tupleTypeArgs); + } else if (isUnpackedClass(newTypeArgType) && newTypeArgType.priv.tupleTypeArgs) { + appendArray(newTupleTypeArgs!, newTypeArgType.priv.tupleTypeArgs); } else { // Handle the special case where tuple[T, ...] is being specialized // to tuple[Never, ...]. This is equivalent to tuple[()]. @@ -4038,6 +4106,15 @@ class ApplySolvedTypeVarsTransformer extends TypeVarTransformer { return TypeVarType.cloneForUnpacked(replacement, typeVar.priv.isInUnion); } + if ( + !isTypeVarTuple(replacement) && + isTypeVar(replacement) && + isTypeVar(typeVar) && + typeVar.priv.isUnpacked + ) { + return TypeVarType.cloneForUnpacked(replacement); + } + // If this isn't a TypeVarTuple, combine all of the tuple // type args into a common type. if ( @@ -4049,6 +4126,10 @@ class ApplySolvedTypeVarsTransformer extends TypeVarTransformer { replacement = combineTupleTypeArgs(replacement.priv.tupleTypeArgs); } + if (isUnpackedTypeVar(typeVar) && isClass(replacement)) { + replacement = ClassType.cloneForUnpacked(replacement); + } + if (!isTypeVar(replacement) || !TypeVarType.isUnification(replacement) || !this._options.replaceUnsolved) { return replacement; } diff --git a/packages/pyright-internal/src/analyzer/types.ts b/packages/pyright-internal/src/analyzer/types.ts index 090b6c90ff41..099eab62cb41 100644 --- a/packages/pyright-internal/src/analyzer/types.ts +++ b/packages/pyright-internal/src/analyzer/types.ts @@ -1010,9 +1010,23 @@ export namespace ClassType { return newClassType; } - export function cloneForUnpacked(classType: ClassType, isUnpacked = true): ClassType { + export function cloneForUnpacked(classType: ClassType): ClassType { + if (classType.priv.isUnpacked) { + return classType; + } + + const newClassType = TypeBase.cloneType(classType); + newClassType.priv.isUnpacked = true; + return newClassType; + } + + export function cloneForPacked(classType: ClassType): ClassType { + if (!classType.priv.isUnpacked) { + return classType; + } + const newClassType = TypeBase.cloneType(classType); - newClassType.priv.isUnpacked = isUnpacked; + newClassType.priv.isUnpacked = false; return newClassType; } @@ -2778,6 +2792,9 @@ export interface TypeVarDetailsPriv { // If the TypeVar is bound form of a TypeVar, this refers to // the corresponding free TypeVar. freeTypeVar?: TypeVarType | undefined; + + // Is this TypeVar or TypeVarTuple unpacked (i.e. Unpack or * operator applied)? + isUnpacked?: boolean | undefined; } export interface TypeVarType extends TypeBase { @@ -2812,9 +2829,6 @@ export namespace ParamSpecType { } export interface TypeVarTupleDetailsPriv extends TypeVarDetailsPriv { - // Is this TypeVarTuple unpacked (i.e. Unpack or * operator applied)? - isUnpacked?: boolean | undefined; - // Is this TypeVarTuple included in a Union[]? This allows us to // differentiate between Unpack[Vs] and Union[Unpack[Vs]]. isInUnion?: boolean | undefined; @@ -2899,10 +2913,13 @@ export namespace TypeVarType { return newInstance; } - export function cloneForUnpacked(type: TypeVarTupleType, isInUnion = false) { + export function cloneForUnpacked(type: TypeVarType, isInUnion = false) { const newInstance = TypeBase.cloneType(type); newInstance.priv.isUnpacked = true; - newInstance.priv.isInUnion = isInUnion; + + if (isTypeVarTuple(newInstance) && isInUnion) { + newInstance.priv.isInUnion = isInUnion; + } if (newInstance.priv.freeTypeVar) { newInstance.priv.freeTypeVar = TypeVarType.cloneForUnpacked(newInstance.priv.freeTypeVar, isInUnion); @@ -3195,6 +3212,10 @@ export function isUnpackedTypeVarTuple(type: Type): type is TypeVarTupleType { return isTypeVarTuple(type) && !!type.priv.isUnpacked && !type.priv.isInUnion; } +export function isUnpackedTypeVar(type: Type): type is TypeVarTupleType { + return isTypeVar(type) && !isTypeVarTuple(type) && !!type.priv.isUnpacked; +} + export function isUnpackedClass(type: Type): type is ClassType { if (!isClass(type) || !type.priv.isUnpacked) { return false; @@ -3204,7 +3225,7 @@ export function isUnpackedClass(type: Type): type is ClassType { } export function isUnpacked(type: Type): boolean { - return isUnpackedTypeVarTuple(type) || isUnpackedClass(type); + return isUnpackedTypeVarTuple(type) || isUnpackedTypeVar(type) || isUnpackedClass(type); } export function isFunction(type: Type): type is FunctionType { diff --git a/packages/pyright-internal/src/tests/samples/tupleUnpack4.py b/packages/pyright-internal/src/tests/samples/tupleUnpack4.py new file mode 100644 index 000000000000..c0307f77f55b --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/tupleUnpack4.py @@ -0,0 +1,30 @@ +# This sample tests the handling of a TypeVar whose upper bound is +# a tuple when used for an *args parameter. + +from typing import TypeVar, Unpack + + +T = TypeVar("T", bound="tuple[int, ...]") + + +def func1(*args: Unpack[T]) -> tuple[int, ...]: + a, *v = args + + reveal_type(a, expected_text="*T@func1") + + b: int = a + + # This should generate an error. + c: str = a + + reveal_type(v, expected_text="list[*T@func1]") + + return args + + +S = TypeVar("S", bound=list[int]) + + +# This should generate an error. +def func2(*args: Unpack[S]) -> int: + return 0 diff --git a/packages/pyright-internal/src/tests/samples/typeVarTuple8.py b/packages/pyright-internal/src/tests/samples/typeVarTuple8.py index bc8fbbc9700e..3c8e136b052d 100644 --- a/packages/pyright-internal/src/tests/samples/typeVarTuple8.py +++ b/packages/pyright-internal/src/tests/samples/typeVarTuple8.py @@ -15,32 +15,25 @@ _Ys = TypeVarTuple("_Ys") -def func1(x: Union[Unpack[_Xs]]) -> Union[Unpack[_Xs]]: - ... +def func1(x: Union[Unpack[_Xs]]) -> Union[Unpack[_Xs]]: ... -def func2(x: Union[Unpack[_Xs], Unpack[_Ys]]) -> Union[Unpack[_Xs], Unpack[_Ys]]: - ... +def func2(x: Union[Unpack[_Xs], Unpack[_Ys]]) -> Union[Unpack[_Xs], Unpack[_Ys]]: ... -def func3(x: Union[int, Unpack[_Xs]]) -> Union[Unpack[_Xs]]: - ... +def func3(x: Union[int, Unpack[_Xs]]) -> Union[Unpack[_Xs]]: ... -def func4(x: Union[_T, Unpack[_Xs]]) -> Union[_T, Unpack[_Xs]]: - ... +def func4(x: Union[_T, Unpack[_Xs]]) -> Union[_T, Unpack[_Xs]]: ... -def func5(x: Union[Unpack[_Xs]], *args: Unpack[_Xs]) -> Union[Unpack[_Xs]]: - ... +def func5(x: Union[Unpack[_Xs]], *args: Unpack[_Xs]) -> Union[Unpack[_Xs]]: ... -def func6(*args: Unpack[_Xs]) -> Union[Unpack[_Xs]]: - ... +def func6(*args: Unpack[_Xs]) -> Union[Unpack[_Xs]]: ... -def func7(a: list[Union[Unpack[_Xs]]]) -> Union[Unpack[_Xs]]: - ... +def func7(a: list[Union[Unpack[_Xs]]]) -> Union[Unpack[_Xs]]: ... def test1(a: int, b: str, c: list[int], d: Union[complex, str]): @@ -84,11 +77,11 @@ def test1(a: int, b: str, c: list[int], d: Union[complex, str]): # --------- - # This should generate an error v5_1 = func5(a) + reveal_type(v5_1, expected_text="int") - # This should generate an error v5_2 = func5(a, a) + reveal_type(v5_2, expected_text="int") # This should generate an error v5_3 = func5(a, b) diff --git a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts index 581c144e6ac0..c7551acd808f 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator6.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator6.test.ts @@ -248,7 +248,7 @@ test('TypeVarTuple8', () => { configOptions.defaultPythonVersion = pythonVersion3_11; const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeVarTuple8.py'], configOptions); - TestUtils.validateResults(analysisResults, 5); + TestUtils.validateResults(analysisResults, 3); }); test('TypeVarTuple9', () => { diff --git a/packages/pyright-internal/src/tests/typeEvaluator8.test.ts b/packages/pyright-internal/src/tests/typeEvaluator8.test.ts index 50e0384e0264..9198c978c2f6 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator8.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator8.test.ts @@ -787,6 +787,11 @@ test('TupleUnpack3', () => { TestUtils.validateResults(analysisResults1, 1); }); +test('TupleUnpack4', () => { + const analysisResults1 = TestUtils.typeAnalyzeSampleFiles(['tupleUnpack4.py']); + TestUtils.validateResults(analysisResults1, 2); +}); + test('PseudoGeneric1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['pseudoGeneric1.py']);