mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-03 18:38:40 -06:00
Fix excess property checking when accessing last tuple element in destructuring
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
parent
1f1fe0c217
commit
dd265074c6
@ -31857,10 +31857,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const parentType = getContextualTypeForVariableLikeDeclaration(parent, contextFlags) ||
|
||||
parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent, declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal);
|
||||
if (!parentType || isBindingPattern(name) || isComputedNonLiteralName(name)) return undefined;
|
||||
if (parent.name.kind === SyntaxKind.ArrayBindingPattern) {
|
||||
const index = indexOfNode(declaration.parent.elements, declaration);
|
||||
if (index < 0) return undefined;
|
||||
return getContextualTypeForElementExpression(parentType, index, declaration.parent.elements.length);
|
||||
if (parent.name.kind === SyntaxKind.ArrayBindingPattern) {
|
||||
const index = indexOfNode(declaration.parent.elements, declaration);
|
||||
if (index < 0) return undefined;
|
||||
return getContextualTypeForElementExpression(parentType, index, declaration.parent.elements.length);
|
||||
}
|
||||
const nameType = getLiteralTypeFromPropertyName(name);
|
||||
if (isTypeUsableAsPropertyName(nameType)) {
|
||||
@ -32391,25 +32391,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return { first, last };
|
||||
}
|
||||
|
||||
function getContextualTypeForElementExpression(type: Type | undefined, index: number, length?: number, firstSpreadIndex?: number, lastSpreadIndex?: number): Type | undefined {
|
||||
return type && mapType(type, t => {
|
||||
if (isTupleType(t)) {
|
||||
// If index is before any spread element and within the fixed part of the contextual tuple type, return
|
||||
// the type of the contextual tuple element.
|
||||
if ((firstSpreadIndex === undefined || index < firstSpreadIndex) && index < t.target.fixedLength) {
|
||||
return removeMissingType(getTypeArguments(t)[index], !!(t.target.elementFlags[index] && ElementFlags.Optional));
|
||||
}
|
||||
// When the length is known and the index is after all spread elements we compute the offset from the element
|
||||
// to the end and the number of ending fixed elements in the contextual tuple type.
|
||||
const offset = length !== undefined && (lastSpreadIndex === undefined || index > lastSpreadIndex) ? length - index : 0;
|
||||
const fixedEndLength = offset > 0 && (t.target.combinedFlags & ElementFlags.Variable) ? getEndElementCount(t.target, ElementFlags.Fixed) : 0;
|
||||
// If the offset is within the ending fixed part of the contextual tuple type, return the type of the contextual
|
||||
// tuple element.
|
||||
if (offset > 0 && offset <= fixedEndLength) {
|
||||
return getTypeArguments(t)[getTypeReferenceArity(t) - offset];
|
||||
}
|
||||
// Return a union of the possible contextual element types with no subtype reduction.
|
||||
return getElementTypeOfSliceOfTupleType(t, firstSpreadIndex === undefined ? t.target.fixedLength : Math.min(t.target.fixedLength, firstSpreadIndex), length === undefined || lastSpreadIndex === undefined ? fixedEndLength : Math.min(fixedEndLength, length - lastSpreadIndex), /*writing*/ false, /*noReductions*/ true);
|
||||
function getContextualTypeForElementExpression(type: Type | undefined, index: number, length?: number, firstSpreadIndex?: number, lastSpreadIndex?: number): Type | undefined {
|
||||
return type && mapType(type, t => {
|
||||
if (isTupleType(t)) {
|
||||
// If index is before any spread element and within the fixed part of the contextual tuple type, return
|
||||
// the type of the contextual tuple element.
|
||||
if ((firstSpreadIndex === undefined || index < firstSpreadIndex) && index < t.target.fixedLength) {
|
||||
return removeMissingType(getTypeArguments(t)[index], !!(t.target.elementFlags[index] && ElementFlags.Optional));
|
||||
}
|
||||
// When the length is known and the index is after all spread elements we compute the offset from the element
|
||||
// to the end and the number of ending fixed elements in the contextual tuple type.
|
||||
const offset = length !== undefined && (lastSpreadIndex === undefined || index > lastSpreadIndex) ? length - index : 0;
|
||||
const fixedEndLength = offset > 0 && (t.target.combinedFlags & ElementFlags.Variable) ? getEndElementCount(t.target, ElementFlags.Fixed) : 0;
|
||||
// If the offset is within the ending fixed part of the contextual tuple type, return the type of the contextual
|
||||
// tuple element.
|
||||
if (offset > 0 && offset <= fixedEndLength) {
|
||||
return getTypeArguments(t)[getTypeReferenceArity(t) - offset];
|
||||
}
|
||||
// Return a union of the possible contextual element types with no subtype reduction.
|
||||
return getElementTypeOfSliceOfTupleType(t, firstSpreadIndex === undefined ? t.target.fixedLength : Math.min(t.target.fixedLength, firstSpreadIndex), length === undefined || lastSpreadIndex === undefined ? fixedEndLength : Math.min(fixedEndLength, length - lastSpreadIndex), /*writing*/ false, /*noReductions*/ true);
|
||||
}
|
||||
// If element index is known and a contextual property with that name exists, return it. Otherwise return the
|
||||
// iterated or element type of the contextual type.
|
||||
@ -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[],
|
||||
@ -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");
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
lastTupleElementDestructuring.ts(18,27): error TS2322: Type '"c"' is not assignable to type '"a" | "b"'.
|
||||
lastTupleElementDestructuring.ts(19,24): error TS2345: Argument of type '{ notDataType: string; }' is not assignable to parameter of type '{ dataType: "a" | "b"; }'.
|
||||
Property 'dataType' is missing in type '{ notDataType: string; }' but required in type '{ dataType: "a" | "b"; }'.
|
||||
lastTupleElementDestructuring.ts(33,34): error TS2345: Argument of type '{ optional: number; }' is not assignable to parameter of type 'Config'.
|
||||
Property 'required' is missing in type '{ optional: number; }' but required in type 'Config'.
|
||||
|
||||
|
||||
==== lastTupleElementDestructuring.ts (3 errors) ====
|
||||
// Test for fixing excess property checking when accessing last tuple element in destructuring
|
||||
// Fixes https://github.com/microsoft/TypeScript/issues/41548
|
||||
|
||||
declare function foo<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any];
|
||||
declare function bar<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any, any];
|
||||
|
||||
// Cases that should NOT error after fix (accessing last element)
|
||||
const [, , last1] = foo({ dataType: 'a', day: 0 });
|
||||
const [,,last2] = foo({ dataType: 'a', day: 0 });
|
||||
const [,,,last3] = bar({ dataType: 'a', day: 0 });
|
||||
|
||||
// Cases that already worked (not accessing last element)
|
||||
const [, mid1, ] = foo({ dataType: 'a', day: 0 });
|
||||
const [first1, , ] = foo({ dataType: 'a', day: 0 });
|
||||
const [,,third,] = bar({ dataType: 'a', day: 0 });
|
||||
|
||||
// Legitimate errors should still be caught
|
||||
const [, , last4] = foo({ dataType: 'c' }); // Error: 'c' not assignable to 'a' | 'b'
|
||||
~~~~~~~~
|
||||
!!! error TS2322: Type '"c"' is not assignable to type '"a" | "b"'.
|
||||
!!! related TS6500 lastTupleElementDestructuring.ts:4:34: The expected type comes from property 'dataType' which is declared here on type '{ dataType: "a" | "b"; }'
|
||||
const [,,,last5] = bar({ notDataType: 'a' }); // Error: missing required property 'dataType'
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
!!! error TS2345: Argument of type '{ notDataType: string; }' is not assignable to parameter of type '{ dataType: "a" | "b"; }'.
|
||||
!!! error TS2345: Property 'dataType' is missing in type '{ notDataType: string; }' but required in type '{ dataType: "a" | "b"; }'.
|
||||
!!! related TS2728 lastTupleElementDestructuring.ts:5:34: 'dataType' is declared here.
|
||||
|
||||
// Test with more complex object properties
|
||||
interface Config {
|
||||
required: string;
|
||||
optional?: number;
|
||||
}
|
||||
|
||||
declare function withConfig<T extends Config>(template: T): [T, string];
|
||||
|
||||
// Should work - accessing last element with extra property
|
||||
const [,configStr] = withConfig({ required: 'test', extra: 'should work' });
|
||||
|
||||
// Should still error - missing required property
|
||||
const [,configStr2] = withConfig({ optional: 42 }); // Error: missing 'required'
|
||||
~~~~~~~~~~~~~~~~
|
||||
!!! error TS2345: Argument of type '{ optional: number; }' is not assignable to parameter of type 'Config'.
|
||||
!!! error TS2345: Property 'required' is missing in type '{ optional: number; }' but required in type 'Config'.
|
||||
!!! related TS2728 lastTupleElementDestructuring.ts:23:5: 'required' is declared here.
|
||||
55
tests/baselines/reference/lastTupleElementDestructuring.js
Normal file
55
tests/baselines/reference/lastTupleElementDestructuring.js
Normal file
@ -0,0 +1,55 @@
|
||||
//// [tests/cases/compiler/lastTupleElementDestructuring.ts] ////
|
||||
|
||||
//// [lastTupleElementDestructuring.ts]
|
||||
// Test for fixing excess property checking when accessing last tuple element in destructuring
|
||||
// Fixes https://github.com/microsoft/TypeScript/issues/41548
|
||||
|
||||
declare function foo<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any];
|
||||
declare function bar<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any, any];
|
||||
|
||||
// Cases that should NOT error after fix (accessing last element)
|
||||
const [, , last1] = foo({ dataType: 'a', day: 0 });
|
||||
const [,,last2] = foo({ dataType: 'a', day: 0 });
|
||||
const [,,,last3] = bar({ dataType: 'a', day: 0 });
|
||||
|
||||
// Cases that already worked (not accessing last element)
|
||||
const [, mid1, ] = foo({ dataType: 'a', day: 0 });
|
||||
const [first1, , ] = foo({ dataType: 'a', day: 0 });
|
||||
const [,,third,] = bar({ dataType: 'a', day: 0 });
|
||||
|
||||
// Legitimate errors should still be caught
|
||||
const [, , last4] = foo({ dataType: 'c' }); // Error: 'c' not assignable to 'a' | 'b'
|
||||
const [,,,last5] = bar({ notDataType: 'a' }); // Error: missing required property 'dataType'
|
||||
|
||||
// Test with more complex object properties
|
||||
interface Config {
|
||||
required: string;
|
||||
optional?: number;
|
||||
}
|
||||
|
||||
declare function withConfig<T extends Config>(template: T): [T, string];
|
||||
|
||||
// Should work - accessing last element with extra property
|
||||
const [,configStr] = withConfig({ required: 'test', extra: 'should work' });
|
||||
|
||||
// Should still error - missing required property
|
||||
const [,configStr2] = withConfig({ optional: 42 }); // Error: missing 'required'
|
||||
|
||||
//// [lastTupleElementDestructuring.js]
|
||||
// Test for fixing excess property checking when accessing last tuple element in destructuring
|
||||
// Fixes https://github.com/microsoft/TypeScript/issues/41548
|
||||
// Cases that should NOT error after fix (accessing last element)
|
||||
var _a = foo({ dataType: 'a', day: 0 }), last1 = _a[2];
|
||||
var _b = foo({ dataType: 'a', day: 0 }), last2 = _b[2];
|
||||
var _c = bar({ dataType: 'a', day: 0 }), last3 = _c[3];
|
||||
// Cases that already worked (not accessing last element)
|
||||
var _d = foo({ dataType: 'a', day: 0 }), mid1 = _d[1];
|
||||
var _e = foo({ dataType: 'a', day: 0 }), first1 = _e[0];
|
||||
var _f = bar({ dataType: 'a', day: 0 }), third = _f[2];
|
||||
// Legitimate errors should still be caught
|
||||
var _g = foo({ dataType: 'c' }), last4 = _g[2]; // Error: 'c' not assignable to 'a' | 'b'
|
||||
var _h = bar({ notDataType: 'a' }), last5 = _h[3]; // Error: missing required property 'dataType'
|
||||
// Should work - accessing last element with extra property
|
||||
var _j = withConfig({ required: 'test', extra: 'should work' }), configStr = _j[1];
|
||||
// Should still error - missing required property
|
||||
var _k = withConfig({ optional: 42 }), configStr2 = _k[1]; // Error: missing 'required'
|
||||
103
tests/baselines/reference/lastTupleElementDestructuring.symbols
Normal file
103
tests/baselines/reference/lastTupleElementDestructuring.symbols
Normal file
@ -0,0 +1,103 @@
|
||||
//// [tests/cases/compiler/lastTupleElementDestructuring.ts] ////
|
||||
|
||||
=== lastTupleElementDestructuring.ts ===
|
||||
// Test for fixing excess property checking when accessing last tuple element in destructuring
|
||||
// Fixes https://github.com/microsoft/TypeScript/issues/41548
|
||||
|
||||
declare function foo<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any];
|
||||
>foo : Symbol(foo, Decl(lastTupleElementDestructuring.ts, 0, 0))
|
||||
>T : Symbol(T, Decl(lastTupleElementDestructuring.ts, 3, 21))
|
||||
>dataType : Symbol(dataType, Decl(lastTupleElementDestructuring.ts, 3, 32))
|
||||
>template : Symbol(template, Decl(lastTupleElementDestructuring.ts, 3, 56))
|
||||
>T : Symbol(T, Decl(lastTupleElementDestructuring.ts, 3, 21))
|
||||
>T : Symbol(T, Decl(lastTupleElementDestructuring.ts, 3, 21))
|
||||
|
||||
declare function bar<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any, any];
|
||||
>bar : Symbol(bar, Decl(lastTupleElementDestructuring.ts, 3, 84))
|
||||
>T : Symbol(T, Decl(lastTupleElementDestructuring.ts, 4, 21))
|
||||
>dataType : Symbol(dataType, Decl(lastTupleElementDestructuring.ts, 4, 32))
|
||||
>template : Symbol(template, Decl(lastTupleElementDestructuring.ts, 4, 56))
|
||||
>T : Symbol(T, Decl(lastTupleElementDestructuring.ts, 4, 21))
|
||||
>T : Symbol(T, Decl(lastTupleElementDestructuring.ts, 4, 21))
|
||||
|
||||
// Cases that should NOT error after fix (accessing last element)
|
||||
const [, , last1] = foo({ dataType: 'a', day: 0 });
|
||||
>last1 : Symbol(last1, Decl(lastTupleElementDestructuring.ts, 7, 10))
|
||||
>foo : Symbol(foo, Decl(lastTupleElementDestructuring.ts, 0, 0))
|
||||
>dataType : Symbol(dataType, Decl(lastTupleElementDestructuring.ts, 7, 25))
|
||||
>day : Symbol(day, Decl(lastTupleElementDestructuring.ts, 7, 40))
|
||||
|
||||
const [,,last2] = foo({ dataType: 'a', day: 0 });
|
||||
>last2 : Symbol(last2, Decl(lastTupleElementDestructuring.ts, 8, 9))
|
||||
>foo : Symbol(foo, Decl(lastTupleElementDestructuring.ts, 0, 0))
|
||||
>dataType : Symbol(dataType, Decl(lastTupleElementDestructuring.ts, 8, 23))
|
||||
>day : Symbol(day, Decl(lastTupleElementDestructuring.ts, 8, 38))
|
||||
|
||||
const [,,,last3] = bar({ dataType: 'a', day: 0 });
|
||||
>last3 : Symbol(last3, Decl(lastTupleElementDestructuring.ts, 9, 10))
|
||||
>bar : Symbol(bar, Decl(lastTupleElementDestructuring.ts, 3, 84))
|
||||
>dataType : Symbol(dataType, Decl(lastTupleElementDestructuring.ts, 9, 24))
|
||||
>day : Symbol(day, Decl(lastTupleElementDestructuring.ts, 9, 39))
|
||||
|
||||
// Cases that already worked (not accessing last element)
|
||||
const [, mid1, ] = foo({ dataType: 'a', day: 0 });
|
||||
>mid1 : Symbol(mid1, Decl(lastTupleElementDestructuring.ts, 12, 8))
|
||||
>foo : Symbol(foo, Decl(lastTupleElementDestructuring.ts, 0, 0))
|
||||
>dataType : Symbol(dataType, Decl(lastTupleElementDestructuring.ts, 12, 24))
|
||||
>day : Symbol(day, Decl(lastTupleElementDestructuring.ts, 12, 39))
|
||||
|
||||
const [first1, , ] = foo({ dataType: 'a', day: 0 });
|
||||
>first1 : Symbol(first1, Decl(lastTupleElementDestructuring.ts, 13, 7))
|
||||
>foo : Symbol(foo, Decl(lastTupleElementDestructuring.ts, 0, 0))
|
||||
>dataType : Symbol(dataType, Decl(lastTupleElementDestructuring.ts, 13, 26))
|
||||
>day : Symbol(day, Decl(lastTupleElementDestructuring.ts, 13, 41))
|
||||
|
||||
const [,,third,] = bar({ dataType: 'a', day: 0 });
|
||||
>third : Symbol(third, Decl(lastTupleElementDestructuring.ts, 14, 9))
|
||||
>bar : Symbol(bar, Decl(lastTupleElementDestructuring.ts, 3, 84))
|
||||
>dataType : Symbol(dataType, Decl(lastTupleElementDestructuring.ts, 14, 24))
|
||||
>day : Symbol(day, Decl(lastTupleElementDestructuring.ts, 14, 39))
|
||||
|
||||
// Legitimate errors should still be caught
|
||||
const [, , last4] = foo({ dataType: 'c' }); // Error: 'c' not assignable to 'a' | 'b'
|
||||
>last4 : Symbol(last4, Decl(lastTupleElementDestructuring.ts, 17, 10))
|
||||
>foo : Symbol(foo, Decl(lastTupleElementDestructuring.ts, 0, 0))
|
||||
>dataType : Symbol(dataType, Decl(lastTupleElementDestructuring.ts, 17, 25))
|
||||
|
||||
const [,,,last5] = bar({ notDataType: 'a' }); // Error: missing required property 'dataType'
|
||||
>last5 : Symbol(last5, Decl(lastTupleElementDestructuring.ts, 18, 10))
|
||||
>bar : Symbol(bar, Decl(lastTupleElementDestructuring.ts, 3, 84))
|
||||
>notDataType : Symbol(notDataType, Decl(lastTupleElementDestructuring.ts, 18, 24))
|
||||
|
||||
// Test with more complex object properties
|
||||
interface Config {
|
||||
>Config : Symbol(Config, Decl(lastTupleElementDestructuring.ts, 18, 45))
|
||||
|
||||
required: string;
|
||||
>required : Symbol(Config.required, Decl(lastTupleElementDestructuring.ts, 21, 18))
|
||||
|
||||
optional?: number;
|
||||
>optional : Symbol(Config.optional, Decl(lastTupleElementDestructuring.ts, 22, 21))
|
||||
}
|
||||
|
||||
declare function withConfig<T extends Config>(template: T): [T, string];
|
||||
>withConfig : Symbol(withConfig, Decl(lastTupleElementDestructuring.ts, 24, 1))
|
||||
>T : Symbol(T, Decl(lastTupleElementDestructuring.ts, 26, 28))
|
||||
>Config : Symbol(Config, Decl(lastTupleElementDestructuring.ts, 18, 45))
|
||||
>template : Symbol(template, Decl(lastTupleElementDestructuring.ts, 26, 46))
|
||||
>T : Symbol(T, Decl(lastTupleElementDestructuring.ts, 26, 28))
|
||||
>T : Symbol(T, Decl(lastTupleElementDestructuring.ts, 26, 28))
|
||||
|
||||
// Should work - accessing last element with extra property
|
||||
const [,configStr] = withConfig({ required: 'test', extra: 'should work' });
|
||||
>configStr : Symbol(configStr, Decl(lastTupleElementDestructuring.ts, 29, 8))
|
||||
>withConfig : Symbol(withConfig, Decl(lastTupleElementDestructuring.ts, 24, 1))
|
||||
>required : Symbol(required, Decl(lastTupleElementDestructuring.ts, 29, 33))
|
||||
>extra : Symbol(extra, Decl(lastTupleElementDestructuring.ts, 29, 51))
|
||||
|
||||
// Should still error - missing required property
|
||||
const [,configStr2] = withConfig({ optional: 42 }); // Error: missing 'required'
|
||||
>configStr2 : Symbol(configStr2, Decl(lastTupleElementDestructuring.ts, 32, 8))
|
||||
>withConfig : Symbol(withConfig, Decl(lastTupleElementDestructuring.ts, 24, 1))
|
||||
>optional : Symbol(optional, Decl(lastTupleElementDestructuring.ts, 32, 34))
|
||||
|
||||
248
tests/baselines/reference/lastTupleElementDestructuring.types
Normal file
248
tests/baselines/reference/lastTupleElementDestructuring.types
Normal file
@ -0,0 +1,248 @@
|
||||
//// [tests/cases/compiler/lastTupleElementDestructuring.ts] ////
|
||||
|
||||
=== lastTupleElementDestructuring.ts ===
|
||||
// Test for fixing excess property checking when accessing last tuple element in destructuring
|
||||
// Fixes https://github.com/microsoft/TypeScript/issues/41548
|
||||
|
||||
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
|
||||
> : ^
|
||||
|
||||
declare function bar<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any, any];
|
||||
>bar : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any, any]
|
||||
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
|
||||
>dataType : "a" | "b"
|
||||
> : ^^^^^^^^^
|
||||
>template : T
|
||||
> : ^
|
||||
|
||||
// Cases that should NOT error after fix (accessing last element)
|
||||
const [, , last1] = foo({ dataType: 'a', day: 0 });
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
>last1 : 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 [,,last2] = foo({ dataType: 'a', day: 0 });
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
>last2 : 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 [,,,last3] = bar({ dataType: 'a', day: 0 });
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
>last3 : any
|
||||
> : ^^^
|
||||
>bar({ dataType: 'a', day: 0 }) : [{ dataType: "a" | "b"; }, any, any, any]
|
||||
> : ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
|
||||
>bar : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any, any]
|
||||
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
|
||||
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>dataType : "a"
|
||||
> : ^^^
|
||||
>'a' : "a"
|
||||
> : ^^^
|
||||
>day : number
|
||||
> : ^^^^^^
|
||||
>0 : 0
|
||||
> : ^
|
||||
|
||||
// Cases that already worked (not accessing last element)
|
||||
const [, mid1, ] = foo({ dataType: 'a', day: 0 });
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
>mid1 : 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 [first1, , ] = foo({ dataType: 'a', day: 0 });
|
||||
>first1 : { dataType: "a"; day: number; }
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
>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 [,,third,] = bar({ dataType: 'a', day: 0 });
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
>third : any
|
||||
> : ^^^
|
||||
>bar({ dataType: 'a', day: 0 }) : [{ dataType: "a"; day: number; }, any, any, any]
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>bar : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any, any]
|
||||
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
|
||||
>{ dataType: 'a', day: 0 } : { dataType: "a"; day: number; }
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>dataType : "a"
|
||||
> : ^^^
|
||||
>'a' : "a"
|
||||
> : ^^^
|
||||
>day : number
|
||||
> : ^^^^^^
|
||||
>0 : 0
|
||||
> : ^
|
||||
|
||||
// Legitimate errors should still be caught
|
||||
const [, , last4] = foo({ dataType: 'c' }); // Error: 'c' not assignable to 'a' | 'b'
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
>last4 : 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 [,,,last5] = bar({ notDataType: 'a' }); // Error: missing required property 'dataType'
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
>last5 : any
|
||||
> : ^^^
|
||||
>bar({ notDataType: 'a' }) : [{ dataType: "a" | "b"; }, any, any, any]
|
||||
> : ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
|
||||
>bar : <T extends { dataType: "a" | "b"; }>(template: T) => [T, any, any, any]
|
||||
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
|
||||
>{ notDataType: 'a' } : { notDataType: string; }
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>notDataType : string
|
||||
> : ^^^^^^
|
||||
>'a' : "a"
|
||||
> : ^^^
|
||||
|
||||
// Test with more complex object properties
|
||||
interface Config {
|
||||
required: string;
|
||||
>required : string
|
||||
> : ^^^^^^
|
||||
|
||||
optional?: number;
|
||||
>optional : number
|
||||
> : ^^^^^^
|
||||
}
|
||||
|
||||
declare function withConfig<T extends Config>(template: T): [T, string];
|
||||
>withConfig : <T extends Config>(template: T) => [T, string]
|
||||
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
|
||||
>template : T
|
||||
> : ^
|
||||
|
||||
// Should work - accessing last element with extra property
|
||||
const [,configStr] = withConfig({ required: 'test', extra: 'should work' });
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
>configStr : string
|
||||
> : ^^^^^^
|
||||
>withConfig({ required: 'test', extra: 'should work' }) : [{ required: string; extra: string; }, string]
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>withConfig : <T extends Config>(template: T) => [T, string]
|
||||
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
|
||||
>{ required: 'test', extra: 'should work' } : { required: string; extra: string; }
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>required : string
|
||||
> : ^^^^^^
|
||||
>'test' : "test"
|
||||
> : ^^^^^^
|
||||
>extra : string
|
||||
> : ^^^^^^
|
||||
>'should work' : "should work"
|
||||
> : ^^^^^^^^^^^^^
|
||||
|
||||
// Should still error - missing required property
|
||||
const [,configStr2] = withConfig({ optional: 42 }); // Error: missing 'required'
|
||||
> : undefined
|
||||
> : ^^^^^^^^^
|
||||
>configStr2 : string
|
||||
> : ^^^^^^
|
||||
>withConfig({ optional: 42 }) : [Config, string]
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
>withConfig : <T extends Config>(template: T) => [T, string]
|
||||
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
|
||||
>{ optional: 42 } : { optional: number; }
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^
|
||||
>optional : number
|
||||
> : ^^^^^^
|
||||
>42 : 42
|
||||
> : ^^
|
||||
|
||||
@ -1,11 +1,33 @@
|
||||
// Test for fixing excess property checking when accessing last tuple element in destructuring
|
||||
// Fixes https://github.com/microsoft/TypeScript/issues/41548
|
||||
|
||||
declare function foo<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any];
|
||||
declare function bar<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any, any];
|
||||
|
||||
// This should NOT error after fix
|
||||
const [, , last] = foo({ dataType: 'a', day: 0 });
|
||||
// Cases that should NOT error after fix (accessing last element)
|
||||
const [, , last1] = foo({ dataType: 'a', day: 0 });
|
||||
const [,,last2] = foo({ dataType: 'a', day: 0 });
|
||||
const [,,,last3] = bar({ dataType: 'a', day: 0 });
|
||||
|
||||
// This already works (doesn't access last element)
|
||||
const [, mid, ] = foo({ dataType: 'a', day: 0 });
|
||||
// Cases that already worked (not accessing last element)
|
||||
const [, mid1, ] = foo({ dataType: 'a', day: 0 });
|
||||
const [first1, , ] = foo({ dataType: 'a', day: 0 });
|
||||
const [,,third,] = bar({ dataType: 'a', day: 0 });
|
||||
|
||||
// Also test that legitimate errors are still caught
|
||||
const [, , last2] = foo({ dataType: 'c' }); // Should still error
|
||||
// Legitimate errors should still be caught
|
||||
const [, , last4] = foo({ dataType: 'c' }); // Error: 'c' not assignable to 'a' | 'b'
|
||||
const [,,,last5] = bar({ notDataType: 'a' }); // Error: missing required property 'dataType'
|
||||
|
||||
// Test with more complex object properties
|
||||
interface Config {
|
||||
required: string;
|
||||
optional?: number;
|
||||
}
|
||||
|
||||
declare function withConfig<T extends Config>(template: T): [T, string];
|
||||
|
||||
// Should work - accessing last element with extra property
|
||||
const [,configStr] = withConfig({ required: 'test', extra: 'should work' });
|
||||
|
||||
// Should still error - missing required property
|
||||
const [,configStr2] = withConfig({ optional: 42 }); // Error: missing 'required'
|
||||
@ -1,8 +0,0 @@
|
||||
// Test case to check the fix
|
||||
declare function foo<T extends { dataType: 'a' | 'b' }>(template: T): [T, any, any];
|
||||
|
||||
// Error case - accessing last element
|
||||
const [, , last] = foo({ dataType: 'a', day: 0 });
|
||||
|
||||
// Working case - not accessing last element
|
||||
const [, mid, ] = foo({ dataType: 'a', day: 0 });
|
||||
Loading…
x
Reference in New Issue
Block a user