Skip to content

Commit 5f947f8

Browse files
committed
Improve contextual typing of ending tuple elements
1 parent 5739445 commit 5f947f8

File tree

2 files changed

+45
-19
lines changed

2 files changed

+45
-19
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29007,18 +29007,45 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2900729007
return undefined;
2900829008
}
2900929009

29010-
// In an array literal contextually typed by a type T, the contextual type of an element expression at index N is
29011-
// the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature,
29012-
// it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated
29013-
// type of T.
29014-
function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined {
29015-
return arrayContextualType && (
29016-
index >= 0 && getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) ||
29017-
mapType(arrayContextualType, t =>
29018-
isTupleType(t) ?
29019-
getElementTypeOfSliceOfTupleType(t, 0, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true) :
29020-
getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false),
29021-
/*noReductions*/ true));
29010+
function getSpreadIndices(elements: readonly Node[]) {
29011+
let first, last;
29012+
for (let i = 0; i < elements.length; i++) {
29013+
if (isSpreadElement(elements[i])) {
29014+
first ??= i;
29015+
last = i;
29016+
}
29017+
}
29018+
return { first, last };
29019+
}
29020+
29021+
function getContextualTypeForElementExpression(type: Type | undefined, index: number, length?: number, firstSpreadIndex?: number, lastSpreadIndex?: number): Type | undefined {
29022+
return type && mapType(type, t => {
29023+
if (isTupleType(t)) {
29024+
// If index is before any spread element and within the fixed part of the contextual tuple type, return
29025+
// the type of the contextual tuple element.
29026+
if ((firstSpreadIndex === undefined || index < firstSpreadIndex) && index < t.target.fixedLength) {
29027+
return getTypeArguments(t)[index];
29028+
}
29029+
// When the length is known and the index is after all spread elements we compute the offset from the element
29030+
// to the end and the number of ending fixed elements in the contextual tuple type.
29031+
const offset = length !== undefined && (lastSpreadIndex === undefined || index > lastSpreadIndex) ? length - index : 0;
29032+
const fixedEndLength = offset > 0 && t.target.hasRestElement ? getEndElementCount(t.target, ElementFlags.Fixed) : 0;
29033+
// If the offset is within the ending fixed part of the contextual tuple type, return the type of the contextual
29034+
// tuple element.
29035+
if (offset > 0 && offset <= fixedEndLength) {
29036+
return getTypeArguments(t)[getTypeReferenceArity(t) - offset];
29037+
}
29038+
// Return a union of the possible contextual element types with no subtype reduction.
29039+
return getElementTypeOfSliceOfTupleType(t,
29040+
firstSpreadIndex === undefined ? t.target.fixedLength : Math.min(t.target.fixedLength, firstSpreadIndex),
29041+
length === undefined || lastSpreadIndex === undefined ? fixedEndLength : Math.min(fixedEndLength, length - lastSpreadIndex),
29042+
/*writing*/ false, /*noReductions*/ true);
29043+
}
29044+
// If element index is known and a contextual property with that name exists, return it. Otherwise return the
29045+
// iterated or element type of the contextual type.
29046+
return (!firstSpreadIndex || index < firstSpreadIndex) && getTypeOfPropertyOfContextualType(type, "" + index as __String) ||
29047+
getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false);
29048+
}, /*noReductions*/ true);
2902229049
}
2902329050

2902429051
// In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type.
@@ -29249,12 +29276,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2924929276
case SyntaxKind.ArrayLiteralExpression: {
2925029277
const arrayLiteral = parent as ArrayLiteralExpression;
2925129278
const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags);
29252-
// The index of an array literal element doesn't necessarily line up with the index of the corresponding
29253-
// element in a contextual tuple type when there are preceding spread elements in the array literal. For
29254-
// this reason we only pass indices for elements that precede the first spread element.
29255-
const spreadIndex = getNodeLinks(arrayLiteral).firstSpreadIndex ??= findIndex(arrayLiteral.elements, isSpreadElement);
2925629279
const elementIndex = indexOfNode(arrayLiteral.elements, node);
29257-
return getContextualTypeForElementExpression(type, spreadIndex < 0 || elementIndex < spreadIndex ? elementIndex : -1);
29280+
const spreadIndices = getNodeLinks(arrayLiteral).spreadIndices ??= getSpreadIndices(arrayLiteral.elements);
29281+
return getContextualTypeForElementExpression(type, elementIndex, arrayLiteral.elements.length, spreadIndices.first, spreadIndices.last);
2925829282
}
2925929283
case SyntaxKind.ConditionalExpression:
2926029284
return getContextualTypeForConditionalOperand(node, contextFlags);
@@ -32236,7 +32260,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3223632260
}
3223732261
}
3223832262
else {
32239-
const contextualType = getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual);
32263+
const contextualType = isTupleType(restType) ?
32264+
getContextualTypeForElementExpression(restType, i - index, argCount - index) || unknownType :
32265+
getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual);
3224032266
const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode);
3224132267
const hasPrimitiveContextualType = inConstContext || maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping);
3224232268
types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType));

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5997,7 +5997,7 @@ export interface NodeLinks {
59975997
declarationRequiresScopeChange?: boolean; // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter.
59985998
serializedTypes?: Map<string, SerializedTypeEntry>; // Collection of types serialized at this location
59995999
decoratorSignature?: Signature; // Signature for decorator as if invoked by the runtime.
6000-
firstSpreadIndex?: number; // Index of first spread element in array literal (-1 for none)
6000+
spreadIndices?: { first: number | undefined, last: number | undefined }; // Indices of first and last spread elements in array literal
60016001
parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined".
60026002
fakeScopeForSignatureDeclaration?: boolean; // True if this is a fake scope injected into an enclosing declaration chain.
60036003
}

0 commit comments

Comments
 (0)