mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-03 18:38:40 -06:00
Fix contextual typing sensitivity to binding pattern structure without hardcoded values
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
parent
65ba292f29
commit
97de830744
@ -12327,40 +12327,48 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
|
||||
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(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);
|
||||
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
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
//// [tests/cases/compiler/contextualTypeArrayBindingPatternConsistency.ts] ////
|
||||
|
||||
//// [contextualTypeArrayBindingPatternConsistency.ts]
|
||||
type DataType = 'a' | 'b';
|
||||
declare function foo<T extends { dataType: DataType }>(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
|
||||
@ -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<T extends { dataType: DataType }>(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))
|
||||
|
||||
@ -0,0 +1,108 @@
|
||||
//// [tests/cases/compiler/contextualTypeArrayBindingPatternConsistency.ts] ////
|
||||
|
||||
=== contextualTypeArrayBindingPatternConsistency.ts ===
|
||||
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
|
||||
> : ^
|
||||
|
||||
// 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 : <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
|
||||
> : ^
|
||||
|
||||
// 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 : <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
|
||||
> : ^
|
||||
|
||||
// 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 : <T extends { dataType: DataType; }>(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 : <T extends { dataType: DataType; }>(template: T) => [T, any, any]
|
||||
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
|
||||
>{ dataType: 'a', another: 1 } : { dataType: "a"; another: number; }
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>dataType : "a"
|
||||
> : ^^^
|
||||
>'a' : "a"
|
||||
> : ^^^
|
||||
>another : number
|
||||
> : ^^^^^^
|
||||
>1 : 1
|
||||
> : ^
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
// @strict: true
|
||||
|
||||
type DataType = 'a' | 'b';
|
||||
declare function foo<T extends { dataType: DataType }>(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
|
||||
Loading…
x
Reference in New Issue
Block a user