Skip to content

Commit 038bfd4

Browse files
committed
Include source node inferences in string literal completions
1 parent 08e0eb7 commit 038bfd4

File tree

4 files changed

+63
-26
lines changed

4 files changed

+63
-26
lines changed

src/compiler/checker.ts

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,14 +1648,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16481648
isContextSensitive,
16491649
getTypeOfPropertyOfContextualType,
16501650
getFullyQualifiedName,
1651-
getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal),
1652-
getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray, checkMode = CheckMode.IsForStringLiteralArgumentCompletions) => {
1653-
if (checkMode & CheckMode.IsForStringLiteralArgumentCompletions) {
1654-
return runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, checkMode & ~CheckMode.IsForStringLiteralArgumentCompletions));
1655-
}
1656-
return runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, checkMode & ~CheckMode.IsForStringLiteralArgumentCompletions));
1657-
},
1658-
getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => runWithoutResolvedSignatureCaching(node, () => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp)),
1651+
getResolvedSignature: (node, candidatesOutArray, argumentCount) =>
1652+
getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal),
1653+
getCandidateSignaturesForStringLiteralCompletions,
1654+
getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) =>
1655+
runWithoutResolvedSignatureCaching(node, () => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp)),
16591656
getExpandedParameters,
16601657
hasEffectiveRestParameter,
16611658
containsArgumentsReference,
@@ -1834,22 +1831,47 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
18341831
typeHasCallOrConstructSignatures,
18351832
};
18361833

1837-
function runWithoutResolvedSignatureCaching<T>(node: Node | undefined, fn: () => T): T {
1838-
const cachedSignatures = [];
1839-
while (node) {
1840-
if (isCallLikeExpression(node)) {
1841-
const nodeLinks = getNodeLinks(node);
1842-
const resolvedSignature = nodeLinks.resolvedSignature;
1843-
cachedSignatures.push([nodeLinks, resolvedSignature] as const);
1844-
nodeLinks.resolvedSignature = undefined;
1834+
function getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node, checkMode = CheckMode.IsForStringLiteralArgumentCompletions) {
1835+
const candidatesSet = new Set<Signature>();
1836+
const candidates: Signature[] = [];
1837+
1838+
if (checkMode & CheckMode.IsForStringLiteralArgumentCompletions) {
1839+
// first, get candidates when inference is blocked from the source node.
1840+
runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, checkMode));
1841+
for (const candidate of candidates) {
1842+
candidatesSet.add(candidate);
18451843
}
1846-
node = node.parent;
1844+
1845+
// reset candidates for second pass
1846+
candidates.length = 0;
18471847
}
1848-
const result = fn();
1849-
for (const [nodeLinks, resolvedSignature] of cachedSignatures) {
1850-
nodeLinks.resolvedSignature = resolvedSignature;
1848+
1849+
// next, get candidates where the source node is considered for inference.
1850+
runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, checkMode & ~CheckMode.IsForStringLiteralArgumentCompletions));
1851+
for (const candidate of candidates) {
1852+
candidatesSet.add(candidate);
18511853
}
1852-
return result;
1854+
1855+
return arrayFrom(candidatesSet);
1856+
}
1857+
1858+
function runWithoutResolvedSignatureCaching<T>(node: Node | undefined, fn: () => T): T {
1859+
node = findAncestor(node, isCallLikeExpression);
1860+
if (node) {
1861+
const cachedSignatures = [];
1862+
while (node) {
1863+
const links = getNodeLinks(node);
1864+
cachedSignatures.push([links, links.resolvedSignature] as const);
1865+
links.resolvedSignature = undefined;
1866+
node = findAncestor(node.parent, isCallLikeExpression);
1867+
}
1868+
const result = fn();
1869+
for (const [links, resolvedSignature] of cachedSignatures) {
1870+
links.resolvedSignature = resolvedSignature;
1871+
}
1872+
return result;
1873+
}
1874+
return fn();
18531875
}
18541876

18551877
function runWithInferenceBlockedFromSourceNode<T>(node: Node | undefined, fn: () => T): T {
@@ -33103,7 +33125,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3310333125

3310433126
for (let i = 0; i < argCount; i++) {
3310533127
const arg = args[i];
33106-
if (arg.kind !== SyntaxKind.OmittedExpression) {
33128+
if (arg.kind !== SyntaxKind.OmittedExpression && !(checkMode & CheckMode.IsForStringLiteralArgumentCompletions && hasSkipDirectInferenceFlag(arg))) {
3310733129
const paramType = getTypeAtPosition(signature, i);
3310833130
if (couldContainTypeVariables(paramType)) {
3310933131
const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode);
@@ -33745,6 +33767,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3374533767
// decorators are applied to a declaration by the emitter, and not to an expression.
3374633768
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
3374733769
let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal;
33770+
argCheckMode |= checkMode & CheckMode.IsForStringLiteralArgumentCompletions;
3374833771

3374933772
// The following variables are captured and modified by calls to chooseOverload.
3375033773
// If overload resolution or type argument inference fails, we want to report the
@@ -33984,6 +34007,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3398434007
// them now (and keeping do so for any subsequent candidates) and perform a second
3398534008
// round of type inference and applicability checking for this particular candidate.
3398634009
argCheckMode = CheckMode.Normal;
34010+
// argCheckMode = checkMode & CheckMode.IsForStringLiteralArgumentCompletions;
3398734011
if (inferenceContext) {
3398834012
const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext);
3398934013
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext.inferredTypeParameters);

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4992,7 +4992,7 @@ export interface TypeChecker {
49924992
*/
49934993
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
49944994
/** @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
4995-
/** @internal */ getResolvedSignatureForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node, candidatesOutArray: Signature[], checkMode?: CheckMode): Signature | undefined;
4995+
/** @internal */ getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node, checkMode?: CheckMode): Signature[];
49964996
/** @internal */ getExpandedParameters(sig: Signature): readonly (readonly Symbol[])[];
49974997
/** @internal */ hasEffectiveRestParameter(sig: Signature): boolean;
49984998
/** @internal */ containsArgumentsReference(declaration: SignatureDeclaration): boolean;

src/services/stringCompletions.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ import {
117117
ScriptElementKind,
118118
ScriptElementKindModifier,
119119
ScriptTarget,
120-
Signature,
121120
signatureHasRestParameter,
122121
SignatureHelp,
123122
singleElementArray,
@@ -500,9 +499,8 @@ function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current:
500499
function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker, checkMode = CheckMode.IsForStringLiteralArgumentCompletions): StringLiteralCompletionsFromTypes | undefined {
501500
let isNewIdentifier = false;
502501
const uniques = new Map<string, true>();
503-
const candidates: Signature[] = [];
504502
const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg;
505-
checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates, checkMode);
503+
const candidates = checker.getCandidateSignaturesForStringLiteralCompletions(call, editingArgument, checkMode);
506504
const types = flatMap(candidates, candidate => {
507505
if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return;
508506
let type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex);
Lines changed: 15 additions & 0 deletions
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)