TypeScript Version: 2.6.0-rc
Code
let a: [string] = ['a', 'b'];
Expected behavior:
Error: [string, string] is not compatible with [string].
Actual behavior:
Compiles A-ok
The type of string[] and [string, string] is assignable to [string]. Tuple types are not checked for excess elements when _fresh_, unlike objects, so the right hand side is assignable to left hand side.
It is arguable though when the array is fresh, it should follow the same rules as #3823 for objects.
Think of it like this
interface Tuple1<T> extends Array<T> {
0: T;
}
interface Tuple2<T, U> extends Array<T | U> {
0: T;
1: U;
}
declare let a: Tuple1<string>;
declare let b: Tuple2<string, string>;
a = b; // works, 'b' is also viewed as a `string[]` with a `0: string`
See also: #6229
Did you make some specific decision to untype arrays then, compared to normal types? I don't see why this is any different from:
let a: { x: number } = { x: 4, y: 5 };
... which compile errors as you'd expect. If this is intended typescript behavior permanently, then we will have to excise all usage of tuples from our codebase and go to slower object-based types everywhere. This implementation is almost completely un-typesafe. :(
let a: { x: number } = { x: 4, y: 5 };
TypeScript's original behavior allowed this. The motivating example for disallowing it was options-bag-style APIs where people were misspelling optional properties. Are you frequently running into excess tuple errors? (edit: not being cheeky, I'm legitimately looking for motivating use cases)
Yes, we just made a correction to our codebase to change over a hundred usages of tuples that were being used incorrectly.
The question in my book is what the pros and cons of each way are. What are the pros of allowing you to do what feels like, to me, "invalid" assignments to tuples? I.e. why SHOULD it be allowed to do:
let a: [string] = [ 'hi', 12345 ];
That feels like it's an engineer bug in 100% of circumstances, and if we have the ability to fix it with TSC, then we should.
Actually that example is disallowed. Excess assignment slots only work when the excess values are assignable to one of the member of the tuple. 🤷♂️
let a: [ string, number ] = [ 'hi', 12345, 'foo' ]; // ok
let b: [string] = ['hi', 12345]; // bad
let c: [string | number] = ['hi', 12345]; // ok
Personal opinion too, when the array is fresh, it should be strictly checked against the left hand, because as @deregtd indicates, how is that ever a desired situation? Especially considering how the following works:
let a: [string, number] = ['hi', 12345];
let b: [string] = a; // not assignable
let c: [string|number] = a; // ok
That, again, while maybe _sound_ from a type system perspective, I can't see why the assignment to b is bad while c is good from an identifying constructs that are likely to be errors. Either both are desirable, or neither.
From #19536, we would like to consider one of two alternatives here:
length property is manifest in the type, and two tuples are only assignable if they have the exact same length. Tuples will need to be ReadonlyArray for this to work. We will have to include this under a strict flag.See notes in #19585
@mhegazy (1) seems more preferable and is what #6229 and #17765 propose.
I'll implement (2) just to see how well it works, but I played with (1) and it works great. Note that #17765 implements (1) but that doesn't include the open-length tuples from #6629 (or their improved spread checking).
@sandersn I guess you meant 6629 #6229.
My vote goes for freshness, unless the full Strict/Open length tuples proposal gets implemented. Working with non-aliasable tuples is just painful. It will lead to many overloads, which is hardly ideal.
E.g. this is a Haskell library trying to provide uniform usage patterns for tuples of disparate arities: https://github.com/augustss/tuple. The implementation code consists of ugly, automatically generated type class instances (e.g. Select). I don't think we'd like to go in that direction.
Should be fixed by https://github.com/Microsoft/TypeScript/pull/17765
Most helpful comment
From #19536, we would like to consider one of two alternatives here:
lengthproperty is manifest in the type, and two tuples are only assignable if they have the exact same length. Tuples will need to beReadonlyArrayfor this to work. We will have to include this under a strict flag.