Skip to content
Closed
25 changes: 23 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31860,7 +31860,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (parent.name.kind === SyntaxKind.ArrayBindingPattern) {
const index = indexOfNode(declaration.parent.elements, declaration);
if (index < 0) return undefined;
return getContextualTypeForElementExpression(parentType, index);
return getContextualTypeForElementExpression(parentType, index, declaration.parent.elements.length);
}
const nameType = getLiteralTypeFromPropertyName(name);
if (isTypeUsableAsPropertyName(nameType)) {
Expand Down Expand Up @@ -36027,6 +36027,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return skipOuterExpressions(argument, flags);
}

function isCallInLastTupleElementDestructuring(node: CallLikeExpression): boolean {
// Check if this call expression is used as initializer in a variable declaration with array destructuring
const parent = node.parent;
if (parent && isVariableDeclaration(parent) && parent.initializer === node && isArrayBindingPattern(parent.name)) {
const elements = parent.name.elements;
// Check if the destructuring pattern accesses the last element
// (i.e., the last non-omitted element is at the end of the pattern)
for (let i = elements.length - 1; i >= 0; i--) {
if (!isOmittedExpression(elements[i])) {
// If the last non-omitted element is at the last position, it's accessing the last tuple element
return i === elements.length - 1;
}
}
}
return false;
}

function getSignatureApplicabilityError(
node: CallLikeExpression,
args: readonly Expression[],
Expand Down Expand Up @@ -36069,7 +36086,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive),
// we obtain the regular type of any object literal arguments because we may not have inferred complete
// parameter types yet and therefore excess property checks may yield false positives (see #17041).
const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType;
// Also skip fresh literal checking when the call is in last tuple element destructuring context
// to prevent incorrect excess property errors (see #41548).
const shouldSkipFreshness = (checkMode & CheckMode.SkipContextSensitive) ||
(isCallExpression(node) && isCallInLastTupleElementDestructuring(node));
const checkArgType = shouldSkipFreshness ? getRegularTypeOfObjectLiteral(argType) : argType;
const effectiveCheckArgumentNode = getEffectiveCheckNode(arg);
if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? effectiveCheckArgumentNode : undefined, effectiveCheckArgumentNode, headMessage, containingMessageChain, errorOutputContainer)) {
Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
excessPropertyCheckingInArrayDestructuring.ts(12,28): error TS2322: Type '"c"' is not assignable to type '"a" | "b"'.
excessPropertyCheckingInArrayDestructuring.ts(13,26): error TS2345: Argument of type 'number' is not assignable to parameter of type '{ dataType: "a" | "b"; }'.


==== excessPropertyCheckingInArrayDestructuring.ts (2 errors) ====
declare function foo<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any];

// Test the specific problematic case that should now work
const [, , works1] = foo({ dataType: 'a', day: 0 });
const [, , works2] = foo({ dataType: 'b', extra: 'value' });

// Test assignment destructuring (not currently fixed)
let a: any;
[, , a] = foo({ dataType: 'a', day: 0 }); // This might still error

// Test that legitimate errors are still caught
const [, , fails1] = foo({ dataType: 'c' }); // Error: 'c' not assignable to 'a' | 'b'
~~~~~~~~
!!! error TS2322: Type '"c"' is not assignable to type '"a" | "b"'.
!!! related TS6500 excessPropertyCheckingInArrayDestructuring.ts:1:34: The expected type comes from property 'dataType' which is declared here on type '{ dataType: "a" | "b"; }'
const [, , fails2] = foo(123); // Error: number not assignable to constraint
~~~
!!! error TS2345: Argument of type 'number' is not assignable to parameter of type '{ dataType: "a" | "b"; }'.

// Test that non-destructuring cases work as before
const result = foo({ dataType: 'a', day: 0 }); // Should work
const explicit: [{ dataType: 'a', day: number }, any, any] = foo({ dataType: 'a', day: 0 }); // Should work

// Test that other destructuring patterns work correctly
const [first] = foo({ dataType: 'a', day: 0 }); // Should work
const [, second] = foo({ dataType: 'a', day: 0 }); // Should work
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//// [tests/cases/compiler/excessPropertyCheckingInArrayDestructuring.ts] ////

//// [excessPropertyCheckingInArrayDestructuring.ts]
declare function foo<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any];

// Test the specific problematic case that should now work
const [, , works1] = foo({ dataType: 'a', day: 0 });
const [, , works2] = foo({ dataType: 'b', extra: 'value' });

// Test assignment destructuring (not currently fixed)
let a: any;
[, , a] = foo({ dataType: 'a', day: 0 }); // This might still error

// Test that legitimate errors are still caught
const [, , fails1] = foo({ dataType: 'c' }); // Error: 'c' not assignable to 'a' | 'b'
const [, , fails2] = foo(123); // Error: number not assignable to constraint

