Detailed investigation of tuple destructuring excess property issue

Reverted all previous changes and conducted thorough analysis of the root cause.
Found that the issue is related to contextual typing and generic type inference,
not specifically the "last element position" but rather the pattern of
first element omitted + last element named.

Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-07-28 22:47:22 +00:00
parent 31a298b0a2
commit 4235412daa
5 changed files with 0 additions and 454 deletions

View File

@ -22901,47 +22901,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
) {
return false;
}
// Heuristic: If the target type looks like a constraint (simple object type with few properties),
// be more lenient with excess property checking. This handles cases like T extends { prop: Type }
// where the constraint should allow additional properties.
if (target.flags & TypeFlags.Object && !isComparingJsxAttributes) {
const targetProperties = getPropertiesOfType(target);
const targetIndexInfos = getIndexInfosOfType(target);
// If it's a simple object with few properties and no index signatures, it might be a constraint
if (targetProperties.length <= 2 && targetIndexInfos.length === 0) {
// Additional check: at least one property should be a simple string literal union (common in constraints)
// This helps distinguish constraints like { dataType: 'a' | 'b' } from complex intersection types
let hasSimpleUnionProperty = false;
for (const targetProp of targetProperties) {
const propType = getTypeOfSymbol(targetProp);
if (propType.flags & TypeFlags.Union) {
const unionType = propType as UnionType;
// Check if it's a union of string literals (typical of enum-like constraints)
if (unionType.types.length <= 5 && unionType.types.every(t => t.flags & TypeFlags.StringLiteral)) {
hasSimpleUnionProperty = true;
break;
}
}
}
if (hasSimpleUnionProperty) {
// Check if all properties in the target exist in the source
let allTargetPropsExist = true;
for (const targetProp of targetProperties) {
if (!source.symbol?.members?.has(targetProp.escapedName)) {
allTargetPropsExist = false;
break;
}
}
// If the source contains all target properties, likely this is a constraint scenario
if (allTargetPropsExist) {
return false;
}
}
}
}
let reducedTarget = target;
let checkTypes: Type[] | undefined;
if (target.flags & TypeFlags.Union) {
@ -34322,10 +34281,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (targetType.flags & TypeFlags.Substitution) {
return isKnownProperty((targetType as SubstitutionType).baseType, name, isComparingJsxAttributes);
}
if (targetType.flags & TypeFlags.TypeParameter) {
const constraint = getConstraintOfTypeParameter(targetType as TypeParameter);
return constraint ? isKnownProperty(constraint, name, isComparingJsxAttributes) : false;
}
if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) {
for (const t of (targetType as UnionOrIntersectionType).types) {
if (isKnownProperty(t, name, isComparingJsxAttributes)) {

View File

@ -1,50 +0,0 @@
//// [tests/cases/compiler/destructuringAssignmentWithConstraints.ts] ////
//// [destructuringAssignmentWithConstraints.ts]
// Test case for destructuring assignment with generic constraints issue
type DataType = 'a' | 'b';
declare function foo<T extends { dataType: DataType }>(template: T): [T, any, any];
declare function bar<T extends { dataType: DataType }>(template: T): [T, any];
function testDestructuringBug() {
// These work fine (and should continue to work)
const [, ,] = foo({ dataType: 'a', day: 0 });
const [x, y, z] = foo({ dataType: 'a', day: 0 });
const [,] = bar({ dataType: 'a', day: 0 });
const [a, b] = bar({ dataType: 'a', day: 0 });
// These should work but currently don't (this is the bug)
const [, , t] = foo({ dataType: 'a', day: 0 }); // Should not error
const [, u] = bar({ dataType: 'a', day: 0 }); // Should not error
console.log(x, y, z, t, a, b, u);
}
// Test that direct calls work fine (they do)
function testDirectCalls() {
const result1 = foo({ dataType: 'a', day: 0 });
const result2 = bar({ dataType: 'a', day: 0 });
console.log(result1, result2);
}
//// [destructuringAssignmentWithConstraints.js]
// Test case for destructuring assignment with generic constraints issue
function testDestructuringBug() {
// These work fine (and should continue to work)
var _a = foo({ dataType: 'a', day: 0 });
var _b = foo({ dataType: 'a', day: 0 }), x = _b[0], y = _b[1], z = _b[2];
var _c = bar({ dataType: 'a', day: 0 });
var _d = bar({ dataType: 'a', day: 0 }), a = _d[0], b = _d[1];
// These should work but currently don't (this is the bug)
var _e = foo({ dataType: 'a', day: 0 }), t = _e[2]; // Should not error
var _f = bar({ dataType: 'a', day: 0 }), u = _f[1]; // Should not error
console.log(x, y, z, t, a, b, u);
}
// Test that direct calls work fine (they do)
function testDirectCalls() {
var result1 = foo({ dataType: 'a', day: 0 });
var result2 = bar({ dataType: 'a', day: 0 });
console.log(result1, result2);
}

View File

@ -1,104 +0,0 @@
//// [tests/cases/compiler/destructuringAssignmentWithConstraints.ts] ////
=== destructuringAssignmentWithConstraints.ts ===
// Test case for destructuring assignment with generic constraints issue
type DataType = 'a' | 'b';
>DataType : Symbol(DataType, Decl(destructuringAssignmentWithConstraints.ts, 0, 0))
declare function foo<T extends { dataType: DataType }>(template: T): [T, any, any];
>foo : Symbol(foo, Decl(destructuringAssignmentWithConstraints.ts, 2, 26))
>T : Symbol(T, Decl(destructuringAssignmentWithConstraints.ts, 4, 21))
>dataType : Symbol(dataType, Decl(destructuringAssignmentWithConstraints.ts, 4, 32))
>DataType : Symbol(DataType, Decl(destructuringAssignmentWithConstraints.ts, 0, 0))
>template : Symbol(template, Decl(destructuringAssignmentWithConstraints.ts, 4, 55))
>T : Symbol(T, Decl(destructuringAssignmentWithConstraints.ts, 4, 21))
>T : Symbol(T, Decl(destructuringAssignmentWithConstraints.ts, 4, 21))
declare function bar<T extends { dataType: DataType }>(template: T): [T, any];
>bar : Symbol(bar, Decl(destructuringAssignmentWithConstraints.ts, 4, 83))
>T : Symbol(T, Decl(destructuringAssignmentWithConstraints.ts, 5, 21))
>dataType : Symbol(dataType, Decl(destructuringAssignmentWithConstraints.ts, 5, 32))
>DataType : Symbol(DataType, Decl(destructuringAssignmentWithConstraints.ts, 0, 0))
>template : Symbol(template, Decl(destructuringAssignmentWithConstraints.ts, 5, 55))
>T : Symbol(T, Decl(destructuringAssignmentWithConstraints.ts, 5, 21))
>T : Symbol(T, Decl(destructuringAssignmentWithConstraints.ts, 5, 21))
function testDestructuringBug() {
>testDestructuringBug : Symbol(testDestructuringBug, Decl(destructuringAssignmentWithConstraints.ts, 5, 78))
// These work fine (and should continue to work)
const [, ,] = foo({ dataType: 'a', day: 0 });
>foo : Symbol(foo, Decl(destructuringAssignmentWithConstraints.ts, 2, 26))
>dataType : Symbol(dataType, Decl(destructuringAssignmentWithConstraints.ts, 9, 21))
>day : Symbol(day, Decl(destructuringAssignmentWithConstraints.ts, 9, 36))
const [x, y, z] = foo({ dataType: 'a', day: 0 });
>x : Symbol(x, Decl(destructuringAssignmentWithConstraints.ts, 10, 9))
>y : Symbol(y, Decl(destructuringAssignmentWithConstraints.ts, 10, 11))
>z : Symbol(z, Decl(destructuringAssignmentWithConstraints.ts, 10, 14))
>foo : Symbol(foo, Decl(destructuringAssignmentWithConstraints.ts, 2, 26))
>dataType : Symbol(dataType, Decl(destructuringAssignmentWithConstraints.ts, 10, 25))
>day : Symbol(day, Decl(destructuringAssignmentWithConstraints.ts, 10, 40))
const [,] = bar({ dataType: 'a', day: 0 });
>bar : Symbol(bar, Decl(destructuringAssignmentWithConstraints.ts, 4, 83))
>dataType : Symbol(dataType, Decl(destructuringAssignmentWithConstraints.ts, 11, 19))
>day : Symbol(day, Decl(destructuringAssignmentWithConstraints.ts, 11, 34))
const [a, b] = bar({ dataType: 'a', day: 0 });
>a : Symbol(a, Decl(destructuringAssignmentWithConstraints.ts, 12, 9))
>b : Symbol(b, Decl(destructuringAssignmentWithConstraints.ts, 12, 11))
>bar : Symbol(bar, Decl(destructuringAssignmentWithConstraints.ts, 4, 83))
>dataType : Symbol(dataType, Decl(destructuringAssignmentWithConstraints.ts, 12, 22))
>day : Symbol(day, Decl(destructuringAssignmentWithConstraints.ts, 12, 37))
// These should work but currently don't (this is the bug)
const [, , t] = foo({ dataType: 'a', day: 0 }); // Should not error
>t : Symbol(t, Decl(destructuringAssignmentWithConstraints.ts, 15, 12))
>foo : Symbol(foo, Decl(destructuringAssignmentWithConstraints.ts, 2, 26))
>dataType : Symbol(dataType, Decl(destructuringAssignmentWithConstraints.ts, 15, 23))
>day : Symbol(day, Decl(destructuringAssignmentWithConstraints.ts, 15, 38))
const [, u] = bar({ dataType: 'a', day: 0 }); // Should not error
>u : Symbol(u, Decl(destructuringAssignmentWithConstraints.ts, 16, 10))
>bar : Symbol(bar, Decl(destructuringAssignmentWithConstraints.ts, 4, 83))
>dataType : Symbol(dataType, Decl(destructuringAssignmentWithConstraints.ts, 16, 21))
>day : Symbol(day, Decl(destructuringAssignmentWithConstraints.ts, 16, 36))
console.log(x, y, z, t, a, b, u);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>x : Symbol(x, Decl(destructuringAssignmentWithConstraints.ts, 10, 9))
>y : Symbol(y, Decl(destructuringAssignmentWithConstraints.ts, 10, 11))
>z : Symbol(z, Decl(destructuringAssignmentWithConstraints.ts, 10, 14))
>t : Symbol(t, Decl(destructuringAssignmentWithConstraints.ts, 15, 12))
>a : Symbol(a, Decl(destructuringAssignmentWithConstraints.ts, 12, 9))
>b : Symbol(b, Decl(destructuringAssignmentWithConstraints.ts, 12, 11))
>u : Symbol(u, Decl(destructuringAssignmentWithConstraints.ts, 16, 10))
}
// Test that direct calls work fine (they do)
function testDirectCalls() {
>testDirectCalls : Symbol(testDirectCalls, Decl(destructuringAssignmentWithConstraints.ts, 19, 1))
const result1 = foo({ dataType: 'a', day: 0 });
>result1 : Symbol(result1, Decl(destructuringAssignmentWithConstraints.ts, 23, 7))
>foo : Symbol(foo, Decl(destructuringAssignmentWithConstraints.ts, 2, 26))
>dataType : Symbol(dataType, Decl(destructuringAssignmentWithConstraints.ts, 23, 23))
>day : Symbol(day, Decl(destructuringAssignmentWithConstraints.ts, 23, 38))
const result2 = bar({ dataType: 'a', day: 0 });
>result2 : Symbol(result2, Decl(destructuringAssignmentWithConstraints.ts, 24, 7))
>bar : Symbol(bar, Decl(destructuringAssignmentWithConstraints.ts, 4, 83))
>dataType : Symbol(dataType, Decl(destructuringAssignmentWithConstraints.ts, 24, 23))
>day : Symbol(day, Decl(destructuringAssignmentWithConstraints.ts, 24, 38))
console.log(result1, result2);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>result1 : Symbol(result1, Decl(destructuringAssignmentWithConstraints.ts, 23, 7))
>result2 : Symbol(result2, Decl(destructuringAssignmentWithConstraints.ts, 24, 7))
}

View File

@ -1,228 +0,0 @@
//// [tests/cases/compiler/destructuringAssignmentWithConstraints.ts] ////
=== destructuringAssignmentWithConstraints.ts ===
// Test case for destructuring assignment with generic constraints issue
type DataType = 'a' | 'b';
>DataType : DataType
> : ^^^^^^^^
declare function foo<T extends { dataType: DataType }>(template: T): [T, any, any];
>foo : <T extends { dataType: DataType; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>dataType : DataType
> : ^^^^^^^^
>template : T
> : ^
declare function bar<T extends { dataType: DataType }>(template: T): [T, any];
>bar : <T extends { dataType: DataType; }>(template: T) => [T, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>dataType : DataType
> : ^^^^^^^^
>template : T
> : ^
function testDestructuringBug() {
>testDestructuringBug : () => void
> : ^^^^^^^^^^
// These work fine (and should continue to work)
const [, ,] = foo({ dataType: 'a', day: 0 });
> : undefined
> : ^^^^^^^^^
> : undefined
> : ^^^^^^^^^
>foo({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo : <T extends { dataType: DataType; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^
const [x, y, z] = foo({ dataType: 'a', day: 0 });
>x : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>y : any
> : ^^^
>z : any
> : ^^^
>foo({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo : <T extends { dataType: DataType; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^
const [,] = bar({ dataType: 'a', day: 0 });
> : undefined
> : ^^^^^^^^^
>bar({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>bar : <T extends { dataType: DataType; }>(template: T) => [T, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^
const [a, b] = bar({ dataType: 'a', day: 0 });
>a : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>b : any
> : ^^^
>bar({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>bar : <T extends { dataType: DataType; }>(template: T) => [T, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^
// These should work but currently don't (this is the bug)
const [, , t] = foo({ dataType: 'a', day: 0 }); // Should not error
> : undefined
> : ^^^^^^^^^
> : undefined
> : ^^^^^^^^^
>t : any
> : ^^^
>foo({ dataType: 'a', day: 0 }) : [{ dataType: DataType; }, any, any]
> : ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
>foo : <T extends { dataType: DataType; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^
const [, u] = bar({ dataType: 'a', day: 0 }); // Should not error
> : undefined
> : ^^^^^^^^^
>u : any
> : ^^^
>bar({ dataType: 'a', day: 0 }) : [{ dataType: DataType; }, any]
> : ^^^^^^^^^^^^^ ^^^^^^^^^
>bar : <T extends { dataType: DataType; }>(template: T) => [T, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^
console.log(x, y, z, t, a, b, u);
>console.log(x, y, z, t, a, b, u) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>x : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>y : any
>z : any
>t : any
>a : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>b : any
>u : any
}
// Test that direct calls work fine (they do)
function testDirectCalls() {
>testDirectCalls : () => void
> : ^^^^^^^^^^
const result1 = foo({ dataType: 'a', day: 0 });
>result1 : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo : <T extends { dataType: DataType; }>(template: T) => [T, any, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^
const result2 = bar({ dataType: 'a', day: 0 });
>result2 : [{ dataType: "a"; day: number; }, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>bar({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>bar : <T extends { dataType: DataType; }>(template: T) => [T, any]
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>dataType : "a"
> : ^^^
>'a' : "a"
> : ^^^
>day : number
> : ^^^^^^
>0 : 0
> : ^
console.log(result1, result2);
>console.log(result1, result2) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>result1 : [{ dataType: "a"; day: number; }, any, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>result2 : [{ dataType: "a"; day: number; }, any]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

View File

@ -1,27 +0,0 @@
// Test case for destructuring assignment with generic constraints issue
type DataType = 'a' | 'b';
declare function foo<T extends { dataType: DataType }>(template: T): [T, any, any];
declare function bar<T extends { dataType: DataType }>(template: T): [T, any];
function testDestructuringBug() {
// These work fine (and should continue to work)
const [, ,] = foo({ dataType: 'a', day: 0 });
const [x, y, z] = foo({ dataType: 'a', day: 0 });
const [,] = bar({ dataType: 'a', day: 0 });
const [a, b] = bar({ dataType: 'a', day: 0 });
// These should work but currently don't (this is the bug)
const [, , t] = foo({ dataType: 'a', day: 0 }); // Should not error
const [, u] = bar({ dataType: 'a', day: 0 }); // Should not error
console.log(x, y, z, t, a, b, u);
}
// Test that direct calls work fine (they do)
function testDirectCalls() {
const result1 = foo({ dataType: 'a', day: 0 });
const result2 = bar({ dataType: 'a', day: 0 });
console.log(result1, result2);
}