Skip to content

Try to preserve template-ness of string literal types where possible #41631

Closed

Description

@AlCalzone

Search Terms

template literal preserve

Suggestion

Now that we have template literal types, I would love to have the ability to apply them to string template literals.

Use Cases

This would be really useful in the ioBroker type declarations where we try to infer the object types returned by some methods from the given IDs. These IDs can be directly specified in the method calls, but are in reality often built from multiple parts.

A dumbed down example is shown in the following examples:

Examples

Playground

declare function takesLiteral<T extends string>(literal: T): T extends `foo.bar.${infer R}` ? R : unknown;

const t1 = takesLiteral("foo.bar.baz"); // "baz"
const id2 = "foo.bar.baz";
const t2 = takesLiteral(id2); // "baz"

declare const someString: string;
const t3 = takesLiteral(`foo.bar.${someString}`); // expected: string, actual: unknown

const id4 = `foo.bar.${someString}`;
const t4 = takesLiteral(id4); // expected: string, actual: unknown

Both id4 and the string passed to the third call of takesLiteral are string constants that are created with a template literal. TypeScript should be able to preserve that literal-ness, since it already knows we're feeding a string into a template string.

This means we could work with literals of type foo.bar.${string}, not string like we currently do.

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Activity

  1. RyanCavanaugh commented on Dec 1, 2020

    @RyanCavanaugh
    Member
  2. ahejlsberg commented on Dec 2, 2020

    @ahejlsberg
    Member

    This is already covered by #40707 with the caveat that you need to include as const to get template literal types. It would be a breaking change to do it for all template literal expressions, so I think that's the best we can do.

  3. AlCalzone commented on Dec 2, 2020

    @AlCalzone
    ContributorAuthor

    Anders Hejlsberg (@ahejlsberg) cool, that is good to know. Can you elaborate how that would be breaking? These literals should still be assignable to string?

    The mentioned PR only applies to TypeScript though, not JavaScript. Is there any way to get the desired behavior in JS?
    I can't use as const there, nor can I explain all the users of my API (often beginners) how to use JSDoc type casts (is there even one for as const?) as a workaround.

  4. ahejlsberg commented on Dec 2, 2020

    @ahejlsberg
    Member

    Can you elaborate how that would be breaking?

    We'd end up giving more specific types to variables with types inferred from initializers, which would be breaking. However, we could consider introducing widening and non-widening template literals akin to what we do for regular literal types (see #10676 and #11126 for context).

  5. AlCalzone commented on Dec 2, 2020

    @AlCalzone
    ContributorAuthor

    However, we could consider introducing widening and non-widening template literals akin to what we do for regular literal types

    I don't quite grasp the implications of this change but this seems like it might cover most of the use cases I have in mind.

  6. ahejlsberg commented on Dec 9, 2020

    @ahejlsberg
    Member

    Now implemented in #41891.

  7. added
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    and removed on Feb 19, 2021
  8. DanielRosenwasser commented on Feb 19, 2021

    @DanielRosenwasser
    Member

    We tried this and it turned out to be a bit too breaky and didn't really model user expectations. We can reconsider something that avoids the pain that the last one added, but it's unclear what that would be. In the meantime, you can use as const on your template strings.

    Please limit discussion to useful additions, thoughts, or insights - no :+1:s or "just as a use case"s, since those won't be constructive. If you really need to do something like that, vote 👍 on the issue itself.

  9. AlCalzone commented on Feb 19, 2021

    @AlCalzone
    ContributorAuthor

    I've raised #42884 to request support for the as const workaround in JS, because that is currently not an option for JS devs.

  10. ahejlsberg commented on Mar 26, 2021

    @ahejlsberg
    Member

    In #43376 (now merged) we fix this as much as possible without introducing breaking changes:

    declare function takesLiteral<T extends string>(literal: T): T extends `foo.bar.${infer R}` ? R : unknown;
    
    const t1 = takesLiteral("foo.bar.baz"); // "baz"
    const id2 = "foo.bar.baz";
    const t2 = takesLiteral(id2); // "baz"
    
    declare const someString: string;
    const t3 = takesLiteral(`foo.bar.${someString}`);  // string
    
    const id4 = `foo.bar.${someString}` as const;
    const t4 = takesLiteral(id4);  // string

    Note the use of as const in the assignment to id4. An alternative would be to have a `foo.bar.${string}` type annotation, but we won't infer a template literal type without some indication it is desired.

  11. added
    FixedA PR has been merged for this issue
    and removed
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Mar 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

FixedA PR has been merged for this issueSuggestionAn idea for TypeScript

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions