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

Addition of string (or template) literal types should result in a string literal type #51583

Closed
5 tasks done
bradzacher opened this issue Nov 18, 2022 · 9 comments
Closed
5 tasks done
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@bradzacher
Copy link
Contributor

bradzacher commented Nov 18, 2022

Suggestion

πŸ” Search Terms

string literal type template addition sum add

βœ… Viability 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, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

I was playing around today and I noticed that if I add two string literals together, TS does not understand that the result of adding two string literal types is always going to be another string literal type.

πŸ“ƒ Motivating Example

declare const x: 'x';
declare const y: 'y';

const xy = x + y;
//     ^?
//     expected: 'xy'
//     actual:   string

declare const a: `${number}`;
declare const b: `${boolean}`;

const ab = a + b;
//     ^?
//     expected: `${number}${boolean}`
//     actual:   string

const foo = 'a' + 'b';
//     ^?
//     expected: 'ab'
//     actual:   string

declare const f: 'a' | 'b';
declare const g: 'c' | 'd';

const fg = f + g;
//     ^?
//     expected: 'ac' | 'ad' | 'bc' | 'bd'
//     actual:   string

playground

Maybe even including number literal types and boolean literal types would be good as well? Eg

declare const t: 1;
declare const u: 'a';
declare const v: true;

const tuv = t + u + v;
//     ^?
//     expected: '1atrue'
//     actual:   string

playground

πŸ’» Use Cases

I don't have any real usecases, I just thought it would be a neat property to have in the type system rather than always widening the type to string.

One case that it would improve would be computed class/object members:

declare const x: 'x';
declare const y: 'y';

class Foo {
   [x + y] = 1;
   // expected: 'xy'
   // actual:   error A computed property name in a class property declaration must have a simple literal type or a 'unique symbol' type.(1166)
}

const obj = {
    [x + y]: 1,
};
    obj;
//  ^?
//  expected: { xy: number }
//  actual:   { [x: string]: number }

playground


As an extension - if you were brave you could probably also do the same for adding number literal types eg:

declare const x: 1;
declare const y: 2;

const xy = x + y;
//     ^?
//     expected: 3
//     actual:   number

playground

or bigint types:

declare const x: 1n;
declare const y: 2n;

const xy = x + y;
//     ^?
//     expected: 3n
//     actual:   bigint

playground

@MartinJohns
Copy link
Contributor

Duplicate of the very old #13969.

@bradzacher
Copy link
Contributor Author

@MartinJohns most of that seems to specifically be talking about template literal strings - which seems like a great idea, that I didn't consider for my issue!

I do see one or two comments mentioning addition, but most seem to talk about template literals.

@MartinJohns
Copy link
Contributor

The templates are probably the "similar expressions", but the title also mentions "literal types for string concatenations" (also in several examples in the comments).

@bradzacher
Copy link
Contributor Author

Also worth noting (as I just commented in that issue) if you add as const to the template literal string, then it will work as expected! The same isn't true of string literal typed variables - it is a semantic error to use as const on non-literal expressions.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Declined The issue was declined as something which matches the TypeScript vision labels Nov 18, 2022
@RyanCavanaugh
Copy link
Member

We need there to be some way to opt out of this behavior since this is potentially combinatorially explosive (imagine writing hexDigit1 + hexDigit2 + hexDigit3 + hexDigit4), and it's not clear how you would do that here. Since you can already get this behavior with ${f}${g} as const` I think the use case is fully addressed.

@bradzacher
Copy link
Contributor Author

bradzacher commented Nov 18, 2022

We need there to be some way to opt out of this behavior

Couldn't that be like assert one of the summands to string? (a as string) + b +...

That would make it an explicit opt-out instead of an explicit opt-in like template literal strings.


Since you can already get this behavior with [template literals] I think the use case is fully addressed.

I would agree that the usecase of "I want to concatenate string literal types" is addressed!

But in terms of addressing the usecase of better typing for code as it's written - there is a gap. Using template literals to concentrate strings is a stylistic choice which many codebases don't opt-in to unless there's string literals involved (I.e. a + b is stylistically okay, but a + '.' + b is stylistically preferred to be a template literal). So there is a lot of existing code which uses addition to join string variables which could be stricter typed!

@fatcerberus
Copy link

fatcerberus commented Nov 18, 2022

Problem is that if you infer hexDigit + hexDigit to be ${typeof hexDigit}${typeof hexDigit}, this easily creates combinatorially explosive unions and then users start to wonder why they're getting "union is too complex to represent" errors in perfectly mundane code that doesn't even look like a type operator. At least if that user is forced to write ${Hex}${Hex}${Hex} or similar in a type position, they know they're opting in to a construct that can potentially create enormous unions.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Nov 18, 2022

Couldn't that be like assert one of the summands to string?

In principle no, because there's no reason not to construct the type ${string}000 | ${string}001 | ${string}002 | ${string}003 | ... ${string}fff

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Declined" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants