Skip to content
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

Completions for string literal type parameter don't work if it's constraint includes an empty string #47227

Closed
devanshj opened this issue Dec 22, 2021 · 8 comments Β· Fixed by #48811
Labels
Domain: Completion Lists The issue relates to showing completion lists in an editor Experience Enhancement Noncontroversial enhancements Suggestion An idea for TypeScript
Milestone

Comments

@devanshj
Copy link

devanshj commented Dec 22, 2021

Bug Report

πŸ”Ž Search Terms

String literal completions, empty string constraint, intellisense

πŸ•— Version & Regression Information

Tested with v4.5.4

⏯ Playground Link

Playground

πŸ’» Code

declare const foo1: (p: "" | "bar" | "baz") => void

foo1("")
//    ^|
// expected completions: "", "bar", "baz"
// actual completions: "", "bar", "baz"

declare const foo2: <P extends "" | "bar" | "baz">(p: P) => void

foo2("")
//    ^|
// expected completions: "", "bar", "baz"
// actual completions: none

πŸ™ Actual behavior

foo2 has no completions

πŸ™‚ Expected behavior

foo2 should have same completions like foo1 which are "", "bar", "baz"

@devanshj devanshj changed the title Completions for string literal don't work for type parameter that includes empty string in it's constraint Completions for string literal type parameter don't work if it's constraint includes an empty string Dec 22, 2021
@david-shortman
Copy link

When typing a " in the foo1, I don't receive suggestions until I type a b character.

Screen Shot 2021-12-22 at 20 29 15

When I type a b character in a string given to foo2, I get two suggestions.

Screen Shot 2021-12-22 at 20 28 57

@devanshj
Copy link
Author

Yes that's because "" is a valid value so you don't get suggested anything once you're done writing it. But you can trigger intellisense via CTRL + SPACE while caret is between the quotes (like "|" then hit CTRL + SPACE). And then foo1 will have suggestions and foo2 won't:

Sometimes you'd want to look up all possible values before you even start typing something, which you can't do in case of foo2

@david-shortman
Copy link

The issue is specifically with empty strings, it seems. I tried using the suggestion shortcut in an empty argument list, and was presented with the three possible string options for both foo1 and foo2.

Screen Shot 2021-12-22 at 20 51 43

Screen Shot 2021-12-22 at 20 51 53

@devanshj
Copy link
Author

Ah, yeah then it seems the issue occurres only when you have already written ""

@RyanCavanaugh RyanCavanaugh added Experience Enhancement Noncontroversial enhancements Suggestion An idea for TypeScript labels Jan 5, 2022
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Jan 5, 2022
@RyanCavanaugh RyanCavanaugh added the Domain: Completion Lists The issue relates to showing completion lists in an editor label Jan 5, 2022
@Andarist
Copy link
Contributor

This isn't really about an empty string but about the string literal at the argument position already matching one of the union members. The same happens here:

declare const foo2: <P extends "bar" | "barbaz">(p: P) => void

foo2('bar') // we won't get autocomplete for 'barbaz'

@Andarist
Copy link
Contributor

Ok, so the "problem" is that autocomplete here works on the "resolved signature" and since the function is generic this already includes an inferred generic:

declare const foo2: <P extends "bar" | "barbaz">(p: P) => void

// signature: function foo2<"bar">(p: "bar"): void
foo2('bar')

This can be verified~ when hovering over the function symbol at the call site.

When the generic is not properly inferred because we have a signature applicability error then the signature is displayed like this:

declare const foo2: <P extends "bar" | "barbaz">(p: P) => void

// signature: const foo2: <"bar" | "barbaz">(p: "bar" | "barbaz") => void
foo2('unknown')

and that is what makes it work~.

I've also looked into how this behaves with overloads and we get a full list of completions until we match one of the signatures (so basically it's very similar to single-signature functions discussed above):

declare function bar1<P extends "bar" | "barbaz">(p: P): number;
declare function bar1<P extends "qwe" | "qwert">(p: P): string;

// completions: "bar" | "barbaz" | "qwe" | "qwert"
bar1('') 

This is thanks to the fact that getResolvedSignatureForStringLiteralCompletions aggregates all candidate signatures in the candidates array (after all nothing has been matched, so all signatures are still candidates).

Some other interesting bits:

  • the resolveCall/chooseOverload functions are called within runWithInferenceBlockedFromSourceNode but this doesn't block signature selection
  • this happens with CheckMode.IsForStringLiteralArgumentCompletions and that potentially could be used to resolveCall differently here

@andrewbranch do you see any specific implementation difficulties for this? Or a rather simple heuristic could be used here? I would have to dive deeper into how different positions within the arguments list are resolved, potentially they might need the chooseOverload calls to narrow down candidates and this might be quite problematic. Ideally, we would skip inference for the particular position that is being the subject of the autocomplete while narrowing down based on other arguments etc. Since the same generic can appear at different positions this probably gets complicated quite quickly 😬

@andrewbranch
Copy link
Member

Isn’t this exactly what I fixed at #48410? I’m guessing not, since you’re referring to methods and CheckModes I added there. But what you’re describing is what I remember doing πŸ˜…

@Andarist
Copy link
Contributor

Ye, this is very related - the difference is that in the test case that you have added there is no overload selected because there is a mismatch between the arity of the function declaration and the supplied arguments count. So the added logic from your PR (#48410) handles that situation within inferTypeArguments that is called (transitively) from getCandidateForOverloadFailure. In this situation here - there is no overload failure though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: Completion Lists The issue relates to showing completion lists in an editor Experience Enhancement Noncontroversial enhancements Suggestion An idea for TypeScript
Projects
None yet
5 participants