-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
base: main
Are you sure you want to change the base?
Include source node inferences in string literal completions deeper in arguments #56182
Conversation
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
@@ -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"] }); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
All this PR does is reusing the existing algorithm in a new code path.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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"], |
There was a problem hiding this comment.
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.
bd9e711
to
183578b
Compare
…s-include-source-inferences
…s-include-source-inferences # Conflicts: # src/compiler/utilities.ts
@andrewbranch @sandersn is there anything blocking this from being merged? :) |
There was a problem hiding this 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.
src/compiler/utilities.ts
Outdated
const parent = walkUpParenthesizedExpressions(node.parent); | ||
switch (parent.kind) { | ||
case SyntaxKind.NewExpression: | ||
return checker.getContextualType(parent as NewExpression, contextFlags); |
There was a problem hiding this comment.
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.
src/compiler/utilities.ts
Outdated
: checker.getContextualType(node, contextFlags); | ||
} | ||
case SyntaxKind.CaseClause: | ||
return checker.getTypeAtLocation((parent as CaseClause).parent.parent.expression); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
src/compiler/checker.ts
Outdated
const seen = new Map<string, true>(); | ||
|
||
return [ | ||
...getStringLiteralTypes(getContextualTypeFromParent(expression, checker, ContextFlags.None), seen), |
There was a problem hiding this comment.
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?
49fd25c
to
39ddd44
Compare
@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 👍 |
…s-include-source-inferences
@@ -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>(); |
There was a problem hiding this comment.
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 😄
@jakebailey with 2 approvals, could this land now? :) |
this is an expansion of the approached used by #54121
fixes: #61247