Fixed some string literal argument completions depending on resolved signature (#53996)

This commit is contained in:
Mateusz Burzyński
2023-06-13 18:34:19 +02:00
committed by GitHub
parent 2a37eb2e47
commit c565827b4f
4 changed files with 47 additions and 11 deletions

View File

@@ -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);

View File

@@ -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;

View File

@@ -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<string, true>();
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);

View File

@@ -0,0 +1,31 @@
/// <reference path="fourslash.ts" />
// @strict: true
//// type keyword = "foo" | "bar" | "baz"
////
//// type validateString<s> = s extends keyword
//// ? s
//// : s extends `${infer left extends keyword}|${infer right}`
//// ? right extends keyword
//// ? s
//// : `${left}|${keyword}`
//// : keyword
////
//// type isUnknown<t> = unknown extends t
//// ? [t] extends [{}]
//// ? false
//// : true
//// : false
////
//// type validate<def> = def extends string
//// ? validateString<def>
//// : isUnknown<def> extends true
//// ? keyword
//// : {
//// [k in keyof def]: validate<def[k]>
//// }
//// const parse = <def>(def: validate<def>) => def
//// const shallowExpression = parse("foo|/*ts*/")
//// const nestedExpression = parse({ prop: "foo|/*ts2*/" })
verify.completions({ marker: ["ts", "ts2"], exact: ["foo|foo", "foo|bar", "foo|baz"] });