From ef50d4521d8d33332b2708ed07a8cafdffd93c6c Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 3 May 2023 15:32:30 -0400 Subject: [PATCH 1/2] Include source node inferences in string literal completions --- src/compiler/checker.ts | 39 +++++++++++++++---- src/compiler/types.ts | 2 +- src/services/stringCompletions.ts | 4 +- ...nditionalTypesUsingTemplateLiteralTypes.ts | 15 +++++++ 4 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 tests/cases/fourslash/stringLiteralCompletionsForGenericConditionalTypesUsingTemplateLiteralTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 92dd084f470b1..1510ac24326ea 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1657,8 +1657,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getFullyQualifiedName, getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), - getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray) => - runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions)), + getCandidateSignaturesForStringLiteralCompletions, getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => runWithoutResolvedSignatureCaching(node, () => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp)), getExpandedParameters, @@ -1839,17 +1838,41 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { typeHasCallOrConstructSignatures, }; + function getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node) { + const candidatesSet = new Set(); + const candidates: Signature[] = []; + + // first, get candidates when inference is blocked from the source node. + runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions)); + for (const candidate of candidates) { + candidatesSet.add(candidate); + } + + // reset candidates for second pass + candidates.length = 0; + + // next, get candidates where the source node is considered for inference. + runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions)); + for (const candidate of candidates) { + candidatesSet.add(candidate); + } + + return arrayFrom(candidatesSet); + } + function runWithoutResolvedSignatureCaching(node: Node | undefined, fn: () => T): T { const containingCall = findAncestor(node, isCallLikeExpression); - const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature; if (containingCall) { - getNodeLinks(containingCall).resolvedSignature = undefined; + const links = getNodeLinks(containingCall); + const containingCallResolvedSignature = links.resolvedSignature; + links.resolvedSignature = undefined; + const result = fn(); + links.resolvedSignature = containingCallResolvedSignature; + return result; } - const result = fn(); - if (containingCall) { - getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature; + else { + return fn(); } - return result; } function runWithInferenceBlockedFromSourceNode(node: Node | undefined, fn: () => T): T { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c97bbaffa2834..b1991afbb25cb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5074,7 +5074,7 @@ export interface TypeChecker { */ getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined; /** @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined; - /** @internal */ getResolvedSignatureForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node, candidatesOutArray: Signature[]): Signature | undefined; + /** @internal */ getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node): Signature[]; /** @internal */ getExpandedParameters(sig: Signature): readonly (readonly Symbol[])[]; /** @internal */ hasEffectiveRestParameter(sig: Signature): boolean; /** @internal */ containsArgumentsReference(declaration: SignatureDeclaration): boolean; diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 61c23266701fa..074aea0de5ede 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -114,7 +114,6 @@ import { ScriptElementKind, ScriptElementKindModifier, ScriptTarget, - Signature, signatureHasRestParameter, SignatureHelp, singleElementArray, @@ -481,9 +480,8 @@ function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes | undefined { let isNewIdentifier = false; const uniques = new Map(); - const candidates: Signature[] = []; const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg; - checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates); + const candidates = checker.getCandidateSignaturesForStringLiteralCompletions(call, editingArgument); const types = flatMap(candidates, candidate => { if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return; let type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex); diff --git a/tests/cases/fourslash/stringLiteralCompletionsForGenericConditionalTypesUsingTemplateLiteralTypes.ts b/tests/cases/fourslash/stringLiteralCompletionsForGenericConditionalTypesUsingTemplateLiteralTypes.ts new file mode 100644 index 0000000000000..5c63857b2feaa --- /dev/null +++ b/tests/cases/fourslash/stringLiteralCompletionsForGenericConditionalTypesUsingTemplateLiteralTypes.ts @@ -0,0 +1,15 @@ +/// + +// NOTE: Test pulled from https://github.com/microsoft/TypeScript/pull/52997 + +// @Filename: /a.tsx +//// type PathOf = +//// K extends `${infer U}.${infer V}` +//// ? U extends keyof T ? PathOf : `${P}${keyof T & (string | number)}` +//// : K extends keyof T ? `${P}${K}` : `${P}${keyof T & (string | number)}`; +//// +//// declare function consumer(path: PathOf<{a: string, b: {c: string}}, K>) : number; +//// +//// consumer('b./*ts*/') + +verify.completions({ marker: ["ts"], exact: ["a", "b", "b.c"] }); \ No newline at end of file From 23628ebb02199b3d4188d362edbc3cf12c4e43e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 31 May 2023 19:53:44 +0200 Subject: [PATCH 2/2] Add extra test cases for string completions (#54133) --- ...nditionalTypesUsingTemplateLiteralTypes.ts | 5 ++- ...nditionalTypesUsingTemplateLiteralTypes.ts | 35 +++++++++++++++++++ ...eralCompletionsInPositionTypedUsingRest.ts | 21 +++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/stringLiteralCompletionsFromGenericConditionalTypesUsingTemplateLiteralTypes.ts create mode 100644 tests/cases/fourslash/stringLiteralCompletionsInPositionTypedUsingRest.ts diff --git a/tests/cases/fourslash/stringLiteralCompletionsForGenericConditionalTypesUsingTemplateLiteralTypes.ts b/tests/cases/fourslash/stringLiteralCompletionsForGenericConditionalTypesUsingTemplateLiteralTypes.ts index 5c63857b2feaa..0074976300ce3 100644 --- a/tests/cases/fourslash/stringLiteralCompletionsForGenericConditionalTypesUsingTemplateLiteralTypes.ts +++ b/tests/cases/fourslash/stringLiteralCompletionsForGenericConditionalTypesUsingTemplateLiteralTypes.ts @@ -1,8 +1,7 @@ /// -// NOTE: Test pulled from https://github.com/microsoft/TypeScript/pull/52997 +// repro from https://github.com/microsoft/TypeScript/issues/49680 -// @Filename: /a.tsx //// type PathOf = //// K extends `${infer U}.${infer V}` //// ? U extends keyof T ? PathOf : `${P}${keyof T & (string | number)}` @@ -12,4 +11,4 @@ //// //// consumer('b./*ts*/') -verify.completions({ marker: ["ts"], exact: ["a", "b", "b.c"] }); \ No newline at end of file +verify.completions({ marker: ["ts"], exact: ["a", "b", "b.c"] }); diff --git a/tests/cases/fourslash/stringLiteralCompletionsFromGenericConditionalTypesUsingTemplateLiteralTypes.ts b/tests/cases/fourslash/stringLiteralCompletionsFromGenericConditionalTypesUsingTemplateLiteralTypes.ts new file mode 100644 index 0000000000000..c222c615a8886 --- /dev/null +++ b/tests/cases/fourslash/stringLiteralCompletionsFromGenericConditionalTypesUsingTemplateLiteralTypes.ts @@ -0,0 +1,35 @@ +/// + +// @strict: true + +// repro from https://github.com/microsoft/TypeScript/issues/53997 + +//// type keyword = "foo" | "bar" | "baz" +//// +//// type validateString = s extends keyword +//// ? s +//// : s extends `${infer left extends keyword}|${infer right}` +//// ? right extends keyword +//// ? s +//// : `${left}|${keyword}` +//// : keyword +//// +//// type isUnknown = unknown extends t +//// ? [t] extends [{}] +//// ? false +//// : true +//// : false +//// +//// type validate = def extends string +//// ? validateString +//// : isUnknown extends true +//// ? keyword +//// : { +//// [k in keyof def]: validate +//// } +//// const parse = (def: validate) => def +//// const shallowExpression = parse("foo|/*ts*/") +//// const nestedExpression = parse({ prop: "foo|/*ts2*/" }) + +verify.completions({ marker: ["ts"], exact: ["foo", "bar", "baz", "foo|foo", "foo|bar", "foo|baz"] }); +verify.completions({ marker: ["ts2"], exact: ["foo|foo", "foo|bar", "foo|baz"] }); diff --git a/tests/cases/fourslash/stringLiteralCompletionsInPositionTypedUsingRest.ts b/tests/cases/fourslash/stringLiteralCompletionsInPositionTypedUsingRest.ts new file mode 100644 index 0000000000000..bd4a11eac9e00 --- /dev/null +++ b/tests/cases/fourslash/stringLiteralCompletionsInPositionTypedUsingRest.ts @@ -0,0 +1,21 @@ +/// + +// repro from https://github.com/microsoft/TypeScript/issues/49680#issuecomment-1249191842 + +//// declare function pick(obj: T, ...keys: K[]): Pick; +//// declare function pick2(obj: T, ...keys: K): Pick; +//// +//// const obj = { aaa: 1, bbb: '2', ccc: true }; +//// +//// pick(obj, 'aaa', '/*ts1*/'); +//// pick2(obj, 'aaa', '/*ts2*/'); + +// repro from https://github.com/microsoft/TypeScript/issues/49680#issuecomment-1273677941 + +//// class Q { +//// public select(...args: Keys[]) {} +//// } +//// new Q<{ id: string; name: string }>().select("name", "/*ts3*/"); + +verify.completions({ marker: ["ts1", "ts2"], exact: ["aaa", "bbb", "ccc"] }); +verify.completions({ marker: ["ts3"], exact: ["name", "id"] });