-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Enhance members of literal type like const array type (e.g. string literal types should have literal length property) #34692
Comments
Sounds like a duplicate of #34589. |
No, I don't think so - #34589 is about tuple types. This is about using literal types for the |
Would be nice to understand why this would be useful |
@RyanCavanaugh To implement a type detecting the edge-underscored string, I've also published a new issue #34844, hoping (Literal.length - 1) also be the constant value. However, I can't sure that I've requested exact features for the edge-underscored string. If I'm forgetting something important, please inform me. |
There is a lot of use cases One use case is asserting that a method is only called for a single character, mimicking the type SingleChar = string & { length: 1 };
const charCodeOf = (char: SingleChar): number => char.charCodeAt(0);
charCodeOf('a'); // Should work
charCodeOf('asdhfkj') // Should fail
charCodeOf('') // Should also fail Also prevent literal empty strings from being passed as arguments: type EmptyString = string & { length: 0 };
type SingleChar = string & { length: 1 };
const lastChar = (str: Exclude<string, EmptyString>): SingleChar => str[str.length - 1]!;
lastChar('abc'); // Should work
lastChar(''); // Should fail
// We could also better narrow the return types
const typedLastChar = <S extends string>(
str: S,
): S extends EmptyString ? undefined : SingleChar => str[str.length - 1];
const ch1: string = typedLastChar('qwe'); // should work
const ch2: string = typedLastChar(''); // should fail
const ch3: undefined = typedLastChar(''); // should work I'm sure there's others cases to type against fixed length strings (fixed length hashs maybe? or identifications cards?) In my opninion the proposal is a really good addition, it's amazing for typing DSLs and builders |
Another use case: currently some type level arithmetics leverage the fact that array length produces number literal. With template literal type, if Another example of type level arithmetics: Implementing Arithmetic Within TypeScript’s Type System But of course, the best solution is adding type Add<A extends number, B extends number> = intrinsic
type Subtract<A extends number, B extends number> = intrinsic
type Increment<A extends number> = Add<A, 1>
type Decrement<A extends number> = Subtract<A, 1> 🍺 |
As pointed out, i think it would be nice to have some better typing possibilities on strings. Use cases// --- Improved getter
type FirstChar = 'foobar'[0] // 'f'
type LastChar = 'foobar'[-1] // 'r'
// --- Intrinsic type to get character at index
type FirstChar = CharAt<'foobar', 0>> // 'f'
type LastChar = CharAt<'foobar', -1> // 'r'
// --- Character ranges
type ARange = 'a0' ... 'a1' // 'a0' | 'a1' | 'a2' | 'a3' | 'a4' | 'a5' | 'a6' | 'a7' | 'a8' | 'a9'
type HexCharacter = 'a' ... 'f' | '0' ... '9' // 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
// --- Intrinsic types to restrict string character and size
type HexString = StringOf<HexCharacter>
type RGB = StringOf<HexCharacter, 6, 8>
type MD5 = StringOf<HexCharacter, 32>
type LenghtOf3 = StringOf<3>
type LenghtOf3To6 = StringOf<3, 6>
// --- Use cases
const hex: HexString = 'abcdef0123465789'
const rgb: RGB = 'ff0080'
const rgba: RGB = 'ff0080ff'
const MD5: RGB = '3858f62230ac3c915f300c664312c63f' |
Hello, for those looking for a more efficient solution, I've created a logarithmic implementation for anyone who is still facing this issue but can't use the linear implementation due to the poor performance of it https://gist.github.com/sno2/7dac868ec6d11abb75250ce5e2b36041 Edit: hmm I will consider a logarithmic string indexer as well |
would a PR on this be welcomed? If so, I'd like to give it a shot. |
This const lastChar = (str: Exclude<string, EmptyString>): SingleChar => str[str.length - 1]!; wouldn't work well even if we narrowed down string literals' length, as |
Some extra comments from myself in this issue that I just closed for being (almost) duplicated: About some proposals I've seen in this thread, some general comments:
|
I agree, I wrote that comment a long time ago, and I think I was just throwing out ideas. I didn't go as far as defining formal specifications for it. If we go the maximally minimal route, I would say that having literal strings carry their length information would already allow a bunch of interesting use-cases. What I'm saying is: type Literal = 'string-literal'
type Len = Literal['length'] // this would be 14 Another way of implementing the "no empty strings" example could be: const lastChar = <T extends string>(str: T extends { length: 0 } ? never : T): SingleChar => str[str.length - 1]!; Other interesting examples with generics would be type guard functions for string length: const ofSize = <T extends string, N extends number>(txt: T, size: N): txt is string & { length: N } =>
txt.length === size
// then we could use it for narrowing types:
if (ofSize(txt, 1)) {
console.log(lastChar(txt))
} To be completely honest it still feels a bit janky, and I still have some open questions
At the end of the day, I ask myself how useful would this be? I sent my first comment on this thread more than 4 years ago, and since then didn't really have any compelling use-cases for it, besides the check for empty strings here and there. Maybe other use-cases would be for string formats, like ISO dates or UUIDs, but yet I'm not convinced it is worth the complications in implementation |
I concur that type IsRGB<S extends string> = S extends `#${infer R}` & {length: 4 | 7} ? IsHexString<R> : never
type IsWellFormedCurrencyCode<S extends string> = S extends {length: 3} ? IsAlphaString<S> : never So even if none of the other proposals were implemented, string literal length would still be valuable to add. That being said, there are ways to further enhance this behavior without creating new utility types. For example: // Synonymous with the "StringOf<HexCharacter>" idea mentioned above,
// but implemented via an index signature instead of a utility type:
type HexString = string & {[_: number]: HexDigit} Alternatively, we could implement the same thing via the existential types proposal: type HexString = <exists S extends string> IsHexString<S> extends never ? never : S Either way, that would allow us to do some pretty cool stuff with the string literal length: type RGB = `#${HexString}` & {length: 4 | 7}
type XXXX = HexString & {length: 4}
type XXXXXXXX = HexString & {length: 8}
type XXXXXXXXXXXX = HexString & {length: 12}
type UUID = `${XXXXXXXX}-${XXXX}-${XXXX}-${XXXX}-${XXXXXXXXXXXX}` |
The const array type, its principle members are also be the constant. When define a const array
[number, string, boolean]
, itslength
type be3
. Also, when access to special index, the type also points the exact const type.However, the literal type is not like the const array. When define a literal type "something", its
length
be not9
butnumber
. Also, when access to a special index, the type is not a special literal type but astring
type.What about enhancing the literal type to be like the const array type?
The text was updated successfully, but these errors were encountered: