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

modify String.split and Array.pop to fix a non-bug(!?) in top-repos pubkey/rxdb #56323

Closed
6 tasks done
craigphicks opened this issue Nov 6, 2023 · 7 comments
Closed
6 tasks done
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@craigphicks
Copy link

craigphicks commented Nov 6, 2023

🔍 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

⭐ 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

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

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

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

@MartinJohns
Copy link
Contributor

MartinJohns commented Nov 6, 2023

pop(): this["length"] extends 0 ? undefined : T;

The type of length for arrays is number. This check will not work as you expect, because number never extends 0. This seems to be a duplicate of #30406.

split(separator: string | RegExp, limit?: number): [string, string[]];

split does not return a tuple with two elements. You probably meant to write [string, ...string[]], but this would also be wrong as split() can return an empty array. This seems to be a duplicate of #49635.

@MartinJohns
Copy link
Contributor

MartinJohns commented Nov 6, 2023

You changed it to:

split(separator: ""): string[];
split(separator: string): [string, ...string[]];

This will only work for the string literal "", but if you have an empty string in a string typed variable you will still get the wrong type. This is mentioned in the issue I linked.

@craigphicks
Copy link
Author

craigphicks commented Nov 6, 2023

You changed it to:

split(separator: ""): string[];
split(separator: string): [string, ...string[]];

This will only work for the string literal "", but if you have an empty string in a string typed variable you will still get the wrong type.

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 craigphicks reopened this Nov 6, 2023
@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Nov 6, 2023
@craigphicks
Copy link
Author

pop(): this["length"] extends 0 ? undefined : T;

The type of length for arrays is number. This check will not work as you expect, because number never extends 0.

Tuples have length with a literal number.

@RyanCavanaugh
Copy link
Member

But approximately no one calls pop on a tuple

@MartinJohns
Copy link
Contributor

will generally always use the string literal.

Bold assumption you make for a suggested change to the standard library types.

@craigphicks
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants