Repository navigation
Allow element access of literal type strings (which enables the auxiliary proposal: improve Array.split return type for tuples) #56332
Description
🔍 Search Terms
-
"allow element access of literal string type" - Nothing found.
-
"Literal String should have literal length"
Turned up Template Literal Types with fixed or limited Length #52243 and Regex-validated string types (feedback reset) #41160, but those are both more complicated proposals with string ranges.
Anyway, this proposal doesn't use length, but does use character access, because length is less precise.
With regards to split (which is an auxiliary proposal here):
- String.split is defined as returning string[], but values in the array can be undefined. #52299 (closed)
- String split return type #49635 (closed)
✅ Viability Checklist
- 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 isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion
Main Proposal: Type element access to Literal String types
Allow type element access to Literal String types. (Formatted string access could be a future extension of this proposal.)
This parallels the way in which tuples are treated.
Oddly, I haven't found a previous proposal for this, so one might conclude there is no practical need for it. However, it can be used to add other features, such as a more precise type for "String.split", which was proposed multiple times, but always closed without considering this prerequisite proposal which would make it possible.
The library definition for character access of String as it appears in lib.es5.d.ts
interface String {
readonly [index: number]: string;
}
is not unlike that for ReadonlyArray<T>
readonly [n: number]: T;
which is used to allow element access of readonly tuple types (all tuple types are readonly).
In a tuple the acessed element type value is calculated internally by the compiler, and is made available to the TypeScript user at development time. Notably, in an tuple like
[string, ...string[]]
where the length must be reported as number, the accessed element type
[string, ...string[]][0]
is string, and not string | undefined. That shows that element access gives more precise type information than length does.
The same can be done for literal strings.
Auxiliary Proposal: Narrowed String.split return type for separators of Literal String types having at least one character.
If literal string element access were available, then we could write a more precise definition for String.split as follows:
before:
interface String {
split(separator: string | RegExp, limit?: number): string[];
}
after:
interface String {
split<S extends string>(separator: S): string extends S ? string[] :
S[0] extends undefined ? string[] : [string, ...string[]];
split(separator: string | RegExp, limit?: number): string[];
}
📃 Motivating Example
function checkField(fieldName: string){
let firstPathPart: string;
if (fieldName.includes('.')) {
const partParts = fieldName.split('.');
firstPathPart = partParts[0]; // error
}
}
💻 Use Cases
- What do you want to use this for?
As described above.
- What shortcomings exist with current approaches?
As described above.
- What workarounds are you using in the meantime?
This "workaround" is definitely a hack, and I would never actually use this outside of a demonstration, but replacing the literal string element access with a TypeScript conditional type function CharAtZero, we can get an solution that works with the example problem shown above:
type CharAtZero<S extends string> = string extends S ? string :
S extends `${infer First}${string}` ? First : undefined;
// check result
type S0 = CharAtZero<"">; // undefined
type S1 = CharAtZero<".">; // "."
type S2 = CharAtZero<string>; // string
type S3<Prefixed extends string> = CharAtZero<`foo${Prefixed}`>;
type S31 = S3<"">; // "f"
type S4<Suffixed extends string> = CharAtZero<`${Suffixed}foo`>;
type S41 = S4<"">; // "f"
type S42 = S4<"a">; // "a"
interface String {
split<S extends string>(separator: S): string extends S ? string[] :
CharAtZero<S> extends undefined ? string[] : [string, ...string[]];
split(separator: string | RegExp, limit?: number): string[];
}
function checkField(fieldName: string){
let firstPathPart = fieldName;
if (fieldName.includes('.')) {
const partParts = fieldName.split('.');
firstPathPart = partParts[0];
}
}
const x1 = "".split('.'); // [string, ...string[]]
const x2 = x1[0]; // string
const x3 = "".split('.')[0]; // string
const x4 = "".split(''); // string[]
const x5 = "".split((0 as any as string)); // string[]
Activity
fatcerberus commentedon Nov 6, 2023
I’m like 99% positive this exact thing has been proposed before, but I can’t seem to find the duplicate issue. Huh.
MartinJohns commentedon Nov 6, 2023
Closest I found is #34692.
Also, the "goal" of this issue seems to be just another duplicate of #53362 (which provides a suggestion to deal with empty/non-empty strings).
craigphicks commentedon Nov 7, 2023
Thank you. There are plenty of suggestions there for applications.
The goal of This proposal the is main proposal, is it not? Nowhere in This proposal does not in anyway suggest adding using a complex type expression with inference to the library, which is what #53362 suggests.
MartinJohns commentedon Nov 7, 2023
You literally wrote "auxiliary proposal", where you propose to add a complex type to the library. Only slight difference is that with the option to index characters in string literals it wouldn't require inference.
String.split()seems to be your only use case, so calling it a duplicate is not far off. You might want to add more use cases if you have any.craigphicks commentedon Nov 7, 2023
Martin Johns (@MartinJohns) - As far as I can see this already a duplicate of #34692 - again, thank you for that. So there is no point in adding more use cases.