Skip to content

Strings do not have known keys associated with their indexes #41037

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

Open
ghost opened this issue Oct 10, 2020 · 5 comments
Open

Strings do not have known keys associated with their indexes #41037

ghost opened this issue Oct 10, 2020 · 5 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@ghost
Copy link

ghost commented Oct 10, 2020

TypeScript Version: Nightly

Code

{
    const t: 't' = ("test" as const)[0];
}
{
    const t: 't' = ([ 't', 'e', 's', 't' ] as const)[0];
}

Expected behavior:

TypeScript associated keys with values for string literals.

Actual behavior:

TypeScript gives all indexes into a string the type string.
TypeScript allows out of bounds indexes into string literals.

Playground Link: https://www.typescriptlang.org/play?esModuleInterop=false&experimentalDecorators=false&emitDecoratorMetadata=false&ts=4.1.0-insiders.20201010#code/PTAEBcFMGdwLlAIirRBuCBPADpAJqAIbSgDukANhUSdgPbTQCWARhZKEwHagAqAygBpQAYzoBbbOygVMnceICu4Qmw6EuBANZc6pHoXCiJ2JuwhNxkAFBiusCDCMBeJCnCIax++DTXrIKDQABZ0ihQELByQAI6KTABuhOxcRuB0oAAUAE6QhHh0XLKgANqgAOTg5cLlkNUV0PWV5aAAugCU-nYO8BVVoK7uJQAMrX62hdB07AB0FHQA5pkuzq7N7RiBAGbJ0JDCduDZhNly6WTBhnz8oFEUTJAJMF2TRiKX2Qiw2dwLA46wEoAVjGL3s00gc0WyxwkDoW1EHwGqyQ31+iA2oG2u323iOJzOGVIlyMAlulAeT2g-gA3tZQAzvD0nP8ys0anUao0av1Wl5ur5-IymWkEM1-kNRn4AL5AA

Related Issues: #19846

Strings, especially string literals, ought to be comparable to an array of characters when indexed.
When accessing a string "foo" the type should be comparable to indexing readonly [ 'f', 'o', 'o' ], especially considering that JavaScript strings are completely immutable.

I doubt anyone would be against the lengths being known too, ex: const length: 4 = "test".length;

@weswigham
Copy link
Member

I had an old PR for this: #19846

@ghost
Copy link
Author

ghost commented Oct 11, 2020

I had an old PR for this: #19846

Oh, I must have not searched well enough, adding that to the "related issues," except now this looks like a duplicate :/

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Oct 12, 2020
@RyanCavanaugh
Copy link
Member

When would this be useful?

@ghost
Copy link
Author

ghost commented Oct 12, 2020

When would this be useful?

Well, this can be broken up into two distinct parts:
Safety:
it's just as safe as using as const on an array of characters. How can you go wrong there? Strings are immutable, so they might as well always be as const. Knowing the exact characters at each index can only help. I'm not sure what the uses will be, but they should fall in line exactly with that of the contrasted equivalent: character arrays. It's a pretty small niche, but it's something. I was personally working on a data compression/decompression algorithm, whether I should have or not, I indexed into a "known" string, and expected "known" results, at worst, a union of the characters in the string, but instead, I was presented with string.

Bug:
All numerical accesses currently give string. There was a recent configuration flag offered for more pessimistic indexing on arrays. I am not suggesting that we make that the default here at all, but safety, as we all know, is pretty important. The problem is that all indexes yield string, whether or not those keys are "safe," or "unsafe" keys. This is even more problematic when you view strings not as character arrays, but instead as character tuples, which is what I am suggesting. It's blatantly obvious that something like "foo"[-1] is just plain wrong, and should never end up in anyone's codebase. Hopefully, TS helps prevent that from happening in the future.

@krryan
Copy link

krryan commented Apr 10, 2022

When would this be useful?

A question on Stack Overflow for which this property would be the most obvious and clearest solution: extracting the first character of a string literal. Can be done with S extends `${infer H}${string}` ? H : never, but this is a highly non-obvious approach, much more complicated, and not well documented (the PR explains this behavior but it is not in the docs). Moreover, reaching characters that aren’t first gets quite annoying quite quickly: either you have to hard-code each index using `${string}${string}${infer Char2}${string}` for [2], or you have to use something like

type NthCharOf<S extends string, N extends number> =
  string extends S ? string :
  number extends S ? EachCharOf<S> :
  S extends `${infer H}${infer T}`
    ? [N, 0] extends [0, N]
      ? H
      : NthCharOf<T, Decrement<N>>
    : undefined;

type EachCharOf<S extends string, A extends string = never> =
  string extends S ? string :
  S extends `${infer H}${infer T}`
    ? EachCharOf<T, A | H>
    : never;

type Decrement<N extends number> =
  TupleOfLengthN<N> extends [unknown, ...infer T] ? T['length'] : number;

type TupleOfLengthN<N extends number, T extends unknown[] = []> =
  [N, T['length']] extends [T['length'], N] ? T :
  TupleOfLengthN<N, [...N, unknown]>;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants