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

Normalize Specific String Length Behavior with Tuple's #43134

Closed
5 tasks done
sno2 opened this issue Mar 8, 2021 · 8 comments
Closed
5 tasks done

Normalize Specific String Length Behavior with Tuple's #43134

sno2 opened this issue Mar 8, 2021 · 8 comments
Labels
Duplicate An existing issue was already created

Comments

@sno2
Copy link
Contributor

sno2 commented Mar 8, 2021

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 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 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
@sno2
Copy link
Contributor Author

sno2 commented 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:

type Name = "Carter";

type FirstLetter = Name[0]; // "C"

type AllLetters = Name[number]; // "C" | "a" | "r" | "t" | "e" | "r"

type OutOfBoundsLetter = Name[6]; // error: String type 'Name' has no character at index '6'. [Very similar to tuple out of bounds error]

@MartinJohns
Copy link
Contributor

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.

That sounds like a poor use-case for this, it won't prevent invalid strings to be passed. #41160 would work for this.

@sno2
Copy link
Contributor Author

sno2 commented Mar 8, 2021

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.

That sounds like a poor use-case for this, it won't prevent invalid strings to be passed. #41160 would work for this.

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 only length that cryptographic hex strings can hold. The solution would be the following:

type CryptoPublicKey = string & { length: 64 };

type CryptoSecretKey = string & { length: 128 };

const public: CryptoPublicKey = "b2adab9840a26690b15c24bd7f1b19d317468d06e21e84f6333350263bc62c5c";

const secret: CryptoSecretKey = "ea01f13a513086dffeeb13d871e8c4fc38a6b19e8d203969c650e992a84139a374505c0e5ac292e0bafab13021c8fa76c63bf4be1759b324b95a9c9c0f2be0da";

const badSecret: CryptoSecretKey = "ea0"; // error: type 'ea0' is not assignable to type 'string & { length: 128 }'.  '3' and '128' are not compatible.

You would basically have the power to not only get the length of a string, but also constrict its length 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
Copy link
Contributor

MartinJohns commented 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
Copy link
Member

I absolutely know we have an issue on this already, but cannot find it 😒

@MartinJohns
Copy link
Contributor

I absolutely know we have an issue on this already, but cannot find it 😒

I thought so as well, but couldn't find it either... until now: #34692 Turns out we both commented on it.

@sno2
Copy link
Contributor Author

sno2 commented Mar 8, 2021

Closing in favor of #34692

@sno2 sno2 closed this as completed Mar 8, 2021
@RyanCavanaugh
Copy link
Member

Thank you for saving my sanity @MartinJohns πŸ˜…

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Mar 8, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants