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

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

Closed
5 tasks done
AlCalzone opened this issue Nov 22, 2020 · 9 comments · Fixed by #41891
Closed
5 tasks done

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

AlCalzone opened this issue Nov 22, 2020 · 9 comments · Fixed by #41891
Assignees
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@AlCalzone
Copy link
Contributor

AlCalzone commented Nov 22, 2020

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.
@RyanCavanaugh RyanCavanaugh added In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Dec 1, 2020
@RyanCavanaugh
Copy link
Member

@ahejlsberg thoughts?

@ahejlsberg
Copy link
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.

@AlCalzone
Copy link
Contributor Author

AlCalzone commented Dec 2, 2020

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

@ahejlsberg
Copy link
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).

@AlCalzone
Copy link
Contributor Author

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.

@ahejlsberg
Copy link
Member

Now implemented in #41891.

@DanielRosenwasser DanielRosenwasser added Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. and removed In Discussion Not yet reached consensus labels Feb 19, 2021
@DanielRosenwasser
Copy link
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.

@AlCalzone
Copy link
Contributor Author

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

@ahejlsberg
Copy link
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.

@ahejlsberg ahejlsberg added Fixed A PR has been merged for this issue and removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Mar 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants