From 7ab8679aab721587d5207456cfc11ae7230912ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 24 Apr 2023 16:49:16 +0200 Subject: [PATCH 1/2] Root out `CheckMode.IsForStringLiteralArgumentCompletions` --- src/compiler/checker.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 81cb730122da7..4403c53fb2132 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1256,8 +1256,7 @@ export const enum CheckMode { SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions SkipGenericFunctions = 1 << 3, // Skip single signature generic functions IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help - IsForStringLiteralArgumentCompletions = 1 << 5, // Do not infer from the argument currently being typed - RestBindingElement = 1 << 6, // Checking a type that is going to be used to determine the type of a rest binding element + RestBindingElement = 1 << 5, // Checking a type that is going to be used to determine the type of a rest binding element // e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`, // we need to preserve generic types instead of substituting them for constraints } @@ -1658,7 +1657,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray) => - runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions)), + runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, CheckMode.Normal)), getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => runWithoutResolvedSignatureCaching(node, () => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp)), getExpandedParameters, @@ -25198,7 +25197,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const constraint = getConstraintOfTypeParameter(inference.typeParameter); if (constraint) { const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); - if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { + if (!inferredType || inferredType === wildcardType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { inference.inferredType = inferredType = instantiatedConstraint; } } @@ -32436,7 +32435,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { for (let i = 0; i < argCount; i++) { const arg = args[i]; - if (arg.kind !== SyntaxKind.OmittedExpression && !(checkMode & CheckMode.IsForStringLiteralArgumentCompletions && hasSkipDirectInferenceFlag(arg))) { + if (arg.kind !== SyntaxKind.OmittedExpression) { const paramType = getTypeAtPosition(signature, i); if (couldContainTypeVariables(paramType)) { const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode); @@ -33079,7 +33078,6 @@ 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 @@ -33318,7 +33316,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If one or more context sensitive arguments were excluded, we start including // them now (and keeping do so for any subsequent candidates) and perform a second // round of type inference and applicability checking for this particular candidate. - argCheckMode = checkMode & CheckMode.IsForStringLiteralArgumentCompletions; + argCheckMode = CheckMode.Normal; if (inferenceContext) { const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext); checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext.inferredTypeParameters); @@ -37803,7 +37801,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.StringLiteral: return hasSkipDirectInferenceFlag(node) ? - anyType : + wildcardType : getFreshTypeOfLiteralType(getStringLiteralType((node as StringLiteralLike).text)); case SyntaxKind.NumericLiteral: checkGrammarNumericLiteral(node as NumericLiteral); From 6d31988933666212f431741abb5885310361ae29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 24 Apr 2023 18:36:20 +0200 Subject: [PATCH 2/2] Fixed some string literal argument completions depending on resolved signature --- src/compiler/checker.ts | 11 +++++-- src/compiler/types.ts | 3 +- src/services/stringCompletions.ts | 7 +++-- ...nditionalTypesUsingTemplateLiteralTypes.ts | 31 +++++++++++++++++++ 4 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 tests/cases/fourslash/stringCompletionsFromGenericConditionalTypesUsingTemplateLiteralTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4403c53fb2132..c66f84705634b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1256,7 +1256,8 @@ export const enum CheckMode { SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions SkipGenericFunctions = 1 << 3, // Skip single signature generic functions IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help - RestBindingElement = 1 << 5, // Checking a type that is going to be used to determine the type of a rest binding element + IsForStringLiteralArgumentCompletions = 1 << 5, // Do not infer from the argument currently being typed + RestBindingElement = 1 << 6, // Checking a type that is going to be used to determine the type of a rest binding element // e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`, // we need to preserve generic types instead of substituting them for constraints } @@ -1656,8 +1657,12 @@ 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.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)); + }, getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => runWithoutResolvedSignatureCaching(node, () => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp)), getExpandedParameters, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a651f955078f6..77abc6e7349c8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1,5 +1,6 @@ import { BaseNodeFactory, + CheckMode, CreateSourceFileOptions, EmitHelperFactory, GetCanonicalFileName, @@ -5073,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 */ getResolvedSignatureForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node, candidatesOutArray: Signature[], checkMode?: CheckMode): Signature | undefined; /** @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..d722979f61e40 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -7,6 +7,7 @@ import { CaseClause, changeExtension, CharacterCodes, + CheckMode, combinePaths, comparePaths, comparePatternKeys, @@ -387,7 +388,7 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL // Get string literal completions from specialized signatures of the target // i.e. declare function f(a: 'A'); // f("/*completion position*/") - return argumentInfo && getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || fromContextualType(); + return argumentInfo && (getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker, CheckMode.Normal)) || fromContextualType(); } // falls through (is `require("")` or `require(""` or `import("")`) @@ -478,12 +479,12 @@ function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined); } -function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes | undefined { +function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker, checkMode = CheckMode.IsForStringLiteralArgumentCompletions): 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); + checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates, checkMode); 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/stringCompletionsFromGenericConditionalTypesUsingTemplateLiteralTypes.ts b/tests/cases/fourslash/stringCompletionsFromGenericConditionalTypesUsingTemplateLiteralTypes.ts new file mode 100644 index 0000000000000..e6023b51b64e4 --- /dev/null +++ b/tests/cases/fourslash/stringCompletionsFromGenericConditionalTypesUsingTemplateLiteralTypes.ts @@ -0,0 +1,31 @@ +/// + +// @strict: true +//// 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", "ts2"], exact: ["foo|foo", "foo|bar", "foo|baz"] });