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
Generic wrapper interface no longer assignable #31804
Expected behavior:
No errors, as it worked in pre 3.5.0
Actual behavior:
Property 'foo' in type 'B' is not assignable to the same property in base type 'A'.
Type 'Wrapper' is not assignable to type 'Wrapper'.
Property 'bar' is missing in type 'AWrapped' but required in type 'BWrapped'
Why? What was this used for?
Our project uses a setup like this for strongly typed events; we define the events in interfaces, and then classes that emit the events store the generic event emitter instances on them. (The actual implementation is a lot more complex, this is a stripped down version)
Previously, it worked to simply override the property's type, but now that doesn't work anymore, and I can't find a workaround. I think this is might prevent our team from updating because of how much this breaks, at least for a time... The amount that has to be rewritten to undo the dependency on this functionality is not negligible.
The T in Wrapper<T> is now being measured as invariant. Parameters are contravariant positions, but there are concrete instances where two types do not contravary, but the arguments of their methods (which is what we're actually looking at here) do. Unfortunately, typically the arguments of the methods contravary so long as the containing types covary (meaning variance inversion brought on by the infer type within the Parameters helper is lost). The comparison we trip up on is Parameters<Extract<?[any], AnyFunction>> to Parameters<Extract<?[any], AnyFunction>> (the any's show up due to erasure during signature comparison, the ? marks are two different but known related types). Parameters is actually measured as bivariant, but then we simplify Extract<?[any], AnyFunction> to a substitution type, neglect to unwrap it (there's the bug!), and consequently report the types as not assignable, as substitutions on their own have no assignability rules (they are meant to be substituted).
As a workaround (until we take a fix), if you specify Params as:
the use of Required will bypass variance-based typechecking (as the Required transform is none of co- nor contra- nor in- nor bi- variant) on the containing types, allowing the assignment to succeed when supplied with correct concrete instances, as is the case here.
typeAnyFunction=(...args: any[])=>any;typeParams<T>=Parameters<Extract<Required<T>,AnyFunction>>;interfaceCaller<T>{call<KextendskeyofT>(event: K, ...args: Params<T[K]>): void;}interfaceACallable{foo(thing1: boolean,thing2: number): void;bar(): void;}classA{caller: Caller<ACallable>;}newA().caller.call("foo",true,1);// errors, [boolean, number] is not assignable to nevernewA().caller.call("bar");// errors, [] is not assignable to never
It does make the inheritance work, but then the thing we're trying to inherit and extend doesn't work anymore, so... ¯\_(ツ)_/¯
It seems like Required<any function type> just always eats the function type, removing any callable signatures. As a result the arguments can no longer be extracted from it.
I did find an actual workaround tho, and that's using Required on the extracted parameters instead:
Edit... Required isn't good enough because it makes optional parameters required, Partial isn't good enough either for the same reason (makes all required params optional).
However, I found the One True Workaround™, and that's just doing the exact same thing that Partial and Required do, but making no changes. It's time to introduce the new type LiterallyJustTheSameThing:
Activity
weswigham commentedon Jun 26, 2019
The
TinWrapper<T>is now being measured as invariant. Parameters are contravariant positions, but there are concrete instances where two types do not contravary, but the arguments of their methods (which is what we're actually looking at here) do. Unfortunately, typically the arguments of the methods contravary so long as the containing types covary (meaning variance inversion brought on by theinfertype within theParametershelper is lost). The comparison we trip up on isParameters<Extract<?[any], AnyFunction>>toParameters<Extract<?[any], AnyFunction>>(theany's show up due to erasure during signature comparison, the?marks are two different but known related types).Parametersis actually measured as bivariant, but then we simplifyExtract<?[any], AnyFunction>to a substitution type, neglect to unwrap it (there's the bug!), and consequently report the types as not assignable, as substitutions on their own have no assignability rules (they are meant to be substituted).As a workaround (until we take a fix), if you specify
Paramsas:the use of
Requiredwill bypass variance-based typechecking (as theRequiredtransform is none of co- nor contra- nor in- nor bi- variant) on the containing types, allowing the assignment to succeed when supplied with correct concrete instances, as is the case here.ChiriVulpes commentedon Jun 29, 2019
It seems like defining Params as you suggest doesn't actually work for this purpose, but I'm not sure why.
Simplified example (got rid of the inheritance stuff from B temporarily, better names)
Using your suggested change with
Required<T>:It does make the inheritance work, but then the thing we're trying to inherit and extend doesn't work anymore, so... ¯\_(ツ)_/¯
ChiriVulpes commentedon Jul 2, 2019
It seems like
Required<any function type>just always eats the function type, removing any callable signatures. As a result the arguments can no longer be extracted from it.I did find an actual workaround tho, and
that's using Required on the extracted parameters instead:Edit...
Requiredisn't good enough because it makes optional parameters required,Partialisn't good enough either for the same reason (makes all required params optional).However, I found the One True Workaround™, and that's just doing the exact same thing that
PartialandRequireddo, but making no changes. It's time to introduce the new typeLiterallyJustTheSameThing:weswigham commentedon Jul 2, 2019
Hopefully you won't need a workaround for long, since #32116 fixes the root cause of the problem.