Skip to content

Commit

Permalink
Fixed a bug that results in a false positive when using a TypeVarTupl…
Browse files Browse the repository at this point in the history
…e to capture the parameters of a generic callable that includes one or more default argument values. This addresses microsoft#7146. (microsoft#7150)
  • Loading branch information
erictraut authored Jan 28, 2024
1 parent e18963f commit 725f91f
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 10 deletions.
7 changes: 6 additions & 1 deletion packages/pyright-internal/src/analyzer/constraintSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,11 @@ export function populateTypeVarContextBasedOnExpectedType(
tupleType = transformExpectedType(tupleEntry.type, liveTypeVarScopes, usageOffset);
}

return { type: tupleType, isUnbounded: tupleEntry.isUnbounded };
return {
type: tupleType,
isUnbounded: tupleEntry.isUnbounded,
isOptional: tupleEntry.isOptional,
};
})
);
}
Expand Down Expand Up @@ -1246,6 +1250,7 @@ function stripLiteralValueForUnpackedTuple(evaluator: TypeEvaluator, type: Type)

return {
isUnbounded: arg.isUnbounded,
isOptional: arg.isOptional,
type: strippedType,
};
});
Expand Down
12 changes: 10 additions & 2 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22194,7 +22194,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
specializeTupleClass(
tupleClassType,
removedArgs.map((typeArg) => {
return { type: typeArg.type, isUnbounded: typeArg.isUnbounded };
return {
type: typeArg.type,
isUnbounded: typeArg.isUnbounded,
isOptional: typeArg.isOptional,
};
}),
/* isTypeArgumentExplicit */ true,
/* isUnpackedTuple */ true
Expand Down Expand Up @@ -22222,7 +22226,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
specializeTupleClass(
tupleClassType,
removedArgs.map((typeArg) => {
return { type: typeArg.type, isUnbounded: typeArg.isUnbounded };
return {
type: typeArg.type,
isUnbounded: typeArg.isUnbounded,
isOptional: typeArg.isOptional,
};
}),
/* isTypeArgumentExplicit */ true,
/* isUnpackedTuple */ true
Expand Down
23 changes: 18 additions & 5 deletions packages/pyright-internal/src/analyzer/typeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,12 +549,16 @@ export function cleanIncompleteUnknown(type: Type, recursionCount = 0): Type {
let typeChanged = false;

if (subtype.tupleTypeArguments) {
const updatedTupleTypeArgs = subtype.tupleTypeArguments.map((tupleTypeArg) => {
const updatedTupleTypeArgs: TupleTypeArgument[] = subtype.tupleTypeArguments.map((tupleTypeArg) => {
const newTypeArg = cleanIncompleteUnknown(tupleTypeArg.type, recursionCount);
if (newTypeArg !== tupleTypeArg.type) {
typeChanged = true;
}
return { type: newTypeArg, isUnbounded: tupleTypeArg.isUnbounded };
return {
type: newTypeArg,
isUnbounded: tupleTypeArg.isUnbounded,
isOptional: tupleTypeArg.isOptional,
};
});

if (typeChanged) {
Expand Down Expand Up @@ -1454,7 +1458,11 @@ export function applySourceContextTypeVarsToSignature(
destSignature.setTupleTypeVar(
entry.typeVar,
entry.tupleTypes.map((arg) => {
return { isUnbounded: arg.isUnbounded, type: applySolvedTypeVars(arg.type, srcContext) };
return {
type: applySolvedTypeVars(arg.type, srcContext),
isUnbounded: arg.isUnbounded,
isOptional: arg.isOptional,
};
})
);
}
Expand Down Expand Up @@ -1491,8 +1499,9 @@ export function applyInScopePlaceholders(typeVarContext: TypeVarContext) {
entry.typeVar,
entry.tupleTypes.map((arg) => {
return {
isUnbounded: arg.isUnbounded,
type: applyInScopePlaceholdersToType(arg.type, signature),
isUnbounded: arg.isUnbounded,
isOptional: arg.isOptional,
};
})
);
Expand Down Expand Up @@ -3700,7 +3709,11 @@ class TypeVarTransformer {
) {
appendArray(newTupleTypeArgs!, newTypeArgType.tupleTypeArguments);
} else {
newTupleTypeArgs!.push({ type: newTypeArgType, isUnbounded: oldTypeArgType.isUnbounded });
newTupleTypeArgs!.push({
type: newTypeArgType,
isUnbounded: oldTypeArgType.isUnbounded,
isOptional: oldTypeArgType.isOptional,
});
}
});
} else if (typeParams.length > 0) {
Expand Down
4 changes: 3 additions & 1 deletion packages/pyright-internal/src/analyzer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,9 @@ export namespace ClassType {

newClassType.tupleTypeArguments = tupleTypeArguments
? tupleTypeArguments.map((t) =>
isNever(t.type) ? { type: UnknownType.create(), isUnbounded: t.isUnbounded } : t
isNever(t.type)
? { type: UnknownType.create(), isUnbounded: t.isUnbounded, isOptional: t.isOptional }
: t
)
: undefined;

Expand Down
11 changes: 10 additions & 1 deletion packages/pyright-internal/src/tests/samples/variadicTypeVar26.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
# a callable with an indeterminate number of parameters because
# some of them have default arguments.

from typing import Callable, Literal, TypeVarTuple
from typing import Callable, Literal, TypeVar, TypeVarTuple

T = TypeVar("T")
Ts = TypeVarTuple("Ts")


Expand Down Expand Up @@ -51,3 +52,11 @@ def func4(a: Literal["day", "hour"]) -> None:

def func5(x: bool):
func2(func4, "day" if x else "hour")


def func6(x: T, y: T, z: int | None = None) -> None:
...


def func7(x: T, y: T) -> None:
func2(func6, x, y)

0 comments on commit 725f91f

Please sign in to comment.