Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 13.7k
Include source node inferences in string literal completions#54121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
038bfd4c6678703e23961e51def683434de7210673cf58c08c533abfFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -469,6 +469,7 @@ import { | ||
| isCallChain, | ||
| isCallExpression, | ||
| isCallLikeExpression, | ||
| isCallLikeOrFunctionLikeExpression, | ||
| isCallOrNewExpression, | ||
| isCallSignatureDeclaration, | ||
| isCatchClause, | ||
| @@ -1653,12 +1654,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | ||
| getTypeOfPropertyOfContextualType, | ||
| getFullyQualifiedName, | ||
| getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), | ||
| getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray, checkMode = CheckMode.IsForStringLiteralArgumentCompletions) => { | ||
| if (checkMode & CheckMode.IsForStringLiteralArgumentCompletions) { | ||
| return runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, checkMode & ~CheckMode.IsForStringLiteralArgumentCompletions)); | ||
| } | ||
| return runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, checkMode & ~CheckMode.IsForStringLiteralArgumentCompletions)); | ||
| }, | ||
| getCandidateSignaturesForStringLiteralCompletions, | ||
| getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => runWithoutResolvedSignatureCaching(node, () => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp)), | ||
| getExpandedParameters, | ||
| hasEffectiveRestParameter, | ||
| @@ -1838,32 +1834,55 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | ||
| typeHasCallOrConstructSignatures, | ||
| }; | ||
| function getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node) { | ||
| const candidatesSet = new Set<Signature>(); | ||
| const candidates: Signature[] = []; | ||
| // first, get candidates when inference is blocked from the source node. | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: other "double requests" are performed outside of the checker - it's the caller's responsibility to "probe" both type of contextual types (with and without inference blocking). Is this a problem? Perhaps that other code should be moved to the checker? | ||
| 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.Normal)); | ||
| for (const candidate of candidates) { | ||
| candidatesSet.add(candidate); | ||
| } | ||
| return arrayFrom(candidatesSet); | ||
| } | ||
| function runWithoutResolvedSignatureCaching<T>(node: Node | undefined, fn: () => T): T { | ||
| const cachedResolvedSignatures = []; | ||
| const cachedTypes = []; | ||
| while (node) { | ||
| if (isCallLikeExpression(node) || isFunctionLike(node)) { | ||
| node = findAncestor(node, isCallLikeOrFunctionLikeExpression); | ||
| if (node) { | ||
| const cachedResolvedSignatures = []; | ||
| const cachedTypes = []; | ||
| while (node) { | ||
| const nodeLinks = getNodeLinks(node); | ||
| const resolvedSignature = nodeLinks.resolvedSignature; | ||
| cachedResolvedSignatures.push([nodeLinks, resolvedSignature] as const); | ||
| cachedResolvedSignatures.push([nodeLinks, nodeLinks.resolvedSignature] as const); | ||
| nodeLinks.resolvedSignature = undefined; | ||
| if (isFunctionLike(node)) { | ||
| const symbolLinks = getSymbolLinks(getSymbolOfDeclaration(node)); | ||
| const type = symbolLinks.type; | ||
| cachedTypes.push([symbolLinks, type] as const); | ||
| symbolLinks.type = undefined; | ||
| } | ||
| node = findAncestor(node.parent, isCallLikeOrFunctionLikeExpression); | ||
| } | ||
| if (isFunctionLike(node)) { | ||
| const symbolLinks = getSymbolLinks(getSymbolOfDeclaration(node)); | ||
| const type = symbolLinks.type; | ||
| cachedTypes.push([symbolLinks, type] as const); | ||
| symbolLinks.type = undefined; | ||
| const result = fn(); | ||
| for (const [nodeLinks, resolvedSignature] of cachedResolvedSignatures) { | ||
| nodeLinks.resolvedSignature = resolvedSignature; | ||
| } | ||
| node = node.parent; | ||
| } | ||
| const result = fn(); | ||
| for (const [nodeLinks, resolvedSignature] of cachedResolvedSignatures) { | ||
| nodeLinks.resolvedSignature = resolvedSignature; | ||
| } | ||
| for (const [symbolLinks, type] of cachedTypes) { | ||
| symbolLinks.type = type; | ||
| for (const [symbolLinks, type] of cachedTypes) { | ||
| symbolLinks.type = type; | ||
| } | ||
| return result; | ||
| } | ||
| return result; | ||
| return fn(); | ||
| } | ||
| function runWithInferenceBlockedFromSourceNode<T>(node: Node | undefined, fn: () => T): T { | ||
| @@ -33207,7 +33226,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | ||
| for (let i = 0; i < argCount; i++) { | ||
| const arg = args[i]; | ||
| if (arg.kind !== SyntaxKind.OmittedExpression) { | ||
| if (arg.kind !== SyntaxKind.OmittedExpression && !(checkMode & CheckMode.IsForStringLiteralArgumentCompletions && hasSkipDirectInferenceFlag(arg))) { | ||
| const paramType = getTypeAtPosition(signature, i); | ||
| if (couldContainTypeVariables(paramType)) { | ||
| const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode); | ||
| @@ -33849,6 +33868,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | ||
| // decorators are applied to a declaration by the emitter, and not to an expression. | ||
| const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters; | ||
| let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; | ||
| argCheckMode |= checkMode & CheckMode.IsForStringLiteralArgumentCompletions; | ||
| // The following variables are captured and modified by calls to chooseOverload. | ||
| // If overload resolution or type argument inference fails, we want to report the | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /// <reference path="fourslash.ts" /> | ||
| // repro from https://github.com/microsoft/TypeScript/issues/49680 | ||
| //// type PathOf<T, K extends string, P extends string = ""> = | ||
| //// K extends `${infer U}.${infer V}` | ||
| //// ? U extends keyof T ? PathOf<T[U], V, `${P}${U}.`> : `${P}${keyof T & (string | number)}` | ||
| //// : K extends keyof T ? `${P}${K}` : `${P}${keyof T & (string | number)}`; | ||
| //// | ||
| //// declare function consumer<K extends string>(path: PathOf<{a: string, b: {c: string}}, K>) : number; | ||
| //// | ||
| //// consumer('b./*ts*/') | ||
| verify.completions({ marker: ["ts"], exact: ["a", "b", "b.c"] }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| /// <reference path="fourslash.ts" /> | ||
| // repro from https://github.com/microsoft/TypeScript/issues/49680#issuecomment-1249191842 | ||
| //// declare function pick<T extends object, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>; | ||
| //// declare function pick2<T extends object, K extends (keyof T)[]>(obj: T, ...keys: K): Pick<T, K[number]>; | ||
| //// | ||
| //// 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<T> { | ||
| //// public select<Keys extends keyof T>(...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"] }); |
Uh oh!
There was an error while loading. Please reload this page.