Skip to content

Commit ef50d45

Browse files
committed
Include source node inferences in string literal completions
1 parent 7c378db commit ef50d45

File tree

4 files changed

+48
-12
lines changed

4 files changed

+48
-12
lines changed

src/compiler/checker.ts

+31-8
Original file line numberDiff line numberDiff line change
@@ -1657,8 +1657,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16571657
getFullyQualifiedName,
16581658
getResolvedSignature: (node, candidatesOutArray, argumentCount) =>
16591659
getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal),
1660-
getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray) =>
1661-
runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions)),
1660+
getCandidateSignaturesForStringLiteralCompletions,
16621661
getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) =>
16631662
runWithoutResolvedSignatureCaching(node, () => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp)),
16641663
getExpandedParameters,
@@ -1839,17 +1838,41 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
18391838
typeHasCallOrConstructSignatures,
18401839
};
18411840

1841+
function getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node) {
1842+
const candidatesSet = new Set<Signature>();
1843+
const candidates: Signature[] = [];
1844+
1845+
// first, get candidates when inference is blocked from the source node.
1846+
runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions));
1847+
for (const candidate of candidates) {
1848+
candidatesSet.add(candidate);
1849+
}
1850+
1851+
// reset candidates for second pass
1852+
candidates.length = 0;
1853+
1854+
// next, get candidates where the source node is considered for inference.
1855+
runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions));
1856+
for (const candidate of candidates) {
1857+
candidatesSet.add(candidate);
1858+
}
1859+
1860+
return arrayFrom(candidatesSet);
1861+
}
1862+
18421863
function runWithoutResolvedSignatureCaching<T>(node: Node | undefined, fn: () => T): T {
18431864
const containingCall = findAncestor(node, isCallLikeExpression);
1844-
const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature;
18451865
if (containingCall) {
1846-
getNodeLinks(containingCall).resolvedSignature = undefined;
1866+
const links = getNodeLinks(containingCall);
1867+
const containingCallResolvedSignature = links.resolvedSignature;
1868+
links.resolvedSignature = undefined;
1869+
const result = fn();
1870+
links.resolvedSignature = containingCallResolvedSignature;
1871+
return result;
18471872
}
1848-
const result = fn();
1849-
if (containingCall) {
1850-
getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature;
1873+
else {
1874+
return fn();
18511875
}
1852-
return result;
18531876
}
18541877

18551878
function runWithInferenceBlockedFromSourceNode<T>(node: Node | undefined, fn: () => T): T {

src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5074,7 +5074,7 @@ export interface TypeChecker {
50745074
*/
50755075
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
50765076
/** @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
5077-
/** @internal */ getResolvedSignatureForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node, candidatesOutArray: Signature[]): Signature | undefined;
5077+
/** @internal */ getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node): Signature[];
50785078
/** @internal */ getExpandedParameters(sig: Signature): readonly (readonly Symbol[])[];
50795079
/** @internal */ hasEffectiveRestParameter(sig: Signature): boolean;
50805080
/** @internal */ containsArgumentsReference(declaration: SignatureDeclaration): boolean;

src/services/stringCompletions.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ import {
114114
ScriptElementKind,
115115
ScriptElementKindModifier,
116116
ScriptTarget,
117-
Signature,
118117
signatureHasRestParameter,
119118
SignatureHelp,
120119
singleElementArray,
@@ -481,9 +480,8 @@ function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current:
481480
function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes | undefined {
482481
let isNewIdentifier = false;
483482
const uniques = new Map<string, true>();
484-
const candidates: Signature[] = [];
485483
const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg;
486-
checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates);
484+
const candidates = checker.getCandidateSignaturesForStringLiteralCompletions(call, editingArgument);
487485
const types = flatMap(candidates, candidate => {
488486
if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return;
489487
let type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// NOTE: Test pulled from https://github.com/microsoft/TypeScript/pull/52997
4+
5+
// @Filename: /a.tsx
6+
//// type PathOf<T, K extends string, P extends string = ""> =
7+
//// K extends `${infer U}.${infer V}`
8+
//// ? U extends keyof T ? PathOf<T[U], V, `${P}${U}.`> : `${P}${keyof T & (string | number)}`
9+
//// : K extends keyof T ? `${P}${K}` : `${P}${keyof T & (string | number)}`;
10+
////
11+
//// declare function consumer<K extends string>(path: PathOf<{a: string, b: {c: string}}, K>) : number;
12+
////
13+
//// consumer('b./*ts*/')
14+
15+
verify.completions({ marker: ["ts"], exact: ["a", "b", "b.c"] });

0 commit comments

Comments
 (0)