Fix excess property checking in array destructuring contexts

Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-07-21 20:19:02 +00:00
parent 5d148fdce6
commit 14850044d4

View File

@ -36065,12 +36065,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const arg = args[i];
if (arg.kind !== SyntaxKind.OmittedExpression) {
const paramType = getTypeAtPosition(signature, i);
const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode);
// 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;
const effectiveCheckArgumentNode = getEffectiveCheckNode(arg);
const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode);
// 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 a destructuring context to avoid inappropriate
// excess property checking (see #41548).
const shouldSkipFreshness = (checkMode & CheckMode.SkipContextSensitive) ||
(isCallExpression(node) && isCallInDestructuringContext(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");
maybeAddMissingAwaitInfo(arg, checkArgType, paramType);
@ -36415,8 +36419,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), typeArguments, chain);
}
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount);
}
}
function isCallInDestructuringContext(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) {
return isBindingPattern(parent.name);
}
// Check for assignment expressions: [a, b] = foo()
if (parent && isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken && parent.right === node) {
return isArrayLiteralExpression(parent.left) || isObjectLiteralExpression(parent.left);
}
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;
@ -36488,9 +36507,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
//
// For a decorator, no arguments are susceptible to contextual typing due to the fact
// decorators are applied to a declaration by the emitter, and not to an expression.
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
if (!isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive)) {
argCheckMode = CheckMode.SkipContextSensitive;
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
let shouldSkipContextSensitive = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive);
// Also skip context sensitive checking when the call is used in a destructuring context
// to avoid inappropriate excess property checking on object literal arguments
const isInDestructuring = !isDecorator && isCallInDestructuringContext(node);
if (isInDestructuring && !shouldSkipContextSensitive) {
shouldSkipContextSensitive = true;
}
if (shouldSkipContextSensitive) {
argCheckMode = CheckMode.SkipContextSensitive;
}
// If we are in signature help, a trailing comma indicates that we intend to provide another argument,
@ -36678,7 +36706,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) {
return undefined;
}
if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) {
if (getSignatureApplicabilityError(node, args, candidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) {
candidatesForArgumentError = [candidate];
return undefined;
}