TypeScript Version: 3.2.0-dev.20181026
Search Terms:
default argument
default parameters
default value
default generic value
not assignable to
Code
type Z = "a" | "b"
// works fine
const x: Z = 'a'
function y<T extends Z>(a: T) { }
// works fine
y('a')
// When you add default value the autocomplete is working fine, suggesting 'a' and 'b' but type error is present.
// Error: Type '"a"' is not assignable to type 'T'.
function y1<T extends Z>(a: T = 'a') { }
// This also working
function y2(a: "a" | "b" = 'a') { }
Expected behavior:
'a' should be assignable to T
Actual behavior:
Type Error
Playground Link:
http://www.typescriptlang.org/play/#src=type%20Z%20%3D%20%22a%22%20%7C%20%22b%22%0D%0A%0D%0Aconst%20x%3A%20Z%20%3D%20'a'%0D%0A%0D%0Afunction%20y%3CT%20extends%20Z%3E(a%3A%20T)%20%7B%20%7D%0D%0A%0D%0Ay('a')%20%20%0D%0A%0D%0Afunction%20y1%3CT%20extends%20Z%3E(a%3A%20T%20%3D%20'a')%20%7B%20%7D%0D%0A
Related Issues:
Nope.
I think this is related, but not identical to, this issue here #21521.
I would wait on the opinion from someone in the team...but my initial reading of the problem is that the choice to use default arguments is purely syntactic to keep things nice.
Just from the syntax y1() we know to drop in the default argument, and this is completely independent from the instantiation of the type parameters. However you can then run into unsound things like:
y1<"b">(); // argument will be 'a', says it has type 'b'.
To make your suggestion sound would require checking the default argument per instantiation, which is extra complexity that the TS team might not be in favour of. Instead, the current solution is to just pick a default argument that will always be consistent with the type parameter, even if this is limiting.
function y1<T extends Z>(a: T = 'a') { }
is correctly an error - you can write
y1<"a" & { __brand: void }>();
to which "a" would not be assignable (it'd be missing the __brand property). Sadly, extends of a union is not quite a "one of" constraint.
EDIT: apologies, I checked this with strictNullChecks turned off
it's a wider problem not only for unions. in all cases these examples should report error on call site
/* The a argument is optional*/
function y2<T extends { name: string }>(a?: T) { return a.name }
y2()
y2<{ name: string, age: number }>()
I wanted to get something similar to the code below running.
Faced the same question.
function f<T extends boolean>(some: T = false) {}
@weswigham @jack-williams I understand that covering these cases positively might result in additional complexity, so the question is — does it make sense to include/implement at some point in the future or maybe close this ticket and say "not planning to support"? Just for clarity 😄
false isn't assignable to T - T can be jnstantiated as either true or a subtype of false - so the assignment is not allowed. You must produce a T to assign to a T.
@weswigham does it mean that it's practically impossible to just provide a value here, the only way would be using another generic function which can produce T?
UPD: after some thinking, seem my question does not make sense — this is impossible in the situation above to create value of T from thin air.
I tried coming up with a workaround for @DeTeam's example, but then I hit #29400.
- function f<T extends boolean>(some: T = false) {}
+ function f<T extends boolean>(some: T | false = false) {}
false isn't assignable to T - T can be jnstantiated as either true or a subtype of false - so the assignment is not allowed. You must produce a T to assign to a T.
@weswigham Is there a compelling use case for allowing a subtype of false to be possible?
As of now, I'd rather see the following be supported:
function f<T extends boolean>(a: T = false, b: T extends true ? 1 : 0) {}
But I might just be in the weeds here. 🤷♂️
edit: Actually, the false as T workaround is simple enough. ;)
@aleclarson
Is there a compelling use case for allowing a subtype of false to be possible?
Yes, in the future you might have name subtyping similar to flow.
In Flow:
opaque type invalid: false = false;
// invalid is assignable to false, but false is not assignable to invalid
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.
I just found a way to avoid this issue using function overloads: https://twitter.com/kripod97/status/1248551695065456641
JavaScript vs TypeScript implementation of the same function. The latter infers its return type when a second parameter is given.
Applying
keyofon the resulting object returns the union of concrete numeric values insteps. This makes code autocomplete work wonderfully.
Please refer to the implementation above and usage for further details.
Most helpful comment
I tried coming up with a workaround for @DeTeam's example, but then I hit #29400.