diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 77f35376da785..2ab56123620d3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12334,9 +12334,34 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { 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)); + + let elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors)); + + // For contextual typing, normalize pattern length to avoid inference differences + // based purely on binding name presence/absence + if (includePatternInType && !restElement) { + // Extend patterns to ensure consistent contextual types across equivalent destructuring operations + const lastBindingIndex = findLastIndex(elements, e => !isOmittedExpression(e), elements.length - 1); + if (lastBindingIndex >= 0) { + // Extend to at least one position beyond the last binding to ensure consistent behavior + // This makes patterns like [, s, ] equivalent to [, s, ,] for contextual typing purposes + const targetLength = lastBindingIndex + 2; + elementTypes = elementTypes.concat(Array(Math.max(0, targetLength - elementTypes.length)).fill(anyType)); + } + } + 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); + 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 { + // Extended elements for contextual typing are optional + return ElementFlags.Optional; + } + }); + let result = createTupleType(elementTypes, elementFlags) as TypeReference; if (includePatternInType) { result = cloneTypeReference(result); diff --git a/tests/baselines/reference/contextualTypeArrayBindingPatternConsistency.js b/tests/baselines/reference/contextualTypeArrayBindingPatternConsistency.js new file mode 100644 index 0000000000000..809b8e767cc49 --- /dev/null +++ b/tests/baselines/reference/contextualTypeArrayBindingPatternConsistency.js @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/contextualTypeArrayBindingPatternConsistency.ts] //// + +//// [contextualTypeArrayBindingPatternConsistency.ts] +type DataType = 'a' | 'b'; +declare function foo(template: T): [T, any, any]; + +// These should behave identically since they call the same function with the same argument +// but use different destructuring patterns + +// Pattern 1: [, , t] - should not have excess property error +const [, , t1] = foo({ dataType: 'a', day: 0 }); + +// Pattern 2: [, s, ] - should not have excess property error +const [, s1, ] = foo({ dataType: 'a', day: 0 }); + +// Both patterns should allow the excess property because they produce consistent contextual types +// that don't interfere with generic type inference + +// Additional test cases to ensure the fix is general +const [, s2, ] = foo({ dataType: 'b', extra: 'test' }); // [, s, ] pattern with different property +const [, , s3] = foo({ dataType: 'a', another: 1 }); // [, , s] pattern + +//// [contextualTypeArrayBindingPatternConsistency.js] +"use strict"; +// These should behave identically since they call the same function with the same argument +// but use different destructuring patterns +// Pattern 1: [, , t] - should not have excess property error +var _a = foo({ dataType: 'a', day: 0 }), t1 = _a[2]; +// Pattern 2: [, s, ] - should not have excess property error +var _b = foo({ dataType: 'a', day: 0 }), s1 = _b[1]; +// Both patterns should allow the excess property because they produce consistent contextual types +// that don't interfere with generic type inference +// Additional test cases to ensure the fix is general +var _c = foo({ dataType: 'b', extra: 'test' }), s2 = _c[1]; // [, s, ] pattern with different property +var _d = foo({ dataType: 'a', another: 1 }), s3 = _d[2]; // [, , s] pattern diff --git a/tests/baselines/reference/contextualTypeArrayBindingPatternConsistency.symbols b/tests/baselines/reference/contextualTypeArrayBindingPatternConsistency.symbols new file mode 100644 index 0000000000000..426e60a784bc0 --- /dev/null +++ b/tests/baselines/reference/contextualTypeArrayBindingPatternConsistency.symbols @@ -0,0 +1,48 @@ +//// [tests/cases/compiler/contextualTypeArrayBindingPatternConsistency.ts] //// + +=== contextualTypeArrayBindingPatternConsistency.ts === +type DataType = 'a' | 'b'; +>DataType : Symbol(DataType, Decl(contextualTypeArrayBindingPatternConsistency.ts, 0, 0)) + +declare function foo(template: T): [T, any, any]; +>foo : Symbol(foo, Decl(contextualTypeArrayBindingPatternConsistency.ts, 0, 26)) +>T : Symbol(T, Decl(contextualTypeArrayBindingPatternConsistency.ts, 1, 21)) +>dataType : Symbol(dataType, Decl(contextualTypeArrayBindingPatternConsistency.ts, 1, 32)) +>DataType : Symbol(DataType, Decl(contextualTypeArrayBindingPatternConsistency.ts, 0, 0)) +>template : Symbol(template, Decl(contextualTypeArrayBindingPatternConsistency.ts, 1, 55)) +>T : Symbol(T, Decl(contextualTypeArrayBindingPatternConsistency.ts, 1, 21)) +>T : Symbol(T, Decl(contextualTypeArrayBindingPatternConsistency.ts, 1, 21)) + +// These should behave identically since they call the same function with the same argument +// but use different destructuring patterns + +// Pattern 1: [, , t] - should not have excess property error +const [, , t1] = foo({ dataType: 'a', day: 0 }); +>t1 : Symbol(t1, Decl(contextualTypeArrayBindingPatternConsistency.ts, 7, 10)) +>foo : Symbol(foo, Decl(contextualTypeArrayBindingPatternConsistency.ts, 0, 26)) +>dataType : Symbol(dataType, Decl(contextualTypeArrayBindingPatternConsistency.ts, 7, 22)) +>day : Symbol(day, Decl(contextualTypeArrayBindingPatternConsistency.ts, 7, 37)) + +// Pattern 2: [, s, ] - should not have excess property error +const [, s1, ] = foo({ dataType: 'a', day: 0 }); +>s1 : Symbol(s1, Decl(contextualTypeArrayBindingPatternConsistency.ts, 10, 8)) +>foo : Symbol(foo, Decl(contextualTypeArrayBindingPatternConsistency.ts, 0, 26)) +>dataType : Symbol(dataType, Decl(contextualTypeArrayBindingPatternConsistency.ts, 10, 22)) +>day : Symbol(day, Decl(contextualTypeArrayBindingPatternConsistency.ts, 10, 37)) + +// Both patterns should allow the excess property because they produce consistent contextual types +// that don't interfere with generic type inference + +// Additional test cases to ensure the fix is general +const [, s2, ] = foo({ dataType: 'b', extra: 'test' }); // [, s, ] pattern with different property +>s2 : Symbol(s2, Decl(contextualTypeArrayBindingPatternConsistency.ts, 16, 8)) +>foo : Symbol(foo, Decl(contextualTypeArrayBindingPatternConsistency.ts, 0, 26)) +>dataType : Symbol(dataType, Decl(contextualTypeArrayBindingPatternConsistency.ts, 16, 22)) +>extra : Symbol(extra, Decl(contextualTypeArrayBindingPatternConsistency.ts, 16, 37)) + +const [, , s3] = foo({ dataType: 'a', another: 1 }); // [, , s] pattern +>s3 : Symbol(s3, Decl(contextualTypeArrayBindingPatternConsistency.ts, 17, 10)) +>foo : Symbol(foo, Decl(contextualTypeArrayBindingPatternConsistency.ts, 0, 26)) +>dataType : Symbol(dataType, Decl(contextualTypeArrayBindingPatternConsistency.ts, 17, 22)) +>another : Symbol(another, Decl(contextualTypeArrayBindingPatternConsistency.ts, 17, 37)) + diff --git a/tests/baselines/reference/contextualTypeArrayBindingPatternConsistency.types b/tests/baselines/reference/contextualTypeArrayBindingPatternConsistency.types new file mode 100644 index 0000000000000..f8ff000958857 --- /dev/null +++ b/tests/baselines/reference/contextualTypeArrayBindingPatternConsistency.types @@ -0,0 +1,108 @@ +//// [tests/cases/compiler/contextualTypeArrayBindingPatternConsistency.ts] //// + +=== contextualTypeArrayBindingPatternConsistency.ts === +type DataType = 'a' | 'b'; +>DataType : DataType +> : ^^^^^^^^ + +declare function foo(template: T): [T, any, any]; +>foo : (template: T) => [T, any, any] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>dataType : DataType +> : ^^^^^^^^ +>template : T +> : ^ + +// These should behave identically since they call the same function with the same argument +// but use different destructuring patterns + +// Pattern 1: [, , t] - should not have excess property error +const [, , t1] = foo({ dataType: 'a', day: 0 }); +> : undefined +> : ^^^^^^^^^ +> : undefined +> : ^^^^^^^^^ +>t1 : 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 +> : ^ + +// Pattern 2: [, s, ] - should not have excess property error +const [, s1, ] = foo({ dataType: 'a', day: 0 }); +> : undefined +> : ^^^^^^^^^ +>s1 : 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 +> : ^ + +// Both patterns should allow the excess property because they produce consistent contextual types +// that don't interfere with generic type inference + +// Additional test cases to ensure the fix is general +const [, s2, ] = foo({ dataType: 'b', extra: 'test' }); // [, s, ] pattern with different property +> : undefined +> : ^^^^^^^^^ +>s2 : any +> : ^^^ +>foo({ dataType: 'b', extra: 'test' }) : [{ dataType: "b"; extra: string; }, any, any] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : (template: T) => [T, any, any] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>{ dataType: 'b', extra: 'test' } : { dataType: "b"; extra: string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>dataType : "b" +> : ^^^ +>'b' : "b" +> : ^^^ +>extra : string +> : ^^^^^^ +>'test' : "test" +> : ^^^^^^ + +const [, , s3] = foo({ dataType: 'a', another: 1 }); // [, , s] pattern +> : undefined +> : ^^^^^^^^^ +> : undefined +> : ^^^^^^^^^ +>s3 : any +> : ^^^ +>foo({ dataType: 'a', another: 1 }) : [{ dataType: "a"; another: number; }, any, any] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>foo : (template: T) => [T, any, any] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>{ dataType: 'a', another: 1 } : { dataType: "a"; another: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>dataType : "a" +> : ^^^ +>'a' : "a" +> : ^^^ +>another : number +> : ^^^^^^ +>1 : 1 +> : ^ + diff --git a/tests/cases/compiler/contextualTypeArrayBindingPatternConsistency.ts b/tests/cases/compiler/contextualTypeArrayBindingPatternConsistency.ts new file mode 100644 index 0000000000000..0685d4a4dc24e --- /dev/null +++ b/tests/cases/compiler/contextualTypeArrayBindingPatternConsistency.ts @@ -0,0 +1,20 @@ +// @strict: true + +type DataType = 'a' | 'b'; +declare function foo(template: T): [T, any, any]; + +// These should behave identically since they call the same function with the same argument +// but use different destructuring patterns + +// Pattern 1: [, , t] - should not have excess property error +const [, , t1] = foo({ dataType: 'a', day: 0 }); + +// Pattern 2: [, s, ] - should not have excess property error +const [, s1, ] = foo({ dataType: 'a', day: 0 }); + +// Both patterns should allow the excess property because they produce consistent contextual types +// that don't interfere with generic type inference + +// Additional test cases to ensure the fix is general +const [, s2, ] = foo({ dataType: 'b', extra: 'test' }); // [, s, ] pattern with different property +const [, , s3] = foo({ dataType: 'a', another: 1 }); // [, , s] pattern \ No newline at end of file diff --git a/tests/cases/compiler/contextualTypeDestructuringPositionSensitivity.ts b/tests/cases/compiler/contextualTypeDestructuringPositionSensitivity.ts new file mode 100644 index 0000000000000..98438edb78517 --- /dev/null +++ b/tests/cases/compiler/contextualTypeDestructuringPositionSensitivity.ts @@ -0,0 +1,8 @@ +// @strict: true + +type DataType = 'a' | 'b'; +declare function foo(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 }); \ No newline at end of file