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

Narrow length of tuples #33182

Open
5 tasks done
MicahZoltu opened this issue Sep 1, 2019 · 3 comments
Open
5 tasks done

Narrow length of tuples #33182

MicahZoltu opened this issue Sep 1, 2019 · 3 comments
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@MicahZoltu
Copy link
Contributor

Search Terms

tuple length narrow

Suggestion

Narrow the length property on tuple types when used as an Array.

Use Cases

Often times a method expects an array of some particular length. This can be expressed as [T, T, T] where the number of tuple items is the length of the array. However, sometimes the length is itself generic and in order to properly constrain the type you need to type the parameter as Array<T> & {length:L}. The problem with this is that if you have a tuple of length L, the compiler still assumes that the length is of type number.

It would be useful if a tuple type correctly narrowed the type of length to be the number of elements in the tuple. This way you could pass a tuple into a method that expects a fixed length array without casting.

Examples

declare function bytesToInt(bytes: Array<number> & {length:4}): number
bytesToInt([1,2,3,4]) // currently errors, would be nice if it didn't.
bytesToInt([1,2,3,4] as Array<number> & {length:4}) // error prone and something the compiler could do for you.

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, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

It would be super cool if TypedArrays could get this behavior, since their length is fixed at construction time. I'm not sure exactly how to achieve this, so I'll leave it out of this suggestion but if it is possible for new Uint8Array(4) to result in a type Uint8Array & {length:4} that would be great.

@AnyhowStep
Copy link
Contributor

I think one of the problems is that you can have an "array" of length 9001, but "1337" in myArr can be false

myArr = []
myArr.length = 9001
alert(myArr.length)
alert("1337" in myArr)

9001
false


Whereas, with a tuple type, if it had length 9001, it will have all keys from 0 till 9000, inclusive of both

@MicahZoltu
Copy link
Contributor Author

@AnyhowStep What problems are caused (with regards to this feature request) by JavaScript supporting sparse arrays? I recognize that sparse arrays can certainly cause problems, but that seems to be true unrelated to this feature request.

@jcalz
Copy link
Contributor

jcalz commented Sep 1, 2019

Looks like the problem here is that {length: 4} doesn't prompt the compiler not to widen the type of the array literal [1, 2, 3, 4] to number[]. There are various tricks used to prevent the widening of the types of input parameters, and & {length: 4} isn't one of them. But (Array<T> | [T]) is, so you could do this:

declare function bytesToInt(
  bytes: (Array<number> | [number]) & { length: 4 }
): number;

bytesToInt([1, 2, 3, 4]); // okay
bytesToInt([1]); // error!
bytesToInt([1, 2, 3]); // error!
bytesToInt([1, 2, 3, 4, 5]); // error!

so you can get the generic length version like this;

declare function genericBytesToNum<L extends number>(
  length: L,
  tuple: (Array<number> | [number]) & { length: L & {} } // noinfer
): number;

genericBytesToNum(2, [1, 2]); // okay
genericBytesToNum(2, [1, 2, 3, 4]); // error
genericBytesToNum(0, []); // okay
genericBytesToNum(0, [1, 2, 3, 4]); // error
genericBytesToNum(4, [1, 2, 3, 4]); // okay
genericBytesToNum(4, [1, 2, 3, 4, 5]); // error

Note that without that & {} the function infers L from both length and tuple, which would give you something like 2 | 4 for genericBytesToNum(2, [1,2,3,4]), defeating the purpose. You want tuple to be non-inferential, and & {} is one way to get this behavior.

Playground link

@sandersn sandersn added In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Sep 3, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants