Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
BaseNodeFactory,
CheckMode,
CreateSourceFileOptions,
EmitHelperFactory,
GetCanonicalFileName,
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 4 additions & 3 deletions src/services/stringCompletions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CaseClause,
changeExtension,
CharacterCodes,
CheckMode,
combinePaths,
comparePaths,
comparePatternKeys,
Expand Down Expand Up @@ -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("")`)

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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*/" })
Copy link
Contributor Author

@Andarist Andarist Apr 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's worth pointing out here that this nestedExpression was already working OK, the problem was in the shallowExpression's case as that was using a completely different part of the algorithm (getStringLiteralCompletionsFromSignature, and not getContextualType~)


verify.completions({ marker: ["ts", "ts2"], exact: ["foo|foo", "foo|bar", "foo|baz"] });