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
Overloads with newable signatures not chosen #24428
We keep seeing this bug as we are rolling out --strictFunctionTypes to Angular users. Other places where it occurs is:
// https://github.com/angular/angular/blob/master/packages/core/src/type.ts#L25exportinterfaceType<T>{new(...args: any[]): T;}// https://github.com/angular/angular/blob/master/packages/core/src/di/injector.ts#L71declarefunctionget<T>(token: Type<T>): T;// https://github.com/angular/angular/blob/master/packages/core/src/di/injector.ts#L76declarefunctionget<T>(token: any): any;classC{constructor(a: string){}}letf=get(C);f.boom;// should fail if f is of type C, not any
With --strictFnTypes f is of type 'any', while without the flag it is of type 'C'.
I don't 100% know why, but I think this is because of that crappy preliminary subtype pass that we do on overload resolution. It's sometimes helpful though.
If you switch to
exportinterfaceType<T>{new(...args: never[]): T;}
that should work as you expect though. Is that a reasonable workaround?
In the realm of workarounds, leave as it is, is an ok workaround, because you just get some more 'any's. Changing the angular core types would be harder, probably something else will error and 'never[]' is quite unorthodox, though I see the reasoning.
I am more interested in getting a proper fix on the roadmap in future releases.
I think this is because of that crappy preliminary subtype pass that we do on overload resolution. It's sometimes helpful though.
This is precisely the cause. new(x: string) => C isn't a subtype of new(...args: any[]) => T because any isn't a subtype of string (strict argument variance inverts the relationship for the parameter, and while any is assignable to string, it is not a subtype), while new(x: string) => C is easily a subtype of Function, structurally.
I think Daniel's suggestion is actually the appropriate fix to make your code strictFunctionTypes safe. And it emphasizes that while you are taking a thing with any number of constructor arguments, you can never safely actually provide any arguments for that constructor in the function's implementation (since nothing is assignable to never sans never itself) since you're not validating what those types are.
I see, so using never[] is the principled answer for --strict users and not just a workaround. In that case, I will file bugs with angular and material to fix this.
Would you be willing to add something like Constructable to the core libs? I am seeing this all over the place, because it is so core to mixins and DI, and basically every one has used 'any' instead of 'never'.
Finally, I still think there is a bug (maybe more minor that originally thought), because why removing the 'any' overload succeeds even with --strict on.
// https://github.com/angular/angular/blob/master/packages/core/src/type.ts#L25exportinterfaceType<T>{new(...args: any[]): T;}// https://github.com/angular/angular/blob/master/packages/core/src/di/injector.ts#L71declarefunctionget<T>(token: Type<T>): T;classC{constructor(a: string){}}letf=get(C);f.boom;// f is inferred as C and there is type error here.
Finally, I still think there is a bug (maybe more minor that originally thought), because why removing the 'any' overload succeeds even with --strict on.
Yeah, the call is still valid with any, we just prioritize overloads whose arguments are proper subtypes to arguments which are merely assignable during call resolution. any is flexible for assignability (where it's both a top type and a bottom type), but as far as subtyping goes (which again, matters in this case since calls which resolve using subtypes are preferred), only functions as an unknown-like top. In strictFunctionTypes we become very pedantic about variance when comparing signatures (without it we check either direction and if either direction is fine we say it's OK), and so using the correct bounds type (never or unknown/any) for a given position becomes very important for proper subtype relationships.
I don't think so? The original issue was phrased as a kind of question - a change was proposed to make the OP's code typecheck, and the reason why it is like it is was given.
Yes, I had a poor understanding of how overloads were chosen. The existence of 'subtyping' vs 'assignability' relation checks was news to me. I find it surprising, but not buggy. Thanks for explaining Wes!
Yeah, we know overload resolution is really complex with a lot of nuance that you don't usually need to know about. Some patterns are hard to type without them, but we recommend using simple union types (and maybe conditionals) where possible instead.
Activity
RyanCavanaugh commentedon May 29, 2018
Daniel Rosenwasser (@DanielRosenwasser) thoughts?
rkirov commentedon Sep 18, 2018
We keep seeing this bug as we are rolling out --strictFunctionTypes to Angular users. Other places where it occurs is:
With --strictFnTypes f is of type 'any', while without the flag it is of type 'C'.
DanielRosenwasser commentedon Sep 24, 2018
I don't 100% know why, but I think this is because of that crappy preliminary subtype pass that we do on overload resolution. It's sometimes helpful though.
If you switch to
that should work as you expect though. Is that a reasonable workaround?
rkirov commentedon Sep 24, 2018
In the realm of workarounds, leave as it is, is an ok workaround, because you just get some more 'any's. Changing the angular core types would be harder, probably something else will error and 'never[]' is quite unorthodox, though I see the reasoning.
I am more interested in getting a proper fix on the roadmap in future releases.
Also the 'constructor' pattern is used quite often for frameworks that want to accept any consturctor. Here it is in angular material - https://github.com/angular/material2/blob/master/src/lib/core/common-behaviors/constructor.ts.
weswigham commentedon Sep 24, 2018
This is precisely the cause.
new(x: string) => Cisn't a subtype ofnew(...args: any[]) => Tbecauseanyisn't a subtype ofstring(strict argument variance inverts the relationship for the parameter, and whileanyis assignable to string, it is not a subtype), whilenew(x: string) => Cis easily a subtype ofFunction, structurally.weswigham commentedon Sep 24, 2018
I think Daniel's suggestion is actually the appropriate fix to make your code
strictFunctionTypessafe. And it emphasizes that while you are taking a thing with any number of constructor arguments, you can never safely actually provide any arguments for that constructor in the function's implementation (since nothing is assignable toneversansneveritself) since you're not validating what those types are.rkirov commentedon Sep 24, 2018
I see, so using
never[]is the principled answer for --strict users and not just a workaround. In that case, I will file bugs with angular and material to fix this.Would you be willing to add something like
Constructableto the core libs? I am seeing this all over the place, because it is so core to mixins and DI, and basically every one has used 'any' instead of 'never'.References for mixins:
Finally, I still think there is a bug (maybe more minor that originally thought), because why removing the 'any' overload succeeds even with --strict on.
weswigham commentedon Sep 24, 2018
Yeah, the call is still valid with
any, we just prioritize overloads whose arguments are proper subtypes to arguments which are merely assignable during call resolution.anyis flexible for assignability (where it's both a top type and a bottom type), but as far as subtyping goes (which again, matters in this case since calls which resolve using subtypes are preferred), only functions as anunknown-like top. InstrictFunctionTypeswe become very pedantic about variance when comparing signatures (without it we check either direction and if either direction is fine we say it's OK), and so using the correct bounds type (neverorunknown/any) for a given position becomes very important for proper subtype relationships.4 remaining items
Goodwine commentedon Mar 1, 2019
Conditional types instead of overloads as a workaround for now? It seems to me that the overloads buggy on some cases like the one described here
[example]
I'm on the opinion that
Functionshould returnunknowninstead ofT:)but we could do something like...
T extends Newable<infer U> ? U : T extends () => infer U ? U : unknownor something like that.. maybe?RyanCavanaugh commentedon Mar 13, 2019
Wesley Wigham (@weswigham) is there any action item on our side for this?
weswigham commentedon Mar 13, 2019
I don't think so? The original issue was phrased as a kind of question - a change was proposed to make the OP's code typecheck, and the reason why it is like it is was given.
rkirov commentedon Mar 13, 2019
Yes, I had a poor understanding of how overloads were chosen. The existence of 'subtyping' vs 'assignability' relation checks was news to me. I find it surprising, but not buggy. Thanks for explaining Wes!
weswigham commentedon Mar 13, 2019
Yeah, we know overload resolution is really complex with a lot of nuance that you don't usually need to know about. Some patterns are hard to type without them, but we recommend using simple union types (and maybe conditionals) where possible instead.