Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 64 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)) {
Expand All @@ -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;

Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6308,6 +6308,7 @@ export interface NodeLinks {
externalHelpersModule?: Symbol; // Resolved symbol for the external helpers module
instantiationExpressionTypes?: Map<number, Type>; // Cache of instantiation expression types for the node
nonExistentPropCheckCache?: Set<string>;
completionArgumentIndex?: number; // Temporary storage for argument index being completed during intellisense
}

/** @internal */
Expand Down
66 changes: 66 additions & 0 deletions tests/cases/fourslash/completionForGenericOverloadObjectLiteral.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/// <reference path="fourslash.ts" />

// @Filename: /test.ts
////declare function task<T>(make: () => T): void
////declare function task<T>(arg: {make: () => T}): void
////
////task({
//// /*1*/
////});
////
////declare function task2<T>(arg: {make: () => T}): void
////declare function task2<T>(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<T, U>(fn: (x: T) => U): void
////declare function process<T, U>(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
});