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
Expected behavior:
I would expect this to be working code.
Actual behavior:
It errors with:
[ts] Argument of type 'string[][]' is not assignable to parameter of type 'Iterable<[string, string]>'.
Types of property '[Symbol.iterator]' are incompatible.
Type '() => IterableIterator<string[]>' is not assignable to type '() => Iterator<[string, string]>'.
Type 'IterableIterator<string[]>' is not assignable to type 'Iterator<[string, string]>'.
Types of property 'next' are incompatible.
Type '(value?: any) => IteratorResult<string[]>' is not assignable to type '(value?: any) => IteratorResult<[string, string]>'.
Type 'IteratorResult<string[]>' is not assignable to type 'IteratorResult<[string, string]>'.
Type 'string[]' is not assignable to type '[string, string]'.
Property '0' is missing in type 'string[]'.
If I give the compiler a clue using as [string, string] this becomes working code:
What you are referring to is a tuple. Tuples, because they are a construct of TypeScript only, cannot be contextually inferred, because they are ambiguous and TypeScript will default to the most "JavaScripty" type. For example, at run-time, these two things look exactly the same:
Thanks for commenting Kitson Kelly (@kitsonk). Due to the syntax similarity I hadn't made the distinction between arrays and tuples in this context. That's very helpful.
I'm not aware of any documentation of TypeScript Map usage; do you happen to know of any? I've blogged about this for now so the information is out there (and here obviously 😄).
I'd be interested to know if there's any possibility that the compiler could be made to infer a tuple in the case when a fixed size array is known to be exported from a function (as in this case)...
Oh that's interesting Ivo Gabe de Wolff (@ivogabe); it does look related but still different I think... I'm not clear the PR will allow TypeScript to infer a tuple type from an array; I stand to be corrected of course...
TypeScript already could infer tuple types, but you need to 'trigger' it. So when you pass an array to [T, U], it will try to make a tuple type. The problem in the issue I referenced was that the signature of Map didn't trigger there. The signature uses an iterable, and that isn't considered when the target is ES5. The fix was to add a new signature to it, which accepts an array instead of an iterable.
Oh nvm, this is a different issue. The problem is here that the inference does not work through function calls. So TS isn't triggered to create a union type in map. You could change your code to this:
Yes, I think there is a level of indirection there where TypeScript has to "bubble up" the inference of the types given the call site and where they are ambiguous it picks the more "likely" one. So string[] wins out over [ string, string ] because contextually, it is less "surprising":
function foo() {
return [ 'foo', 'bar' ];
}
const a = foo(); // string[] is less surprising than [ string, string ]
Because what are you intending? There are no contextual clues that you meant for that array to be fixed at only two elements. The only thing that is "safe" for TypeScript to assume is that it is an array of strings. There has to be a limit to how much guessing TypeScript can do on your behalf.
Mohamed Hegazy (@mhegazy) I don't know how complex it would be, but it would be really cool if contextual typing would work in such case too, as it would be useful for functional programming in TypeScript. Would that be doable? (I might have written too much Haskell lately).
it is a chicken and egg problem. we need the type to select the overload, that has the contextual type. ideally you would not forget about the tuples, and infer array too quickly, but that would be a breaking change. we are definitely open to proposals to make these scenarios better.
constfoo: [string,number]&(string|number)[]=['bar',12];constbar=foo[0];// stringfoo.push('bar');constbaz=foo[1];// numberconstqat=foo[2];// string | numberconstqux=foo.shift();// string | numberconst[a,b]=foo;// a is string, b is number but in reality it is reversed
Ohhh... I see. A tuple can't really have push or shift methods, because its type would effectively have to change every time they were called and no type system I'm aware of supports that, probably because it's more reasonable to just make tuples immutable and use slice and concat methods instead (this could even be implemented without any reallocations in languages with strong move semantics)
So a [string,number] & (string|number)[] doesn't really make sense, conceptually, because there can't be a type that has all of the methods of both of the conjuncts. I suppose the most correct signature for ['bar', 12] would be Into<[string,number]> & Into<(string|number)[]>, it can be converted Into either of the two types, but it hasn't decided which it is yet, it is certainly not both at the same time. I don't suppose this is relevant to typescript, though, sorry.
Activity
kitsonk commentedon Jun 2, 2016
What you are referring to is a tuple. Tuples, because they are a construct of TypeScript only, cannot be contextually inferred, because they are ambiguous and TypeScript will default to the most "JavaScripty" type. For example, at run-time, these two things look exactly the same:
I am sure the TypeScript team would be glad to accept a PR on the documentation.
johnnyreilly commentedon Jun 2, 2016
Thanks for commenting Kitson Kelly (@kitsonk). Due to the syntax similarity I hadn't made the distinction between arrays and tuples in this context. That's very helpful.
I'm not aware of any documentation of TypeScript Map usage; do you happen to know of any? I've blogged about this for now so the information is out there (and here obviously 😄).
I'd be interested to know if there's any possibility that the compiler could be made to infer a tuple in the case when a fixed size array is known to be exported from a function (as in this case)...
ivogabe commentedon Jun 2, 2016
TypeScript should be able to infer a tuple type in the Map constructor. Might #8407 be related?
johnnyreilly commentedon Jun 2, 2016
Oh that's interesting Ivo Gabe de Wolff (@ivogabe); it does look related but still different I think... I'm not clear the PR will allow TypeScript to infer a tuple type from an array; I stand to be corrected of course...
ivogabe commentedon Jun 2, 2016
TypeScript already could infer tuple types, but you need to 'trigger' it. So when you pass an array to
[T, U], it will try to make a tuple type. The problem in the issue I referenced was that the signature of Map didn't trigger there. The signature uses an iterable, and that isn't considered when the target is ES5. The fix was to add a new signature to it, which accepts an array instead of an iterable.johnnyreilly commentedon Jun 2, 2016
So you think the PR will resolve this? Or is that only in the case where the target is ES5? (I'm targeting ES6)
ivogabe commentedon Jun 2, 2016
Oh nvm, this is a different issue. The problem is here that the inference does not work through function calls. So TS isn't triggered to create a union type in
map. You could change your code to this:kitsonk commentedon Jun 2, 2016
Yes, I think there is a level of indirection there where TypeScript has to "bubble up" the inference of the types given the call site and where they are ambiguous it picks the more "likely" one. So
string[]wins out over[ string, string ]because contextually, it is less "surprising":Because what are you intending? There are no contextual clues that you meant for that array to be fixed at only two elements. The only thing that is "safe" for TypeScript to assume is that it is an array of strings. There has to be a limit to how much guessing TypeScript can do on your behalf.
johnnyreilly commentedon Jun 2, 2016
Cheers for the alternate syntax Ivo Gabe de Wolff (@ivogabe).
mhegazy commentedon Jun 2, 2016
As noted by Ivo Gabe de Wolff (@ivogabe) and Kitson Kelly (@kitsonk) tuple types are never inferred without a contextual type.
ivogabe commentedon Jun 2, 2016
Mohamed Hegazy (@mhegazy) I don't know how complex it would be, but it would be really cool if contextual typing would work in such case too, as it would be useful for functional programming in TypeScript. Would that be doable? (I might have written too much Haskell lately).
mhegazy commentedon Jun 2, 2016
it is a chicken and egg problem. we need the type to select the overload, that has the contextual type. ideally you would not forget about the tuples, and infer array too quickly, but that would be a breaking change. we are definitely open to proposals to make these scenarios better.
johnnyreilly commentedon Jun 2, 2016
Is the tuple the 🐓 or the 🐣?
I'm hoping it's the 🐓. 😀
makoConstruct commentedon Nov 23, 2016
Would it be possible to have typescript think of
['str', 5]as[string,number] & (string|number)[]in these cases, instead of just one or the other?kitsonk commentedon Nov 23, 2016
That isn't the worst thing in the world... found one situation where it does produce surprising results:
makoConstruct commentedon Nov 23, 2016
Ohhh... I see. A tuple can't really have
pushorshiftmethods, because its type would effectively have to change every time they were called and no type system I'm aware of supports that, probably because it's more reasonable to just make tuples immutable and usesliceandconcatmethods instead (this could even be implemented without any reallocations in languages with strong move semantics)So a
[string,number] & (string|number)[]doesn't really make sense, conceptually, because there can't be a type that has all of the methods of both of the conjuncts. I suppose the most correct signature for ['bar', 12] would beInto<[string,number]> & Into<(string|number)[]>, it can be convertedIntoeither of the two types, but it hasn't decided which it is yet, it is certainly not both at the same time. I don't suppose this is relevant to typescript, though, sorry.