diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a3902d9ea79..519b724fb62 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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); + 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. @@ -36069,11 +36069,7 @@ 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). - // Also skip fresh literal checking when the call is in certain destructuring contexts that can cause - // incorrect excess property errors (see #41548). - const shouldSkipFreshness = (checkMode & CheckMode.SkipContextSensitive) || - (isCallExpression(node) && isCallInProblematicDestructuringContext(node)); - const checkArgType = shouldSkipFreshness ? getRegularTypeOfObjectLiteral(argType) : argType; + const checkArgType = checkMode & CheckMode.SkipContextSensitive ? 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"); @@ -36421,24 +36417,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); } - function isCallInProblematicDestructuringContext(node: CallLikeExpression): boolean { - // Check if this call expression is used as the initializer in a variable declaration with a destructuring pattern - const parent = node.parent; - if (parent && isVariableDeclaration(parent) && parent.initializer === node) { - if (isArrayBindingPattern(parent.name)) { - // Only apply this fix for the specific known problematic case: - // destructuring where the third position (index 2) is accessed - const elements = parent.name.elements; - return elements.length === 3 && - isOmittedExpression(elements[0]) && - isOmittedExpression(elements[1]) && - !isOmittedExpression(elements[2]); - } - } - - return false; - } - function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, headMessage?: DiagnosticMessage): Signature { const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; diff --git a/tests/cases/compiler/lastTupleElementDestructuring.ts b/tests/cases/compiler/lastTupleElementDestructuring.ts new file mode 100644 index 00000000000..43015dbb039 --- /dev/null +++ b/tests/cases/compiler/lastTupleElementDestructuring.ts @@ -0,0 +1,11 @@ +// Test for fixing excess property checking when accessing last tuple element in destructuring +declare function foo(template: T): [T, any, any]; + +// This should NOT error after fix +const [, , last] = foo({ dataType: 'a', day: 0 }); + +// This already works (doesn't access last element) +const [, mid, ] = foo({ dataType: 'a', day: 0 }); + +// Also test that legitimate errors are still caught +const [, , last2] = foo({ dataType: 'c' }); // Should still error \ No newline at end of file diff --git a/tests/debug_test.ts b/tests/debug_test.ts new file mode 100644 index 00000000000..2a49a4ee07e --- /dev/null +++ b/tests/debug_test.ts @@ -0,0 +1,8 @@ +// Test case to check the fix +declare function foo(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 }); \ No newline at end of file