diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ba5765c0826a3..40e0ef945b962 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -32063,9 +32063,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { anyType; } + // Special handling for completions with overloaded functions and object literals: + // When inference is blocked (completion context), the first argument is an object literal, + // AND there are multiple overloads, clear the cached signature to force fresh overload resolution. + // This prevents showing function properties (bind, call, apply) when the correct overload expects an object. + const links = getNodeLinks(callTarget); + + // For issue #62693: Store which argument index is being completed so chooseOverload can use this information + // to prefer object-type overloads over function-type overloads when completing object literals. + if (isInferencePartiallyBlocked) { + links.completionArgumentIndex = argIndex; + } + // If we're already in the process of resolving the given signature, don't resolve again as // that could cause infinite recursion. Instead, return anySignature. - const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); + const signature = links.resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); @@ -36697,6 +36709,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { candidateForArgumentArityError = undefined; candidateForTypeArgumentError = undefined; + // Special handling for issue #62693: When completing an object literal argument for a generic function + // with overloads (one taking a function, one taking an object), we need to prefer the object-type overload + // even though generic inference is blocked during completion. + const completingObjectLiteralArg = isInferencePartiallyBlocked && args.length > 0 && isObjectLiteralExpression(args[0]) && getNodeLinks(node).completionArgumentIndex === 0; + const shouldRelaxApplicabilityForSimpleObjectLiteral = completingObjectLiteralArg; + if (isSingleNonGenericCandidate) { const candidate = candidates[0]; if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { @@ -36715,6 +36733,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { continue; } + // Special handling for overload resolution: skip overloads with function-type + // parameters when completing for an object literal argument (not just when object literal exists). + // This prevents showing function properties (bind, call, apply) instead of object properties in intellisense. + if (completingObjectLiteralArg && candidate.declaration && isFunctionLikeDeclaration(candidate.declaration)) { + const params = candidate.declaration.parameters; + if (params.length > 0) { + const firstParam = params[0]; + const paramTypeNode = firstParam.type; + // Check if the parameter's type annotation is a function type + if (paramTypeNode && (paramTypeNode.kind === SyntaxKind.FunctionType || paramTypeNode.kind === SyntaxKind.ConstructorType)) { + // This parameter expects a function, but we're passing an object literal + // Skip this overload candidate and continue to next + continue; + } + } + } + let checkCandidate: Signature; let inferenceContext: InferenceContext | undefined; @@ -36744,10 +36779,35 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkCandidate = candidate; } if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { - // Give preference to error candidates that have no rest parameters (as they are more specific) - (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); - continue; + // Special case for issue #62693: when we have mixed function-type and simple object-type overloads, + // accept the simple object-type overload even if the object literal is incomplete during completion + if (shouldRelaxApplicabilityForSimpleObjectLiteral && candidate.declaration && isFunctionLikeDeclaration(candidate.declaration)) { + const params = candidate.declaration.parameters; + if (params.length > 0) { + const paramTypeNode = params[0].type; + // ONLY accept if this is the SIMPLE object-type overload (not function-type, not complex type) + if (paramTypeNode && paramTypeNode.kind === SyntaxKind.TypeLiteral) { + // Accept this candidate despite applicability error (incomplete object literal) + // Fall through to accept this candidate + } + else { + // Not the simple object-type overload, reject normally + (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); + continue; + } + } + else { + (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); + continue; + } + } + else { + // Normal case: reject candidate + (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); + continue; + } } + if (argCheckMode) { // If one or more context sensitive arguments were excluded, we start including // them now (and keeping do so for any subsequent candidates) and perform a second diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 617ec4d8bda75..f7112d2350da9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6308,6 +6308,7 @@ export interface NodeLinks { externalHelpersModule?: Symbol; // Resolved symbol for the external helpers module instantiationExpressionTypes?: Map; // Cache of instantiation expression types for the node nonExistentPropCheckCache?: Set; + completionArgumentIndex?: number; // Temporary storage for argument index being completed during intellisense } /** @internal */ diff --git a/tests/cases/fourslash/completionForGenericOverloadObjectLiteral.ts b/tests/cases/fourslash/completionForGenericOverloadObjectLiteral.ts new file mode 100644 index 0000000000000..5dc5f0c968568 --- /dev/null +++ b/tests/cases/fourslash/completionForGenericOverloadObjectLiteral.ts @@ -0,0 +1,66 @@ +/// + +// @Filename: /test.ts +////declare function task(make: () => T): void +////declare function task(arg: {make: () => T}): void +//// +////task({ +//// /*1*/ +////}); +//// +////declare function task2(arg: {make: () => T}): void +////declare function task2(make: () => T): void +//// +////task2({ +//// /*2*/ +////}); +//// +////declare function task3(make: () => void): void +////declare function task3(arg: {make: () => void}): void +//// +////task3({ +//// /*3*/ +////}); +//// +////// More rigorous test with multiple properties and multiple type parameters +////declare function process(fn: (x: T) => U): void +////declare function process(opts: {transform: (x: T) => U, validate?: (x: T) => boolean}): void +//// +////process({ +//// /*4*/ +////}); + +// Test 1: Generic overload with function parameter first should show object properties, not function properties +verify.completions({ + marker: "1", + includes: { name: "make", kind: "property", kindModifiers: "declare" }, + excludes: ["bind", "call", "apply"], + isNewIdentifierLocation: false +}); + +// Test 2: Generic overload with object parameter first should show object properties (control test) +verify.completions({ + marker: "2", + includes: { name: "make", kind: "property", kindModifiers: "declare" }, + excludes: ["bind", "call", "apply"], + isNewIdentifierLocation: false +}); + +// Test 3: Non-generic overload should show object properties regardless of order (control test) +verify.completions({ + marker: "3", + includes: { name: "make", kind: "property", kindModifiers: "declare" }, + excludes: ["bind", "call", "apply"], + isNewIdentifierLocation: false +}); + +// Test 4: Multiple type parameters with multiple properties (rigorous test) +verify.completions({ + marker: "4", + includes: [ + { name: "transform", kind: "property", kindModifiers: "declare", sortText: completion.SortText.LocationPriority }, + { name: "validate", kind: "property", kindModifiers: "declare,optional", sortText: completion.SortText.OptionalMember } + ], + excludes: ["bind", "call", "apply"], + isNewIdentifierLocation: false +});