镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

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

Description

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"

Activity

  1. 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[/+] on Dec 22, 2021
  2. david-shortman commented on Dec 23, 2021

    @david-shortman

    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

  3. devanshj commented on Dec 23, 2021

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

  4. david-shortman commented on Dec 23, 2021

    @david-shortman

    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

  5. devanshj commented on Dec 23, 2021

    @devanshj
    Author

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

  6. Andarist commented on Apr 21, 2022

    @Andarist
    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'
  7. Andarist commented on Apr 21, 2022

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

    Andrew Branch (@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 😬

  8. andrewbranch commented on Apr 21, 2022

    @andrewbranch
    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 😅

  9. Andarist commented on Apr 22, 2022

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions