Skip to content

Include source node inferences in string literal completions deeper in arguments #56182

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

Andarist
Copy link
Contributor

@Andarist Andarist commented Oct 23, 2023

this is an expansion of the approached used by #54121

fixes: #61247

@typescript-bot
Copy link
Collaborator

This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise.

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Oct 23, 2023
@@ -25,7 +25,7 @@

goTo.marker("1");
edit.insert(`x`)
verify.completions({ exact: ["MORNING", "LUNCH_TIME", "ALOHA"] });
verify.completions({ exact: ["ALOHAx", "MORNING", "LUNCH_TIME", "ALOHA"] });
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ALOHAx is not a "valid" completion here but the IDE won't list it anyway since it's the literal value of what is the current value of the string literal node for which the completion is requested

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, actually... it doesn't show it when typing (the completions popup goes away when my input goes outside of the valid values) but it does show up when requested explicitly with cmd+space.

I still feel like this is an improvement though as the invalid value can be somewhat easily ignored by the user. Maybe this is something that could be improved on the VS Code side though unless this is the intended behavior here

Copy link
Member

Choose a reason for hiding this comment

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

This kind of makes sense since it is actively changing the inference to TEvent in raise, but the experience is not really desirable. I think VS Code showing you completion items that fully match your current token text is intentional and desirable in general, though. The reason this completion entry is bad is that the token being completed is "self-fulfilling," i.e. that text/type doesn't appear anywhere else. I fixed property assignments like that a while ago, so maybe a similar strategy could be used for string literals. I don't think it's a blocker for this PR, but I think it's worth investigating. #35709

Copy link
Member

Choose a reason for hiding this comment

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

I don't know, on second thought, if you change the token text to a substring of one of the valid options, like ALO/**/, it's going to be really annoying if the visible completions in VS Code are ALO first and ALOHA second.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know, on second thought, if you change the token text to a substring of one of the valid options, like ALO/**/, it's going to be really annoying if the visible completions in VS Code are ALO first and ALOHA second.

This is not a new problem so I think it shouldn't be a blocker here (TS playground):

type GreetingEvent =
  | "MORNING"
  | "LUNCH_TIME"
  | "ALOHA";

interface RaiseActionObject<TEvent> {
  type: "raise";
  event: TEvent;
}

declare function raise<TEvent>(
  ev: TEvent
): RaiseActionObject<TEvent>;

declare function createMachine<TEvent>(config: {
  actions: RaiseActionObject<TEvent>;
}): void;

createMachine<GreetingEvent>({
  actions: raise("ALO"),
});

We can get those completions here:
completions popup showing both ALO and ALOHA

All this PR does is reusing the existing algorithm in a new code path.

Copy link
Member

Choose a reason for hiding this comment

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

Hm, I see that in the playground, but the diff in the fourslash test makes it look like a new problem, doesn’t it? What am I missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The cases in the TS playground above and in the added fourslash test are different (and yet the same :P).

Strings used directly at argument positions and somewhere deeper within arguments (like as property values) hit 2 different codepaths. So what we observe on the TS playground above is a result of that other codepath and what this PR does is to port some logic from that other codepath to the other place. So now this fourslash test starts to behave like the TS playground above.

//// });

verify.completions({
marker: ["1", "2"],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It didn't work for marker 1 because the completions type took priority over the regular contextual type. With inference blocked and all the T was inferred here as { a: any } based on the initial: keyof T. That result "locked" the string literal completions to that blocked result whereas the intended/better completions can be returned here based on the contextual type (when the inferred type of T is { a: unknown; b: unknown; }).

Note that this PR doesn't improve the very same situation for regular (non-string) completions - when we have a node with ThisNodeHasError flag. Improvements to that can be explored separately.

@Andarist Andarist force-pushed the fix/string-completions-include-source-inferences branch from bd9e711 to 183578b Compare October 23, 2023 09:55
andrewbranch
andrewbranch previously approved these changes Jan 2, 2024
…s-include-source-inferences

# Conflicts:
#	src/compiler/utilities.ts
@Andarist
Copy link
Contributor Author

@andrewbranch @sandersn is there anything blocking this from being merged? :)

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

The method and results seem decent. I don't really like the code organization -- although there may be a reason for it that I'm missing.

const parent = walkUpParenthesizedExpressions(node.parent);
switch (parent.kind) {
case SyntaxKind.NewExpression:
return checker.getContextualType(parent as NewExpression, contextFlags);
Copy link
Member

Choose a reason for hiding this comment

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

using the checker like this implies that this utility should be in services. Does it also rely on some compiler internals? I looked and I don't see it.

: checker.getContextualType(node, contextFlags);
}
case SyntaxKind.CaseClause:
return checker.getTypeAtLocation((parent as CaseClause).parent.parent.expression);
Copy link
Member

Choose a reason for hiding this comment

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

getContextualTypeFromParent reads like an enhancement of checker.getContextualType. Are the new/case/binaryExpression additions not generally applicable? If not, what conditions are they relevant for? If so, should they be added to getContextualType in the checker?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can't be moved to the checker. Those are specific to services and, for example, power renames in situations like:

type Foo = "a" | "b";

class C {
   p: Foo = "a";
   m() {
       if (this.p === "a") {}
   }
}

When u rename "a" to some other string that rename carries over to those other locations. But outside of situations like this the left/right operand isn't meant to contextually type the other one when equality comparator is involved.

Similarly, usually identifiers are not contextually-typed but this logic in services provides contextual typing for them.

const seen = new Map<string, true>();

return [
...getStringLiteralTypes(getContextualTypeFromParent(expression, checker, ContextFlags.None), seen),
Copy link
Member

Choose a reason for hiding this comment

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

passing checker into a function from inside the checker just seems quite strange. could getStringLiteralTypes be the part in the checker, and getContextualStringLiteralCompletionTypes and getContextualTypeFromParent be in services?

@Andarist Andarist force-pushed the fix/string-completions-include-source-inferences branch 2 times, most recently from 49fd25c to 39ddd44 Compare August 29, 2024 21:00
@Andarist
Copy link
Contributor Author

The method and results seem decent. I don't really like the code organization -- although there may be a reason for it that I'm missing.

@sandersn I addressed this by moving the logic closer to services. I agree that the previous shape of the code wasn't the best - thanks for pointing it out 👍

@Andarist Andarist requested a review from sandersn August 29, 2024 21:02
@@ -411,7 +413,15 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
// });
return stringLiteralCompletionsForObjectLiteral(typeChecker, parent.parent);
}
return fromContextualType() || fromContextualType(ContextFlags.None);
if (findAncestor(parent.parent, isCallLikeExpression)) {
const uniques = new Map<string, true>();
Copy link
Member

Choose a reason for hiding this comment

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

Nothing you did (this is preexisting), but it sure does make me sad to see Map<T, true> in 2024 😄

@Andarist
Copy link
Contributor Author

@jakebailey with 2 approvals, could this land now? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
Status: Waiting on author
Development

Successfully merging this pull request may close these issues.

Unexpected Auto-Completion Behavior in Type Inference
6 participants