// Test that non-destructuring cases work as before
const result = foo({ dataType: 'a', day: 0 }); // Should work
const explicit: [{ dataType: 'a', day: number }, any, any] = foo({ dataType: 'a', day: 0 }); // Should work

// Test that other destructuring patterns work correctly
const [first] = foo({ dataType: 'a', day: 0 }); // Should work
const [, second] = foo({ dataType: 'a', day: 0 }); // Should work

//// [excessPropertyCheckingInArrayDestructuring.js]
"use strict";
var _a;
// Test the specific problematic case that should now work
var _b = foo({ dataType: 'a', day: 0 }), works1 = _b[2];
var _c = foo({ dataType: 'b', extra: 'value' }), works2 = _c[2];
// Test assignment destructuring (not currently fixed)
var a;
_a = foo({ dataType: 'a', day: 0 }), a = _a[2]; // This might still error
// Test that legitimate errors are still caught
var _d = foo({ dataType: 'c' }), fails1 = _d[2]; // Error: 'c' not assignable to 'a' | 'b'
var _e = foo(123), fails2 = _e[2]; // Error: number not assignable to constraint
// Test that non-destructuring cases work as before
var result = foo({ dataType: 'a', day: 0 }); // Should work
var explicit = foo({ dataType: 'a', day: 0 }); // Should work
// Test that other destructuring patterns work correctly
var first = foo({ dataType: 'a', day: 0 })[0]; // Should work
var _f = foo({ dataType: 'a', day: 0 }), second = _f[1]; // Should work
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//// [tests/cases/compiler/excessPropertyCheckingInArrayDestructuring.ts] ////

=== excessPropertyCheckingInArrayDestructuring.ts ===
declare function foo<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any];
>foo : Symbol(foo, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 0))
>T : Symbol(T, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 21))
>dataType : Symbol(dataType, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 32))
>template : Symbol(template, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 56))
>T : Symbol(T, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 21))
>T : Symbol(T, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 21))

// Test the specific problematic case that should now work
const [, , works1] = foo({ dataType: 'a', day: 0 });
>works1 : Symbol(works1, Decl(excessPropertyCheckingInArrayDestructuring.ts, 3, 10))
>foo : Symbol(foo, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 0))
>dataType : Symbol(dataType, Decl(excessPropertyCheckingInArrayDestructuring.ts, 3, 26))
>day : Symbol(day, Decl(excessPropertyCheckingInArrayDestructuring.ts, 3, 41))

const [, , works2] = foo({ dataType: 'b', extra: 'value' });
>works2 : Symbol(works2, Decl(excessPropertyCheckingInArrayDestructuring.ts, 4, 10))
>foo : Symbol(foo, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 0))
>dataType : Symbol(dataType, Decl(excessPropertyCheckingInArrayDestructuring.ts, 4, 26))
>extra : Symbol(extra, Decl(excessPropertyCheckingInArrayDestructuring.ts, 4, 41))

// Test assignment destructuring (not currently fixed)
let a: any;
>a : Symbol(a, Decl(excessPropertyCheckingInArrayDestructuring.ts, 7, 3))

[, , a] = foo({ dataType: 'a', day: 0 }); // This might still error
>a : Symbol(a, Decl(excessPropertyCheckingInArrayDestructuring.ts, 7, 3))
>foo : Symbol(foo, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 0))
>dataType : Symbol(dataType, Decl(excessPropertyCheckingInArrayDestructuring.ts, 8, 15))
>day : Symbol(day, Decl(excessPropertyCheckingInArrayDestructuring.ts, 8, 30))

// Test that legitimate errors are still caught
const [, , fails1] = foo({ dataType: 'c' }); // Error: 'c' not assignable to 'a' | 'b'
>fails1 : Symbol(fails1, Decl(excessPropertyCheckingInArrayDestructuring.ts, 11, 10))
>foo : Symbol(foo, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 0))
>dataType : Symbol(dataType, Decl(excessPropertyCheckingInArrayDestructuring.ts, 11, 26))

const [, , fails2] = foo(123); // Error: number not assignable to constraint
>fails2 : Symbol(fails2, Decl(excessPropertyCheckingInArrayDestructuring.ts, 12, 10))
>foo : Symbol(foo, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 0))

// Test that non-destructuring cases work as before
const result = foo({ dataType: 'a', day: 0 }); // Should work
>result : Symbol(result, Decl(excessPropertyCheckingInArrayDestructuring.ts, 15, 5))
>foo : Symbol(foo, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 0))
>dataType : Symbol(dataType, Decl(excessPropertyCheckingInArrayDestructuring.ts, 15, 20))
>day : Symbol(day, Decl(excessPropertyCheckingInArrayDestructuring.ts, 15, 35))

