Skip to content

Commit c565827

Browse files
authored
Fixed some string literal argument completions depending on resolved signature (#53996)
1 parent 2a37eb2 commit c565827

File tree

4 files changed

+47
-11
lines changed

4 files changed

+47
-11
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,8 +1641,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16411641
getFullyQualifiedName,
16421642
getResolvedSignature: (node, candidatesOutArray, argumentCount) =>
16431643
getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal),
1644-
getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray) =>
1645-
runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions)),
1644+
getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray, checkMode = CheckMode.IsForStringLiteralArgumentCompletions) => {
1645+
if (checkMode & CheckMode.IsForStringLiteralArgumentCompletions) {
1646+
return runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, checkMode & ~CheckMode.IsForStringLiteralArgumentCompletions));
1647+
}
1648+
return runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, checkMode & ~CheckMode.IsForStringLiteralArgumentCompletions));
1649+
},
16461650
getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) =>
16471651
runWithoutResolvedSignatureCaching(node, () => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp)),
16481652
getExpandedParameters,
@@ -25230,7 +25234,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2523025234
const constraint = getConstraintOfTypeParameter(inference.typeParameter);
2523125235
if (constraint) {
2523225236
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
25233-
if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
25237+
if (!inferredType || inferredType === wildcardType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
2523425238
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
2523525239
inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint;
2523625240
}
@@ -32508,7 +32512,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3250832512

3250932513
for (let i = 0; i < argCount; i++) {
3251032514
const arg = args[i];
32511-
if (arg.kind !== SyntaxKind.OmittedExpression && !(checkMode & CheckMode.IsForStringLiteralArgumentCompletions && hasSkipDirectInferenceFlag(arg))) {
32515+
if (arg.kind !== SyntaxKind.OmittedExpression) {
3251232516
const paramType = getTypeAtPosition(signature, i);
3251332517
if (couldContainTypeVariables(paramType)) {
3251432518
const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode);
@@ -33152,7 +33156,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3315233156
// decorators are applied to a declaration by the emitter, and not to an expression.
3315333157
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
3315433158
let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal;
33155-
argCheckMode |= checkMode & CheckMode.IsForStringLiteralArgumentCompletions;
3315633159

3315733160
// The following variables are captured and modified by calls to chooseOverload.
3315833161
// If overload resolution or type argument inference fails, we want to report the
@@ -33391,7 +33394,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3339133394
// If one or more context sensitive arguments were excluded, we start including
3339233395
// them now (and keeping do so for any subsequent candidates) and perform a second
3339333396
// round of type inference and applicability checking for this particular candidate.
33394-
argCheckMode = checkMode & CheckMode.IsForStringLiteralArgumentCompletions;
33397+
argCheckMode = CheckMode.Normal;
3339533398
if (inferenceContext) {
3339633399
const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext);
3339733400
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext.inferredTypeParameters);
@@ -37901,7 +37904,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3790137904
case SyntaxKind.NoSubstitutionTemplateLiteral:
3790237905
case SyntaxKind.StringLiteral:
3790337906
return hasSkipDirectInferenceFlag(node) ?
37904-
anyType :
37907+
wildcardType :
3790537908
getFreshTypeOfLiteralType(getStringLiteralType((node as StringLiteralLike).text));
3790637909
case SyntaxKind.NumericLiteral:
3790737910
checkGrammarNumericLiteral(node as NumericLiteral);

src/compiler/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
BaseNodeFactory,
3+
CheckMode,
34
CreateSourceFileOptions,
45
EmitHelperFactory,
56
GetCanonicalFileName,
@@ -5076,7 +5077,7 @@ export interface TypeChecker {
50765077
*/
50775078
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
50785079
/** @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
5079-
/** @internal */ getResolvedSignatureForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node, candidatesOutArray: Signature[]): Signature | undefined;
5080+
/** @internal */ getResolvedSignatureForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node, candidatesOutArray: Signature[], checkMode?: CheckMode): Signature | undefined;
50805081
/** @internal */ getExpandedParameters(sig: Signature): readonly (readonly Symbol[])[];
50815082
/** @internal */ hasEffectiveRestParameter(sig: Signature): boolean;
50825083
/** @internal */ containsArgumentsReference(declaration: SignatureDeclaration): boolean;

src/services/stringCompletions.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CaseClause,
88
changeExtension,
99
CharacterCodes,
10+
CheckMode,
1011
combinePaths,
1112
comparePaths,
1213
comparePatternKeys,
@@ -388,7 +389,7 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
388389
// Get string literal completions from specialized signatures of the target
389390
// i.e. declare function f(a: 'A');
390391
// f("/*completion position*/")
391-
return argumentInfo && getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || fromContextualType(ContextFlags.None);
392+
return argumentInfo && (getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker, CheckMode.Normal)) || fromContextualType(ContextFlags.None);
392393
}
393394
// falls through (is `require("")` or `require(""` or `import("")`)
394395

@@ -479,12 +480,12 @@ function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current:
479480
type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined);
480481
}
481482

482-
function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes | undefined {
483+
function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker, checkMode = CheckMode.IsForStringLiteralArgumentCompletions): StringLiteralCompletionsFromTypes | undefined {
483484
let isNewIdentifier = false;
484485
const uniques = new Map<string, true>();
485486
const candidates: Signature[] = [];
486487
const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg;
487-
checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates);
488+
checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates, checkMode);
488489
const types = flatMap(candidates, candidate => {
489490
if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return;
490491
let type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @strict: true
4+
//// type keyword = "foo" | "bar" | "baz"
5+
////
6+
//// type validateString<s> = s extends keyword
7+
//// ? s
8+
//// : s extends `${infer left extends keyword}|${infer right}`
9+
//// ? right extends keyword
10+
//// ? s
11+
//// : `${left}|${keyword}`
12+
//// : keyword
13+
////
14+
//// type isUnknown<t> = unknown extends t
15+
//// ? [t] extends [{}]
16+
//// ? false
17+
//// : true
18+
//// : false
19+
////
20+
//// type validate<def> = def extends string
21+
//// ? validateString<def>
22+
//// : isUnknown<def> extends true
23+
//// ? keyword
24+
//// : {
25+
//// [k in keyof def]: validate<def[k]>
26+
//// }
27+
//// const parse = <def>(def: validate<def>) => def
28+
//// const shallowExpression = parse("foo|/*ts*/")
29+
//// const nestedExpression = parse({ prop: "foo|/*ts2*/" })
30+
31+
verify.completions({ marker: ["ts", "ts2"], exact: ["foo|foo", "foo|bar", "foo|baz"] });

0 commit comments

Comments
 (0)