Skip to content

Fix contextual typing sensitivity to binding pattern structure in destructuring #62142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
51 changes: 34 additions & 17 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12327,23 +12327,40 @@
}

// Return the type implied by an array binding pattern
function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type {
const elements = pattern.elements;
const lastElement = lastOrUndefined(elements);
const restElement = lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken ? lastElement : undefined;
if (elements.length === 0 || elements.length === 1 && restElement) {
return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType;
}
const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors));
const minLength = findLastIndex(elements, e => !(e === restElement || isOmittedExpression(e) || hasDefaultValue(e)), elements.length - 1) + 1;
const elementFlags = map(elements, (e, i) => e === restElement ? ElementFlags.Rest : i >= minLength ? ElementFlags.Optional : ElementFlags.Required);
let result = createTupleType(elementTypes, elementFlags) as TypeReference;
if (includePatternInType) {
result = cloneTypeReference(result);
result.pattern = pattern;
result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral;
}
return result;
function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type {
const elements = pattern.elements;
const lastElement = lastOrUndefined(elements);
const restElement = lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken ? lastElement : undefined;
if (elements.length === 0 || elements.length === 1 && restElement) {
return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType;
}
const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors));

Check failure on line 12338 in src/compiler/checker.ts

View workflow job for this annotation

GitHub Actions / copilot

'elementTypes' is never reassigned. Use 'const' instead
// For contextual typing, ensure both [, , t] and [, s, ] produce the same contextual type [any, any, any]
// by extending shorter tuples to at least 3 elements when constructing contextual types
if (includePatternInType && !restElement && elementTypes.length < 3) {
while (elementTypes.length < 3) {
elementTypes.push(anyType);
}
}

const minLength = findLastIndex(elements, e => !(e === restElement || isOmittedExpression(e) || hasDefaultValue(e)), elements.length - 1) + 1;
const elementFlags = map(elementTypes, (_, i) => {
if (i < elements.length) {
const e = elements[i];
return e === restElement ? ElementFlags.Rest : i >= minLength ? ElementFlags.Optional : ElementFlags.Required;
} else {
// Added elements for contextual typing should be optional
return ElementFlags.Optional;
}
});
let result = createTupleType(elementTypes, elementFlags) as TypeReference;
if (includePatternInType) {
result = cloneTypeReference(result);
result.pattern = pattern;
result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral;
}
return result;
}

// Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @strict: true

type DataType = 'a' | 'b';
declare function foo<T extends { dataType: DataType }>(template: T): [T, any, any];

// These should behave the same - both should allow excess properties
const [, , t] = foo({ dataType: 'a', day: 0 });
const [, s, ] = foo({ dataType: 'a', day: 0 });