const explicit: [{ dataType: 'a', day: number }, any, any] = foo({ dataType: 'a', day: 0 }); // Should work
>explicit : Symbol(explicit, Decl(excessPropertyCheckingInArrayDestructuring.ts, 16, 5))
>dataType : Symbol(dataType, Decl(excessPropertyCheckingInArrayDestructuring.ts, 16, 18))
>day : Symbol(day, Decl(excessPropertyCheckingInArrayDestructuring.ts, 16, 33))
>foo : Symbol(foo, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 0))
>dataType : Symbol(dataType, Decl(excessPropertyCheckingInArrayDestructuring.ts, 16, 66))
>day : Symbol(day, Decl(excessPropertyCheckingInArrayDestructuring.ts, 16, 81))

// Test that other destructuring patterns work correctly
const [first] = foo({ dataType: 'a', day: 0 }); // Should work
>first : Symbol(first, Decl(excessPropertyCheckingInArrayDestructuring.ts, 19, 7))
>foo : Symbol(foo, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 0))
>dataType : Symbol(dataType, Decl(excessPropertyCheckingInArrayDestructuring.ts, 19, 21))
>day : Symbol(day, Decl(excessPropertyCheckingInArrayDestructuring.ts, 19, 36))

const [, second] = foo({ dataType: 'a', day: 0 }); // Should work
>second : Symbol(second, Decl(excessPropertyCheckingInArrayDestructuring.ts, 20, 8))
>foo : Symbol(foo, Decl(excessPropertyCheckingInArrayDestructuring.ts, 0, 0))
>dataType : Symbol(dataType, Decl(excessPropertyCheckingInArrayDestructuring.ts, 20, 24))
>day : Symbol(day, Decl(excessPropertyCheckingInArrayDestructuring.ts, 20, 39))

Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//// [tests/cases/compiler/excessPropertyCheckingInArrayDestructuring.ts] ////

=== excessPropertyCheckingInArrayDestructuring.ts ===
declare function foo<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any];
>foo : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>dataType : "a" | "b"
> : ^^^^^^^^^
>template : T
> : ^

// Test the specific problematic case that should now work
const [, , works1] = foo({ dataType: 'a', day: 0 });
> : undefined
> : ^^^^^^^^^
> : undefined
> : ^^^^^^^^^
>works1 : any
> : ^^^
>foo({ dataType: 'a', day: 0 }) : [{ dataType: "a" | "b"; }, any, any]
> : ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
>foo : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^

const [, , works2] = foo({ dataType: 'b', extra: 'value' });
> : undefined
> : ^^^^^^^^^
> : undefined
> : ^^^^^^^^^
>works2 : any
> : ^^^
>foo({ dataType: 'b', extra: 'value' }) : [{ dataType: "a" | "b"; }, any, any]
> : ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
>foo : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'b', extra: 'value' } : { dataType: "b"; extra: string; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "b"
> : ^^^
>'b' : "b"
> : ^^^
>extra : string
> : ^^^^^^
>'value' : "value"
> : ^^^^^^^

// Test assignment destructuring (not currently fixed)
let a: any;
>a : any
> : ^^^

[, , a] = foo({ dataType: 'a', day: 0 }); // This might still error
>[, , a] = foo({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>[, , a] : [undefined, undefined, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : undefined
> : ^^^^^^^^^
> : undefined
> : ^^^^^^^^^
>a : any
> : ^^^
>foo({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^

// Test that legitimate errors are still caught
const [, , fails1] = foo({ dataType: 'c' }); // Error: 'c' not assignable to 'a' | 'b'
> : undefined
> : ^^^^^^^^^
> : undefined
> : ^^^^^^^^^
>fails1 : any
> : ^^^
>foo({ dataType: 'c' }) : [{ dataType: "a" | "b"; }, any, any]
> : ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
>foo : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'c' } : { dataType: "c"; }
> : ^^^^^^^^^^^^^^^^^^
>dataType : "c"
> : ^^^
>'c' : "c"
> : ^^^

const [, , fails2] = foo(123); // Error: number not assignable to constraint
> : undefined
> : ^^^^^^^^^
> : undefined
> : ^^^^^^^^^
>fails2 : any
> : ^^^
>foo(123) : [{ dataType: "a" | "b"; }, any, any]
> : ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
>foo : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>123 : 123
> : ^^^

// Test that non-destructuring cases work as before
const result = foo({ dataType: 'a', day: 0 }); // Should work
>result : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^

const explicit: [{ dataType: 'a', day: number }, any, any] = foo({ dataType: 'a', day: 0 }); // Should work
>explicit : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>day : number
> : ^^^^^^
>foo({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^

// Test that other destructuring patterns work correctly
const [first] = foo({ dataType: 'a', day: 0 }); // Should work
>first : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^

const [, second] = foo({ dataType: 'a', day: 0 }); // Should work
> : undefined
> : ^^^^^^^^^
>second : any
> : ^^^
>foo({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^

Loading
Loading