You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
1-tuples seem to be treated the same as arrays #1292
It seems to be treating [[K, V]] (1-tuple of 2-tuple of K and V) as [K, V][] (array of 2-tuple of K and V). So y is being inferred as [K, V] instead of {}
This looks to be by design. If you had a[number, number] tuple type, you'd see this same behavior at index 2. It used to be that at an arbitrary or unaccounted index, you'd get the best-common type of the constituent types, which often resulted in {}. In 1.4, for your last example, x will have the type T | U because we'll be using the union type of the constituent types.
Hmm. I assumed the intent of inferring {} was specifically to disallow doing anything useful with indices higher than in the tuple signature without casting (and thus expressing the intent that you know what you're doing). I don't think I understand the logic behind giving higher indices the union type of the defined indices - the higher indices are undefined as far as the type system is concerned, so the only options that seem right are compiler error, any, or {}.
Also, in that case, shouldn't v here would be inferred as B | C (or atleast A) and not as {} ?
Correct, when union types make it in to an official release, you'll no longer get {} for v. The idea of tuple types is that they tie in closely to ES6 destructuring semantics. ES6 doesn't have tuples, but it does have arrays, and I think we didn't want to limit the expressivity you get with arrays. For instance, a contrived example might be:
Daniel Rosenwasser (@DanielRosenwasser) I was asking why v isn't inferred as A, since in your first post you said it should be inferred as the best common type.
Your example does make the case for having tuples decay to arrays for the purpose of calling methods, but I still feel that indexer access for tuples should prevent accessing indices higher than in the type signature, either via a compiler error or requiring an explicit downcast from {}.
classA{foo: string;}classBextendsA{bar: string;}classCextendsA{baz: string;}varvalue: [B,C];vart=value[0];// Bvaru=value[1];// Cvarv=value[2];// B | C
the type of v is indeed inferred as B | C, and not as {}. This is with the latest compiler in master and it is the intended behavior.
The reason we use the union type for elements outside the declared tuple range is code like this:
var t = value[0]; // B
var u = value[1]; // C
var i = condition ? 0 : 1;
var v = value[i]; // B | C
We could consider giving an error when the index is known to be out of range (i.e. in the isolated case of an out of range numeric literal), but I'm not sure it is worth the effort. As an aside, we will report an error with destructuring, i.e.
var value: [B, C];
var [t, u] = value; // Ok, t: B and u: C
var [t, u, v] = value; // Error, no matching value for 'v'
The destructuring work is slated for the 1.5 release.
Since TS considers m-tuples to be assignable to n-tuples if n < m and the first n types of both are the same, one can write code like this:
classA{foo: string;}vartuple1: [number,string]vartuple2: [number,string,A];functionbar(tuple: [number,string]){vart=tuple[2];if(t!==undefined){/* we were passed a 3-tuple, do things with t */}}bar(tuple1);bar(tuple2);
If t is inferred as number | string it will become a runtime problem when calling bar with tuple2, so it would be nice to have it not compile as-is without even a warning. Please consider either a compiler warning / error or changing the inference rules in such a way that it requires an explicit cast to be able to do anything useful with t.
It is good that destructuring assignment is going to give an error on accessing higher indices than the tuple signature. But then users will surely be confused at the inconsistency between destructuring a tuple automatically (conservative, default is type-safe) and manually (default is type-unsafe).
Incidentally, I thought the way to make my above example type-safe might be to change the signature to take in the 3-tuple I want, and add a 2-tuple overload.
classB{bar: {baz: string}};functionbar(tuple: [number,string]);functionbar(tuple: [number,string,B]){vart=tuple[2];if(t!==undefined){// Being called via 3-tuple overloadconsole.log(t.bar.baz);}}
I expected the call to bar(tuple2) to not compile since B is not assignable to A, but it ends up being compiled against the 2-tuple overload. So the t.bar.baz access would blow up at runtime.
classA{foo: string;}vartuple1: [number,string]vartuple2: [number,string,A];functionbar(tuple: [number,string]){vart=tuple[2];if(t!==undefined){/* we were passed a 3-tuple, do things with t */}}bar(tuple1);bar(tuple2);// Error
does indeed produce an error because string | number | A (the type of tuple2's numeric index signature) is not assignable to string | number (the type of tuple1's numeric index signature). It only works as long as the extra elements in tuple2 are strings or numbers.
Again, this is with the latest compiler in master. Are you using a different one?
I was using the playground. I assumed the only difference between master and 1.3 was the inferered type of the third tuple member, not the assignability of tuples itself. Happy to be shown wrong!
I'm not entirely convinced it makes sense to infer higher indices than in the tuple signature as a union of the types of the other indices (and in the 1-tuple case leading to the results in the OP). Especially since destructuring is not going to behave the same way.
But since the master compiler does not allow assigning larger tuples to smaller ones in the type-unsafe cases, I guess this design is here to stay. I'll close this issue.
Activity
DanielRosenwasser commentedon Nov 28, 2014
This looks to be by design. If you had a
[number, number]tuple type, you'd see this same behavior at index2. It used to be that at an arbitrary or unaccounted index, you'd get the best-common type of the constituent types, which often resulted in{}. In 1.4, for your last example,xwill have the typeT | Ubecause we'll be using the union type of the constituent types.From 3.3.3 in the Spec:
Arnavion commentedon Nov 28, 2014
Hmm. I assumed the intent of inferring
{}was specifically to disallow doing anything useful with indices higher than in the tuple signature without casting (and thus expressing the intent that you know what you're doing). I don't think I understand the logic behind giving higher indices the union type of the defined indices - the higher indices are undefined as far as the type system is concerned, so the only options that seem right are compiler error,any, or{}.Also, in that case, shouldn't v here would be inferred as
B | C(or atleastA) and not as{}?DanielRosenwasser commentedon Nov 29, 2014
Correct, when union types make it in to an official release, you'll no longer get
{}forv. The idea of tuple types is that they tie in closely to ES6 destructuring semantics. ES6 doesn't have tuples, but it does have arrays, and I think we didn't want to limit the expressivity you get with arrays. For instance, a contrived example might be:Arnavion commentedon Nov 29, 2014
Daniel Rosenwasser (@DanielRosenwasser) I was asking why v isn't inferred as A, since in your first post you said it should be inferred as the best common type.
Your example does make the case for having tuples decay to arrays for the purpose of calling methods, but I still feel that indexer access for tuples should prevent accessing indices higher than in the type signature, either via a compiler error or requiring an explicit downcast from
{}.DanielRosenwasser commentedon Nov 29, 2014
Sorry, I missed that question.
Yes, it's a bit odd. That's actually the way best common type was calculated, even in the following context:
In 1.4, we will instead use the union type of the contained expressions' types.
ahejlsberg commentedon Nov 29, 2014
Arnav Singh (@Arnavion) In your example:
the type of
vis indeed inferred asB | C, and not as{}. This is with the latest compiler in master and it is the intended behavior.The reason we use the union type for elements outside the declared tuple range is code like this:
We could consider giving an error when the index is known to be out of range (i.e. in the isolated case of an out of range numeric literal), but I'm not sure it is worth the effort. As an aside, we will report an error with destructuring, i.e.
The destructuring work is slated for the 1.5 release.
Arnavion commentedon Nov 29, 2014
Since TS considers m-tuples to be assignable to n-tuples if
n < mand the first n types of both are the same, one can write code like this:If t is inferred as
number | stringit will become a runtime problem when calling bar with tuple2, so it would be nice to have it not compile as-is without even a warning. Please consider either a compiler warning / error or changing the inference rules in such a way that it requires an explicit cast to be able to do anything useful with t.It is good that destructuring assignment is going to give an error on accessing higher indices than the tuple signature. But then users will surely be confused at the inconsistency between destructuring a tuple automatically (conservative, default is type-safe) and manually (default is type-unsafe).
Incidentally, I thought the way to make my above example type-safe might be to change the signature to take in the 3-tuple I want, and add a 2-tuple overload.
I expected the call to bar(tuple2) to not compile since B is not assignable to A, but it ends up being compiled against the 2-tuple overload. So the t.bar.baz access would blow up at runtime.
ahejlsberg commentedon Nov 29, 2014
Arnav Singh (@Arnavion) Your example:
does indeed produce an error because
string | number | A(the type of tuple2's numeric index signature) is not assignable tostring | number(the type of tuple1's numeric index signature). It only works as long as the extra elements in tuple2 are strings or numbers.Again, this is with the latest compiler in master. Are you using a different one?
Arnavion commentedon Nov 29, 2014
I was using the playground. I assumed the only difference between master and 1.3 was the inferered type of the third tuple member, not the assignability of tuples itself. Happy to be shown wrong!
Arnavion commentedon Nov 30, 2014
I'm not entirely convinced it makes sense to infer higher indices than in the tuple signature as a union of the types of the other indices (and in the 1-tuple case leading to the results in the OP). Especially since destructuring is not going to behave the same way.
But since the master compiler does not allow assigning larger tuples to smaller ones in the type-unsafe cases, I guess this design is here to stay. I'll close this issue.