diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6ece23f022ab3..e66eff8044589 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); @@ -20887,19 +20881,23 @@ 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); if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); } + if (contextFlags && contextFlags & ContextFlags.Completion && signature.target) { + const baseSignature = getBaseSignature(signature.target); + return intersectTypes(getTypeAtPosition(signature, argIndex), getTypeAtPosition(baseSignature, argIndex)); + } return getTypeAtPosition(signature, argIndex); } @@ -21291,7 +21289,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); 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/harness/fourslash.ts b/src/harness/fourslash.ts index b6a1cf72f3c63..d7cc5d3bd10bb 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -775,7 +775,9 @@ namespace FourSlash { private verifyCompletionsWorker(options: FourSlashInterface.VerifyCompletionsOptions): void { const actualCompletions = this.getCompletionListAtCaret({ ...options.preferences, triggerCharacter: options.triggerCharacter })!; if (!actualCompletions) { - if (ts.hasProperty(options, "exact") && options.exact === undefined) return; + if (ts.hasProperty(options, "exact") && (options.exact === undefined || ts.isArray(options.exact) && !options.exact.length)) { + return; + } this.raiseError(`No completions at position '${this.currentCaretPosition}'.`); } 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/completionsGenericUnconstrained.ts b/tests/cases/fourslash/completionsGenericUnconstrained.ts new file mode 100644 index 0000000000000..3865dc515618f --- /dev/null +++ b/tests/cases/fourslash/completionsGenericUnconstrained.ts @@ -0,0 +1,14 @@ +/// +// @strict: true + +////function f(x: T) { +//// return x; +////} +//// +////f({ /**/ }); + + +verify.completions({ + marker: "", + exact: [] +}); 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' + }, + ] +}) diff --git a/tests/cases/fourslash/completionsWithOptionalPropertiesGenericPartial2.ts b/tests/cases/fourslash/completionsWithOptionalPropertiesGenericPartial2.ts new file mode 100644 index 0000000000000..e52d36c68161c --- /dev/null +++ b/tests/cases/fourslash/completionsWithOptionalPropertiesGenericPartial2.ts @@ -0,0 +1,22 @@ +/// +// @strict: true + +//// interface Foo { +//// a: boolean; +//// } +//// function partialFoo>(x: T, y: T) {return t} +//// partialFoo({ a: true, b: true }, { /*1*/ }); + +verify.completions({ + marker: '1', + includes: [ + { + sortText: completion.SortText.OptionalMember, + name: 'a' + }, + { + sortText: completion.SortText.OptionalMember, + name: 'b' + } + ] +}) diff --git a/tests/cases/fourslash/completionsWithOptionalPropertiesGenericPartial3.ts b/tests/cases/fourslash/completionsWithOptionalPropertiesGenericPartial3.ts new file mode 100644 index 0000000000000..44d2118365fc3 --- /dev/null +++ b/tests/cases/fourslash/completionsWithOptionalPropertiesGenericPartial3.ts @@ -0,0 +1,29 @@ +/// +// @strict: true + +////interface Foo { +//// a: boolean; +////} +////function partialFoo>(x: T, y: T extends { b?: boolean } ? T & { c: true } : T) { +//// return x; +////} +//// +////partialFoo({ a: true, b: true }, { /*1*/ }); + + +verify.completions({ + marker: '1', + includes: [ + { + sortText: completion.SortText.OptionalMember, + name: 'a' + }, + { + sortText: completion.SortText.OptionalMember, + name: 'b' + }, + { + name: 'c' + } + ] +})