Description
Suggestion
π Search Terms
- String#length
- String
- Intrinsic string length property
- String.length type
β Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript codeThis wouldn't change the runtime behavior of existing JavaScript codeThis could be implemented without emitting different JS based on the types of the expressionsThis 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
Currently, tuple types have the length
property intrinsicly computed as shown in the folllowing:
type Names = ["Joe", "Jeff", "Jerry"];
type NamesLength = Names["length"]; // 3
However, for specific string types such as "Joe"
, the length
property is still of type number
:
type Name = "Joe";
type NameLength = Name["length"]; // number
I would like to propose that the length
property be computed in the type type system such that Person["length"]
(as shown in the previous example) would evaluate to 3
(the proper length of the string) instead of number
.
type Name = "Joe";
type NameLength = Name["length"]; // 3
Now, time for a few of the hairy details.
One of them is how the behavior for the property would be when the type is a union of strings. The best way to define this behavior is by following what happens when you get the length
property on a union of as defined in the following code:
type Params1 = [name: string];
type Params2 = [name: string, age: number];
type EitherParams = Params1 | Params2;
type EitherParamsLength = EitherParams["length"]; // 1 | 2
And the string behavior for getting the length of union strings should not contain any "magic" either, just give out a union of each of the string's length:
type Name = "Joe" | "Jeff" | "Robert";
type NameLength = Name["length"]; // 3 | 4 | 6
type Version = "1" | "1.1-alpha1" | "1.2.1" | "1.2.2";
type FormattedVersion = `v${Version}`; // "v1" | "v1.1-alpha1" | "v1.2.1" | "v1.2.2"
type FormattedVersionLength = FormattedVersionLength["length"]; // 2 | 11 | 6
The generic string
type should also behave exactly like a generic Array
type when you are getting the length
.
interface Person {
name: string;
age: number;
}
type People = Person[];
type PeopleLength = People["length"]; // number
type Name = string;
type NameLength = Name["length"]; // number
With union types for the strings when combining string
and a specific string type such as "hello"
, the string
length
should always remain supreme. This behavior is not exactly unexpected because unions like string | "foo" | "asdf"
evalute to just string
.
type Name = string | "foo" | "asdf"; // string
type NameLength = Name["length"]; // number
π Motivating Example
The feature would improve TypeScript because it makes the behavior of similarly-structured concepts (specific string and tuple) more universal. Currently, if I wanted to get the length of a string type, I would have to do something like the following:
/** Turns a string type into a tuple of the characters. */
type Chars<
T extends string,
$Chars extends string[] = []
> = T extends `${infer $Ch}${infer $Rest}`
? "" extends $Rest
? [...$Chars, $Ch]
: Chars<$Rest, [...$Chars, $Ch]>
: never;
type Name = "Carter";
type NameChars = Chars<Name>; // ["C", "a", "r", "t", "e", "r"]
type NameLength = Chars<Name>["length"]; // 6
However, the Chars
type will easily overload due to its recursive nature:
/** Turns a string type into a tuple of the characters. */
type Chars<
T extends string,
$Chars extends string[] = []
> = T extends `${infer $Ch}${infer $Rest}`
? "" extends $Rest
? [...$Chars, $Ch]
: Chars<$Rest, [...$Chars, $Ch]>
: never;
type Name = "a not-even-that-long string";
type NameChars = Chars<Name>; // overload error
type NameLength = Chars<Name>["length"]; // unreachable
π» Use Cases
The ability to get the length of a string type just by accessing the string
property has many benefits. One of which is being able to lock a string to a specific length for something such as a date in which you want to specify the number of characters that come in each part.
type ExtractDateParts<
T extends string
> = T extends `${infer M}-${infer D}-${infer Y}`
? M["length"] | D["length"] extends 2
? Y["length"] extends 4
? { month: M; day: D; year: Y }
: never
: never
: never;
type Valid1 = ExtractDateParts<"10-13-2001">; // { month: "10", day: "17", year: "2004" }
type Invalid1 = ExtractDateParts<"10-1-2001">; // never - day has a length of 1
type Invalid2 = ExtractDateParts<"101-12-2001">; // never - month has a length of 3
Activity
sno2 commentedon Mar 8, 2021
I thought that the normalization of the indices of specific string types and tuples should not be included in this as it seemed too broad. However, if anyone would like to write out an issue for that that would normalize the type behaviors and allow for the following code to work as indicated then please do so:
MartinJohns commentedon Mar 8, 2021
That sounds like a poor use-case for this, it won't prevent invalid strings to be passed. #41160 would work for this.
sno2 commentedon Mar 8, 2021
Sorry, I was trying to explain one of the use-cases of being able to cap strings at a specific length. A use case that can be solved by having the
length
key intrinsically updated is setting the onlylength
that cryptographic hex strings can hold. The solution would be the following:You would basically have the power to not only get the
length
of a string, but also constrict itslength
without even requiring generics. Now, of course you wouldn't be able to verify that the contents of the string are valid in this case, but we are able to set the length of the string which is way better than nothing and can be a base for building better-typed strings. This code could then be modified slightly to solve #41160 (comment).MartinJohns commentedon Mar 8, 2021
Would a string containing only emojis be a valid
CryptoSecretKey
? I doubt so.This just seems like the wrong approach. None of your suggestions would be solved by having a specific length on the string.
RyanCavanaugh commentedon Mar 8, 2021
I absolutely know we have an issue on this already, but cannot find it π’
MartinJohns commentedon Mar 8, 2021
I thought so as well, but couldn't find it either... until now: #34692 Turns out we both commented on it.
sno2 commentedon Mar 8, 2021
Closing in favor of #34692
RyanCavanaugh commentedon Mar 8, 2021
Thank you for saving my sanity @MartinJohns π