Issue
In this first example, the compiler does not complain when creating a new object of class Foo
.
class Foo<Options extends number[] = (1 | 2 | 3)[] > {
constructor(
public options: Options,
public option: Options[number]
) {}
}
new Foo([4, 5], 1);
If I now add another type parameter before Options
and then pass a type for that preceding type parameter, the compiler correctly complains (screenshot of the error).
class Foo<Unused, Options extends number[] = (1 | 2 | 3)[] > {
constructor(
public options: Options,
public option: Options[number]
) {}
}
new Foo<any>([4, 5], 1);
I would expect the first example to behave the same as the second. In the second example, the compiler correctly complains that the first passed constructor argument is not assignable to the default Options
type (1 | 2 | 3)[]
. Why does merely passing any
as the Unused
type parameter suddenly yield the desired behavior? Why does it not work as expected in the first example?
Solution
There are two ways to call/construct a generic function/class. You can either explicitly specify type arguments like foo<X>(x)
or new Foo<X>(x)
or bar<X, Y>(z)
, using the <
⋯>
syntax; or you can leave off the type arguments like foo(x)
or new Foo(x)
or bar(z)
, without the <
⋯>
.
If you explicitly specify any type arguments, then those will be used. If some of the type parameters have default type arguments and you leave out type arguments, then the defaults will be used.
On the other hand, if you don't specify any type arguments, then all the type arguments will be inferred. The defaults will not be used in this case (unless inference fails completely).
You might hope to be able to "mix and match" this functionality, specifying some type arguments, while having some other ones inferred, or get defaults instead of inference, but this is not currently possible. There's a longstanding open GitHub at microsoft/TypeScript#26242 asking for such abilities, but it's not part of the language now.
Given that, we can explain the behavior you're seeing. First:
class Foo<Options extends number[] = (1 | 2 | 3)[] > {
constructor(
public options: Options,
public option: Options[number]
) {}
}
new Foo([4, 5], 1);
You have not specified a generic type argument for Options
. There is no <
⋯>
. So you get inference. Options
is inferred from the argument [4, 5]
for options
, and it is inferred as number[]
. (It is out of scope for this question to go into why it is number[]
and not (4 | 5)[]
or [4, 5]
or anything else.)
Next:
class Foo<Unused, Options extends number[] = (1 | 2 | 3)[] > {
constructor(
public options: Options,
public option: Options[number]
) {}
}
new Foo<any>([4, 5], 1);
You have specified any
for the Unused
type parameter, so no inference happens. The Options
type argument takes its default type of (1 | 2 | 3)[]
, and thus you get an error because [4, 5]
is not assignable to (1 | 2 | 3)[]
.
Whether or not any of this counts as "desired behavior" depends on the use case, but it seems like according to some of the comments it's probably not actually desirable, and the inference-vs-default issue was masking that.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.