diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 967ad86941b40..2874a6f6d3a87 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1641,8 +1641,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.IsForStringLiteralArgumentCompletions)), + 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, @@ -25230,7 +25234,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))) { // If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint. inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint; } @@ -32508,7 +32512,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); @@ -33152,7 +33156,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 @@ -33391,7 +33394,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); @@ -37901,7 +37904,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); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d5c402ea30784..008d4d710bea5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1,5 +1,6 @@ import { BaseNodeFactory, + CheckMode, CreateSourceFileOptions, EmitHelperFactory, GetCanonicalFileName, @@ -5076,7 +5077,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 150d0652dd28c..47a68e9d2e30b 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -7,6 +7,7 @@ import { CaseClause, changeExtension, CharacterCodes, + CheckMode, combinePaths, comparePaths, comparePatternKeys, @@ -388,7 +389,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(ContextFlags.None); + return argumentInfo && (getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker, CheckMode.Normal)) || fromContextualType(ContextFlags.None); } // falls through (is `require("")` or `require(""` or `import("")`) @@ -479,12 +480,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"] });