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
Actual behavior:
Error:(3, 28) TS2345:Argument of type 'Processor[]' is not assignable to parameter of type 'Processor | ReadonlyArray<Processor>'.
Type 'Processor[]' is not assignable to type 'ReadonlyArray<Processor>'.
Types of property 'indexOf' are incompatible.
Type '(searchElement: Processor, fromIndex?: number | undefined) => number' is not assignable to type '(searchElement: Processor, fromIndex?: number | undefined) => number'.
Types of parameters 'searchElement' and 'searchElement' are incompatible.
Types of parameters 'subj' and 'subj' are incompatible.
Type 'T1' is not assignable to type 'T1'. Two different types with this name exist, but they are unrelated.
Type 'T' is not assignable to type 'T1'.
Type 'object' is not assignable to type 'T1'.
First, imagine that Processor<T> is written as just
typeProcessor<Textendsobject>=(subj: T)=>T;
This function is invariant for T because it uses T in both input and output positions. Given a types A and B where one is a subtype of the other, neither Processor<A> nor Processor<B> is assignable to the other, and indeed we error if you try.
Now, changing Processor<T> to the original declaration
This shouldn't really change matters. The function is still invariant for T. However, the type checker has limited capabilities when it comes to checking assignability of function types with generic signatures that differ only in their constraints, so we consider Processor<A> and Processor<B> assignable to each other when really they shouldn't be. But, for some reason, we do end up realizing that Array<Processor<A>> is not assignable to ReadonlyArray<Processor<B>> and that Array<Processor<B>> is not assignable to ReadonlyArray<Processor<A>>. And that's what happens in the original example (where T and T1 in doStuff are like A and B). I'm not 100% clear on how we get there, but it definitely should be an error. The issue here is actually that other cases should also be errors.
My conclusion is that the error in the original example is correct, and that the "fix" in #20455 isn't actually a fix, but rather a change that causes us to not error when we still should.
I think the correct course of action is to reverse #20455 and leave things the way they were.
I was right that it is complicated. But I was wrong about it not being a bug. It is.
The original Processor<T> is naturally contra-variant, not invariant as I was arguing above. Intuitively, it is a function that always returns the same type as you pass it, but what you can pass it is constrained, and the constraint acts in a contra-variant manner (i.e. a Processor<A> can safely be treated as a Processor<B>, but not vice versa).
The actual issue is a combination of two changes: #15104 (covariant checking for callback parameters) and #17806 (arguments to concat should be ReadonlyArray<T>). Because of #17806 we end up in a structural comparison of Array and ReadonlyArray when checking a call to concat. During this structural comparison we treat the searchElement parameter of the indexOf method as a callback parameter and only perform a co-variant comparison, and this comparison fails.
It is actually correct that there is an error. What's wrong is the way indexOf is declared to take T in an input (contra-variant) position, which technically makes Array<T> and ReadonlyArray<T> invariant. That's usually masked by the fact that we always compare methods bivariantly, but in this scenario we don't because of #15104. We've previously discussed changing indexOf (and the others like it) to be properly co-variant, but we'd need the ability to specify super constraints or simply resort to type any, and we didn't want to go there.
Activity
amir-arad commentedon Nov 26, 2017
currently blocks wix-incubator/wix-react-tools#188
mhegazy commentedon Nov 27, 2017
Seems to have been caused by #17806. a possible solution here is to add an overload for
(T | T[])[]or just make it(T | T[] | ReadonlyArray<T>)[]amir-arad commentedon Nov 28, 2017
Mohamed Hegazy (@mhegazy) thanks for the workaround
ahejlsberg commentedon Feb 2, 2018
This actually isn't a bug. But it is complicated.
First, imagine that
Processor<T>is written as justThis function is invariant for
Tbecause it usesTin both input and output positions. Given a typesAandBwhere one is a subtype of the other, neitherProcessor<A>norProcessor<B>is assignable to the other, and indeed we error if you try.Now, changing
Processor<T>to the original declarationThis shouldn't really change matters. The function is still invariant for
T. However, the type checker has limited capabilities when it comes to checking assignability of function types with generic signatures that differ only in their constraints, so we considerProcessor<A>andProcessor<B>assignable to each other when really they shouldn't be. But, for some reason, we do end up realizing thatArray<Processor<A>>is not assignable toReadonlyArray<Processor<B>>and thatArray<Processor<B>>is not assignable toReadonlyArray<Processor<A>>. And that's what happens in the original example (whereTandT1indoStuffare likeAandB). I'm not 100% clear on how we get there, but it definitely should be an error. The issue here is actually that other cases should also be errors.My conclusion is that the error in the original example is correct, and that the "fix" in #20455 isn't actually a fix, but rather a change that causes us to not error when we still should.
I think the correct course of action is to reverse #20455 and leave things the way they were.
ahejlsberg commentedon Feb 2, 2018
I was right that it is complicated. But I was wrong about it not being a bug. It is.
The original
Processor<T>is naturally contra-variant, not invariant as I was arguing above. Intuitively, it is a function that always returns the same type as you pass it, but what you can pass it is constrained, and the constraint acts in a contra-variant manner (i.e. aProcessor<A>can safely be treated as aProcessor<B>, but not vice versa).The actual issue is a combination of two changes: #15104 (covariant checking for callback parameters) and #17806 (arguments to
concatshould beReadonlyArray<T>). Because of #17806 we end up in a structural comparison ofArrayandReadonlyArraywhen checking a call toconcat. During this structural comparison we treat thesearchElementparameter of theindexOfmethod as a callback parameter and only perform a co-variant comparison, and this comparison fails.It is actually correct that there is an error. What's wrong is the way
indexOfis declared to takeTin an input (contra-variant) position, which technically makesArray<T>andReadonlyArray<T>invariant. That's usually masked by the fact that we always compare methods bivariantly, but in this scenario we don't because of #15104. We've previously discussed changingindexOf(and the others like it) to be properly co-variant, but we'd need the ability to specifysuperconstraints or simply resort to typeany, and we didn't want to go there.