diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cf55019b980..e5f75c29e67 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22893,19 +22893,56 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean { if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) { return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny - } - const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - if ( - (relation === assignableRelation || relation === comparableRelation) && - (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target))) - ) { - return false; - } - let reducedTarget = target; - let checkTypes: Type[] | undefined; - if (target.flags & TypeFlags.Union) { - reducedTarget = findMatchingDiscriminantType(source, target as UnionType, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(target as UnionType); - checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget as UnionType).types : [reducedTarget]; + } + const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); + if ( + (relation === assignableRelation || relation === comparableRelation) && + (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target))) + ) { + 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 <= 3 && targetIndexInfos.length === 0) { + // Additional check: at least one property should be a union type (common in constraints) + // This helps distinguish constraints like { dataType: 'a' | 'b' } from regular types like { a: string } + let hasUnionTypeProperty = false; + for (const targetProp of targetProperties) { + const propType = getTypeOfSymbol(targetProp); + if (propType.flags & TypeFlags.Union) { + hasUnionTypeProperty = true; + break; + } + } + + if (hasUnionTypeProperty) { + // 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) { + reducedTarget = findMatchingDiscriminantType(source, target as UnionType, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(target as UnionType); + checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget as UnionType).types : [reducedTarget]; } for (const prop of getPropertiesOfType(source)) { if (shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop)) { diff --git a/tests/baselines/reference/destructuringAssignmentWithConstraints.js b/tests/baselines/reference/destructuringAssignmentWithConstraints.js new file mode 100644 index 00000000000..1f73e4750ec --- /dev/null +++ b/tests/baselines/reference/destructuringAssignmentWithConstraints.js @@ -0,0 +1,50 @@ +//// [tests/cases/compiler/destructuringAssignmentWithConstraints.ts] //// + +//// [destructuringAssignmentWithConstraints.ts] +// Test case for destructuring assignment with generic constraints issue + +type DataType = 'a' | 'b'; + +declare function foo(template: T): [T, any, any]; +declare function bar(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); +} diff --git a/tests/baselines/reference/destructuringAssignmentWithConstraints.symbols b/tests/baselines/reference/destructuringAssignmentWithConstraints.symbols new file mode 100644 index 00000000000..18eb42dbf89 --- /dev/null +++ b/tests/baselines/reference/destructuringAssignmentWithConstraints.symbols @@ -0,0 +1,104 @@ +//// [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(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(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)) +} diff --git a/tests/baselines/reference/destructuringAssignmentWithConstraints.types b/tests/baselines/reference/destructuringAssignmentWithConstraints.types new file mode 100644 index 00000000000..5a2daba7cb5 --- /dev/null +++ b/tests/baselines/reference/destructuringAssignmentWithConstraints.types @@ -0,0 +1,228 @@ +//// [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(template: T): [T, any, any]; +>foo : (template: T) => [T, any, any] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>dataType : DataType +> : ^^^^^^^^ +>template : T +> : ^ + +declare function bar(template: T): [T, any]; +>bar : (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 : (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 : (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 : (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 : (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 : (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 : (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 : (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 : (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] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +}