Repository navigation
modify String.split and Array.pop to fix a non-bug(!?) in top-repos pubkey/rxdb #56323
Description
🔍 Search Terms
#52299 (closed)
#30406 (marked as design limitation)
"typescript issues Literal String should have literal length"
Turned up #52243 and #41160
but those are both more complicated proposals with string ranges.
This proposal only considers plain string literal, for which implementing literal length would straightforward.
✅ 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
before:
type StringLength<S extends string> = S["length"];
type T1 = StringLength<string> // number
type T2 = StringLength<""> // number
type T3 = StringLength<"."> // number
type T3 = StringLength<`${TypeVariable}`> // number
interface String {
split(separator: string | RegExp, limit?: number): string[];
}
interface Array<T> {
pop(): undefined | T;
}
after:
type StringLength<S extends string> = S["length"];
type T1 = StringLength<string> // number
type T2 = StringLength<""> // 0
type T3 = StringLength<"."> // 1
type T3 = StringLength<`${TypeVariable}`> // number
interface String {
split<S extends string>(separator: S): number extends S["length] ? string[] : S["length"] extends 0 ? string[] : [string, ...string[]]
split(separator: string | RegExp, limit?: number): string[];
}
interface Array<T> {
pop(): this["length"] extends 0 ? undefined : T;
}
📃 Motivating Example
In the source of submitting another pull, this error occurred in the bot driver top-repos job
[pubkey/rxdb](https://github.com/pubkey/rxdb)
8 of 10 projects failed to build with the old tsc and were ignored
[config/tsconfig.types.json](https://github.com/pubkey/rxdb/blob/dd946905bf67c8cc26973003d63fa803215a3ca2/config/tsconfig.types.json)
error TS2322: Type 'string | undefined' is not assignable to type 'string'.
[src/plugins/dev-mode/check-schema.ts#L432](https://github.com/pubkey/rxdb/blob/dd946905bf67c8cc26973003d63fa803215a3ca2/src/plugins/dev-mode/check-schema.ts#L432)
[tsconfig.json](https://github.com/pubkey/rxdb/blob/dd946905bf67c8cc26973003d63fa803215a3ca2/tsconfig.json)
error TS2322: Type 'string | undefined' is not assignable to type 'string'.
[src/plugins/dev-mode/check-schema.ts#L432](https://github.com/pubkey/rxdb/blob/dd946905bf67c8cc26973003d63fa803215a3ca2/src/plugins/dev-mode/check-schema.ts#L432)
which maybe means it was already an error, and which can be reproduced in 5.2.2 with
function checkField(fieldName: string){
let lastPathPart = fieldName;
if (fieldName.includes('.')) {
const partParts = fieldName.split('.');
lastPathPart = partParts.pop(); // error
}
}
"".split('.') satisfies [string, ...string[]]; // error
"".split('.').pop() satisfies string; // error
💻 Use Cases
- What do you want to use this for?
Eliminating a top-repos non-bug (Non-bug because it is not expected to compile in 5.2.2),
and to address previous issues which were closed without considering the solution proposed here.
- What shortcomings exist with current approaches?
Using "String.split" with a non zero length literal string is a common case that case a narrower type is correct.
Additionally, "Array.pop" is sometimes used on tuples with length >0 , and in that case
a narrower type is correct.
They may be used in conjunction as shown.
The goal is a better user experience.
- What workarounds are you using in the meantime?
No reasonable workaround is possible unless string literals are given specific length
in the same way that tuples are.
Activity
MartinJohns commentedon Nov 6, 2023
The type of
lengthfor arrays isnumber. This check will not work as you expect, becausenumbernever extends0. This seems to be a duplicate of #30406.splitdoes not return a tuple with two elements. You probably meant to write[string, ...string[]], but this would also be wrong assplit()can return an empty array. This seems to be a duplicate of #49635.MartinJohns commentedon Nov 6, 2023
You changed it to:
This will only work for the string literal
"", but if you have an empty string in astringtyped variable you will still get the wrong type. This is mentioned in the issue I linked.craigphicks commentedon Nov 6, 2023
I closed this issue, but then on second thoughts I open it again. That because use of "" as a separator has a different semantics from a separator which is not empty - it splits the string into length 1 character strings.
Therefore, generally speaking, your linked example would not use a variable that could possibly be the empty string.
Conversely, when the semantic behavior desired is exactly to split the string into characters, it will generally always use the string literal. I'd bet greater than even odds the modification could be made without triggering runtime regressions.
craigphicks commentedon Nov 6, 2023
Tuples have length with a literal number.
RyanCavanaugh commentedon Nov 6, 2023
But approximately no one calls
popon a tupleMartinJohns commentedon Nov 6, 2023
Bold assumption you make for a suggested change to the standard library types.
craigphicks commentedon Nov 6, 2023
This proposal has some problems - I'm going to close it. The motivation, however, remains.
A improved approach may be possible. It's always worth considering how to improve TypeScript user experience.