diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6ece23f022ab3..e86256dab3b15 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -174,12 +174,6 @@ namespace ts { IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help } - const enum ContextFlags { - None = 0, - Signature = 1 << 0, // Obtaining contextual signature - NoConstraints = 1 << 1, // Don't obtain type variable constraints - } - const enum AccessFlags { None = 0, NoIndexSignatures = 1 << 0, @@ -454,9 +448,9 @@ namespace ts { }, getAugmentedPropertiesOfType, getRootSymbols, - getContextualType: nodeIn => { + getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => { const node = getParseTreeNode(nodeIn, isExpression); - return node ? getContextualType(node) : undefined; + return node ? getContextualType(node, contextFlags) : undefined; }, getContextualTypeForObjectLiteralElement: nodeIn => { const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); @@ -17918,7 +17912,7 @@ namespace ts { return getWidenedType(unwidenedType); } - function getInferredType(context: InferenceContext, index: number): Type { + function getInferredType(context: InferenceContext, index: number, contextFlags?: ContextFlags): Type { const inference = context.inferences[index]; if (!inference.inferredType) { let inferredType: Type | undefined; @@ -17963,7 +17957,8 @@ namespace ts { const constraint = getConstraintOfTypeParameter(inference.typeParameter); if (constraint) { const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); - if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { + const isCompletionContext = contextFlags && (contextFlags & ContextFlags.Completion); + if (!inferredType || isCompletionContext || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { inference.inferredType = inferredType = instantiatedConstraint; } } @@ -17976,10 +17971,10 @@ namespace ts { return isInJavaScriptFile ? anyType : unknownType; } - function getInferredTypes(context: InferenceContext): Type[] { + function getInferredTypes(context: InferenceContext, contextFlags?: ContextFlags): Type[] { const result: Type[] = []; for (let i = 0; i < context.inferences.length; i++) { - result.push(getInferredType(context, i)); + result.push(getInferredType(context, i, contextFlags)); } return result; } @@ -20887,16 +20882,16 @@ namespace ts { } // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter. - function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type | undefined { + function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression, contextFlags?: ContextFlags): Type | undefined { const args = getEffectiveCallArguments(callTarget); const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression - return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex); + return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex, contextFlags); } - function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type { + function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number, contextFlags?: ContextFlags): Type { // 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 = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget, /*candidatesOutArray*/ undefined, /*checkMode*/ undefined, contextFlags); if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); } @@ -21291,7 +21286,7 @@ namespace ts { } /* falls through */ case SyntaxKind.NewExpression: - return getContextualTypeForArgument(parent, node); + return getContextualTypeForArgument(parent, node, contextFlags); case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: return isConstTypeReference((parent).type) ? undefined : getTypeFromTypeNode((parent).type); @@ -23393,7 +23388,7 @@ namespace ts { return getInferredTypes(context); } - function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] { + function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext, contextFlags?: ContextFlags): Type[] { if (isJsxOpeningLikeElement(node)) { return inferJsxTypeArguments(node, signature, checkMode, context); } @@ -23459,7 +23454,7 @@ namespace ts { inferTypes(context.inferences, spreadType, restType); } - return getInferredTypes(context); + return getInferredTypes(context, contextFlags); } function getArrayifiedType(type: Type) { @@ -23895,7 +23890,7 @@ namespace ts { return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); } - function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, isOptionalCall: boolean, fallbackError?: DiagnosticMessage): Signature { + function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, isOptionalCall: boolean, fallbackError?: DiagnosticMessage, contextFlags?: ContextFlags): Signature { const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); @@ -23984,7 +23979,7 @@ namespace ts { result = chooseOverload(candidates, subtypeRelation, signatureHelpTrailingComma); } if (!result) { - result = chooseOverload(candidates, assignableRelation, signatureHelpTrailingComma); + result = chooseOverload(candidates, assignableRelation, signatureHelpTrailingComma, contextFlags); } if (result) { return result; @@ -24076,7 +24071,7 @@ namespace ts { return produceDiagnostics || !args ? resolveErrorCall(node) : getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray); - function chooseOverload(candidates: Signature[], relation: Map, signatureHelpTrailingComma = false) { + function chooseOverload(candidates: Signature[], relation: Map, signatureHelpTrailingComma = false, contextFlags?: ContextFlags) { candidatesForArgumentError = undefined; candidateForArgumentArityError = undefined; candidateForTypeArgumentError = undefined; @@ -24113,7 +24108,7 @@ namespace ts { } else { inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); - typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext); + typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext, contextFlags); argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal; } checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); @@ -24283,7 +24278,7 @@ namespace ts { return maxParamsIndex; } - function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, contextFlags?: ContextFlags): Signature { if (node.expression.kind === SyntaxKind.SuperKeyword) { const superType = checkSuperExpression(node.expression); if (isTypeAny(superType)) { @@ -24381,7 +24376,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptional); + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptional, /*fallbackError*/ undefined, contextFlags); } function isGenericFunctionReturningFunction(signature: Signature) { @@ -24399,7 +24394,7 @@ namespace ts { !numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & (TypeFlags.Union | TypeFlags.Never)) && isTypeAssignableTo(funcType, globalFunctionType); } - function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, contextFlags?: ContextFlags): Signature { if (node.arguments && languageVersion < ScriptTarget.ES5) { const spreadIndex = getSpreadArgumentIndex(node.arguments); if (spreadIndex >= 0) { @@ -24452,7 +24447,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, /*isOptional*/ false); + return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, /*isOptional*/ false, /*fallbackError*/ undefined, contextFlags); } // If expressionType's apparent type is an object type with no construct signatures but @@ -24461,7 +24456,7 @@ namespace ts { // operation is Any. It is an error to have a Void this type. const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); if (callSignatures.length) { - const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false); + const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false, /*fallbackError*/ undefined, contextFlags); if (!noImplicitAny) { if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); @@ -24807,12 +24802,12 @@ namespace ts { signature.parameters.length < getDecoratorArgumentCount(decorator, signature)); } - function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, contextFlags?: ContextFlags): Signature { switch (node.kind) { case SyntaxKind.CallExpression: - return resolveCallExpression(node, candidatesOutArray, checkMode); + return resolveCallExpression(node, candidatesOutArray, checkMode, contextFlags); case SyntaxKind.NewExpression: - return resolveNewExpression(node, candidatesOutArray, checkMode); + return resolveNewExpression(node, candidatesOutArray, checkMode, contextFlags); case SyntaxKind.TaggedTemplateExpression: return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode); case SyntaxKind.Decorator: @@ -24831,7 +24826,7 @@ namespace ts { * the function will fill it up with appropriate candidate signatures * @return a signature of the call-like expression or undefined if one can't be found */ - function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, checkMode?: CheckMode): Signature { + function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, checkMode?: CheckMode, contextFlags?: ContextFlags): Signature { const links = getNodeLinks(node); // If getResolvedSignature has already been called, we will have cached the resolvedSignature. // However, it is possible that either candidatesOutArray was not passed in the first time, @@ -24842,7 +24837,7 @@ namespace ts { return cached; } links.resolvedSignature = resolvingSignature; - const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); + const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal, contextFlags); // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call // resolution should be deferred. if (result !== resolvingSignature) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 64415c08d2e9f..36472a2db0499 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3365,8 +3365,10 @@ namespace ts { getFullyQualifiedName(symbol: Symbol): string; getAugmentedPropertiesOfType(type: Type): Symbol[]; + getRootSymbols(symbol: Symbol): readonly Symbol[]; getContextualType(node: Expression): Type | undefined; + /* @internal */ getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined; // eslint-disable-line @typescript-eslint/unified-signatures /* @internal */ getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike): Type | undefined; /* @internal */ getContextualTypeForArgumentAtIndex(call: CallLikeExpression, argIndex: number): Type | undefined; /* @internal */ getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined; @@ -3526,6 +3528,14 @@ namespace ts { Subtype } + /* @internal */ + export const enum ContextFlags { + None = 0, + Signature = 1 << 0, // Obtaining contextual signature + NoConstraints = 1 << 1, // Don't obtain type variable constraints + Completion = 1 << 2, // Obtaining constraint type for completion + } + // NOTE: If modifying this enum, must modify `TypeFormatFlags` too! export const enum NodeBuilderFlags { None = 0, diff --git a/src/services/completions.ts b/src/services/completions.ts index 1c3288a32828a..47e28cd012b0e 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1760,7 +1760,7 @@ namespace ts.Completions { let existingMembers: readonly Declaration[] | undefined; if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { - const typeForObject = typeChecker.getContextualType(objectLikeContainer); + const typeForObject = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completion); if (!typeForObject) return GlobalsSearch.Fail; isNewIdentifierLocation = hasIndexSignature(typeForObject); typeMembers = getPropertiesForObjectExpression(typeForObject, objectLikeContainer, typeChecker); diff --git a/tests/cases/fourslash/completionsWithGenericStringLiteral.ts b/tests/cases/fourslash/completionsWithGenericStringLiteral.ts new file mode 100644 index 0000000000000..6547dee7cd329 --- /dev/null +++ b/tests/cases/fourslash/completionsWithGenericStringLiteral.ts @@ -0,0 +1,11 @@ +/// +// @strict: true + +//// declare function get(obj: T, key: K): T[K]; +//// get({ hello: 123, world: 456 }, "/**/"); + +verify.completions({ + marker: "", + includes: ['hello', 'world'] +}); + diff --git a/tests/cases/fourslash/completionsWithOptionalPropertiesGeneric.ts b/tests/cases/fourslash/completionsWithOptionalPropertiesGeneric.ts new file mode 100644 index 0000000000000..7bd9d566b84b4 --- /dev/null +++ b/tests/cases/fourslash/completionsWithOptionalPropertiesGeneric.ts @@ -0,0 +1,19 @@ +/// +// @strict: true + +//// interface MyOptions { +//// hello?: boolean; +//// world?: boolean; +//// } +//// declare function bar(options?: Partial): void; +//// bar({ hello, /*1*/ }); + +verify.completions({ + marker: '1', + includes: [ + { + sortText: completion.SortText.OptionalMember, + name: 'world' + }, + ] +}) diff --git a/tests/cases/fourslash/completionsWithOptionalPropertiesGenericConstructor.ts b/tests/cases/fourslash/completionsWithOptionalPropertiesGenericConstructor.ts new file mode 100644 index 0000000000000..6959ae1178151 --- /dev/null +++ b/tests/cases/fourslash/completionsWithOptionalPropertiesGenericConstructor.ts @@ -0,0 +1,27 @@ +/// +// @strict: true + +//// interface Options { +//// someFunction?: () => string +//// anotherFunction?: () => string +//// } +//// +//// export class Clazz { +//// constructor(public a: T) {} +//// } +//// +//// new Clazz({ /*1*/ }) + +verify.completions({ + marker: '1', + includes: [ + { + sortText: completion.SortText.OptionalMember, + name: 'someFunction' + }, + { + sortText: completion.SortText.OptionalMember, + name: 'anotherFunction' + }, + ] +}) diff --git a/tests/cases/fourslash/completionsWithOptionalPropertiesGenericDeep.ts b/tests/cases/fourslash/completionsWithOptionalPropertiesGenericDeep.ts new file mode 100644 index 0000000000000..b1ecfc29e7ef2 --- /dev/null +++ b/tests/cases/fourslash/completionsWithOptionalPropertiesGenericDeep.ts @@ -0,0 +1,23 @@ +/// +// @strict: true + +//// interface DeepOptions { +//// another?: boolean; +//// } +//// interface MyOptions { +//// hello?: boolean; +//// world?: boolean; +//// deep?: DeepOptions +//// } +//// declare function bar(options?: Partial): void; +//// bar({ deep: {/*1*/} }); + +verify.completions({ + marker: '1', + includes: [ + { + sortText: completion.SortText.OptionalMember, + name: 'another' + }, + ] +}) diff --git a/tests/cases/fourslash/completionsWithOptionalPropertiesGenericPartial.ts b/tests/cases/fourslash/completionsWithOptionalPropertiesGenericPartial.ts new file mode 100644 index 0000000000000..291d342f35918 --- /dev/null +++ b/tests/cases/fourslash/completionsWithOptionalPropertiesGenericPartial.ts @@ -0,0 +1,33 @@ +/// +// @strict: true + +//// interface Foo { +//// a_a: boolean; +//// a_b: boolean; +//// a_c: boolean; +//// b_a: boolean; +//// } +//// function partialFoo>(t: T) {return t} +//// partialFoo({ /*1*/ }); + +verify.completions({ + marker: '1', + includes: [ + { + sortText: completion.SortText.OptionalMember, + name: 'a_a' + }, + { + sortText: completion.SortText.OptionalMember, + name: 'a_b' + }, + { + sortText: completion.SortText.OptionalMember, + name: 'a_c' + }, + { + sortText: completion.SortText.OptionalMember, + name: 'b_a' + }, + ] +})