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
TS 3.4: Error when passing dynamically imported generic type as a function argument #30712
// ===== page.tsx ======import*asReactfrom'react'exportinterfacePageProps{title: string}exportclassPageextendsReact.Component<PageProps>{render(){return<div>{this.props.title}</div>}}// ===== index.tsx =====import*asReactfrom'react'import{PageProps,Page}from'./page'exportfunctionmyFunction<TProps>(loader: ()=>Promise<React.ComponentType<TProps>>){}// No errormyFunction(()=>Promise.resolve(Page))// No errorconstloader: ()=>Promise<React.ComponentType<PageProps>>=()=>import('./page').then(m=>m.Page)// ErrormyFunction(()=>import('./page').then(m=>m.Page))
Expected behavior:
No compile error. This was the behavior in TS 3.3 and earlier.
Actual behavior:
There is an error after upgrading to TS 3.4:
index.tsx:14:18 - error TS2322: Type 'Promise<typeof Page | ComponentClass<never, any> | FunctionComponen
t<never>>' is not assignable to type 'Promise<ComponentType<PageProps>>'.
Type 'typeof Page | ComponentClass<never, any> | FunctionComponent<never>' is not assignable to type 'C
omponentType<PageProps>'.
Type 'ComponentClass<never, any>' is not assignable to type 'ComponentType<PageProps>'.
Type 'ComponentClass<never, any>' is not assignable to type 'ComponentClass<PageProps, any>'.
Type 'PageProps' is not assignable to type 'never'.
14 myFunction(() => import('./page').then(m => m.Page))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
index.tsx:4:44
4 export function myFunction<TProps>(loader: () => Promise<React.ComponentType<TProps>>) {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The expected type comes from the return type of this signature.
Are there any suggested workarounds for the above in the meantime? I would like to use the incremental build features from 3.4, but hitting this issue with some dynamic imports
The type we want is Promise<typeof Page>. But TResult2 is not inferred from the second argument — it's not given — so return type inference looks to the contextual type and gets React.ComponentType from myFunction.
This is arguably correct since then doesn't say what to do in case of failure, so a weird type like ComponentClass<never> might be what results. But it's not desired.
To get the desired type we could break then into two overloads:
Or we could modify return type inference to only apply to type parameters that have inferences from some argument, not just the return type. I think this would break some existing uses but they might be good breaks.
This one is fake — null as never disappears nicely in the type system — but a real app should probably provide a fallback page in case the import fails.
Reacted by styu, acapusteanindrivo, Brandon James, Jakub (Kuba) Pawlowski, MinyoungNa1997, Rodrigo José Borba Fernandes, Kevin Kreps, Guilherme Paiva and Oskar Filipowicz
I bisected the regression down to 1f32139 in #29847, which infers to unions containing multiple naked type variables. That improvement proceeds to uncover this bug.
Nathan Shively-Sanders (@sandersn) Indeed, but the change in #29847 is entirely reasonable and fixes another issue related to promises. The real question here is where it goes wrong when we're making inferences through the generic return type of Promise.then.
Yep, no question #29847 is an improvement. This bug is just exposed by it, I think.
Here's what happens in a bit more detail:
when inferring type arguments for pp.then(m => m), we get a contextual type during inference for the outer myFunction(pp.then(m => m)) call resolution. We instantiate this type with the special inference flag NoDefault, which produces silentNever when there are no inferences (although it is documented to produce 'unknownType', which doesn't seem right):
promiseType and promiseTypeStrictNull: Fixes a bunch of silentNevers that were incorrectly baselined.
inferFromGenericFunctionReturnTypes1 and 2: Breaks inference from generic function return types in many cases.
I think this means that the bug was technically caught two years ago in #13487, when we tweaked the definition of Promise and incorrectly accepted baselines. We just didn't notice it among the 1500 lines of churn for promiseType.ts.
Anders Hejlsberg (@ahejlsberg) I'm not sure if the correct fix is to change NoDefault to return silentNeverType less often or to change inference downstream to skip silentNever in more cases. Opinions? I think the second is probably a smaller change, so I'll look at that first.
I tried returning nonInferrableType since I believe I want propagation of non-inferrability, but it didn't actually propagate through anonymous type instantiation. I added that and then I get a different error:
It looks like I've prevented any inference from happening, even the good one from the first parameter of then.
This also breaks inferFromGenericFunctionReturnTypes1,2,3 tests, unsurprisingly.
Anders Hejlsberg (@ahejlsberg) Do you think that switching NoDefault to return nonInferrableType is the right idea? I feel like I've just made a mistake in the way I'm propagating it in instantiation.
could you please consider picking up this issue once again?
Reacted by Nicola, Jakub (Kuba) Pawlowski, importantaffairs, Christopher Toth, Vedant Rathore, Elytum, BlooApple, Omar López, Sebastian J., Matt and 9 more
Activity
styu commentedon Apr 30, 2019
Are there any suggested workarounds for the above in the meantime? I would like to use the incremental build features from 3.4, but hitting this issue with some dynamic imports
sandersn commentedon May 20, 2019
Inference for
thentries to infer bothTResult1andTResult2from the below signature.The type we want is
Promise<typeof Page>. ButTResult2is not inferred from the second argument — it's not given — so return type inference looks to the contextual type and gets React.ComponentType frommyFunction.This is arguably correct since
thendoesn't say what to do in case of failure, so a weird type likeComponentClass<never>might be what results. But it's not desired.To get the desired type we could break
theninto two overloads:Or we could modify return type inference to only apply to type parameters that have inferences from some argument, not just the return type. I think this would break some existing uses but they might be good breaks.
Ron Buckton (@rbuckton) Anders Hejlsberg (@ahejlsberg) can you take a look to see whether you would rather
vs
vs
sandersn commentedon May 22, 2019
styu
Workarounds:
avoid context by extracting
import('./page').then(m => m.Page):This avoids the return type inference since there is no contextual type to infer from.
provide a real or fake
onrejected:This one is fake —
null as neverdisappears nicely in the type system — but a real app should probably provide a fallback page in case the import fails.sandersn commentedon May 22, 2019
Here's a much smaller repro that doesn't depend on React or Promise:
Edit: even shorter:
sandersn commentedon May 22, 2019
Anders Hejlsberg (@ahejlsberg) tracked this down a sentinel type, silentNever, possibly incorrectly escaping inference.
Anders Hejlsberg (@ahejlsberg) do you want to take over on this bug or do you want me to finish it up?
ahejlsberg commentedon May 22, 2019
Even smaller:
sandersn commentedon May 23, 2019
I bisected the regression down to 1f32139 in #29847, which infers to unions containing multiple naked type variables. That improvement proceeds to uncover this bug.
ahejlsberg commentedon May 23, 2019
Nathan Shively-Sanders (@sandersn) Indeed, but the change in #29847 is entirely reasonable and fixes another issue related to promises. The real question here is where it goes wrong when we're making inferences through the generic return type of
Promise.then.sandersn commentedon May 23, 2019
Yep, no question #29847 is an improvement. This bug is just exposed by it, I think.
Here's what happens in a bit more detail:
when inferring type arguments for
pp.then(m => m), we get a contextual type during inference for the outermyFunction(pp.then(m => m))call resolution. We instantiate this type with the special inference flagNoDefault, which produces silentNever when there are no inferences (although it is documented to produce 'unknownType', which doesn't seem right):After that, we are inferring from
(u: silentNever) => voidtoT1 | T2.Removing
NoDefaultfromgetMapperFromContextfixes this example. I'm running tests to see what fails and learn why it is set.sandersn commentedon May 23, 2019
There are two classes of test failures:
I think this means that the bug was technically caught two years ago in #13487, when we tweaked the definition of Promise and incorrectly accepted baselines. We just didn't notice it among the 1500 lines of churn for promiseType.ts.
sandersn commentedon May 23, 2019
Anders Hejlsberg (@ahejlsberg) I'm not sure if the correct fix is to change NoDefault to return silentNeverType less often or to change inference downstream to skip silentNever in more cases. Opinions? I think the second is probably a smaller change, so I'll look at that first.
sandersn commentedon May 23, 2019
I tried returning nonInferrableType since I believe I want propagation of non-inferrability, but it didn't actually propagate through anonymous type instantiation. I added that and then I get a different error:
It looks like I've prevented any inference from happening, even the good one from the first parameter of
then.This also breaks inferFromGenericFunctionReturnTypes1,2,3 tests, unsurprisingly.
Anders Hejlsberg (@ahejlsberg) Do you think that switching NoDefault to return nonInferrableType is the right idea? I feel like I've just made a mistake in the way I'm propagating it in instantiation.
sandersn commentedon May 24, 2019
Moving to 3.6 since we are out of time on 3.5, and it's not affecting a large number of people (and there's an easy workaround or two).
wilsoncook commentedon Oct 21, 2020
Any progress?
jantimon commentedon May 5, 2021
Nathan Shively-Sanders (@sandersn) those workarounds can't be used by library writers but have to be known by the consumers of the library..
it is affecting a large number of people - e.g. everyone who is using nextjs with dynamic imports: https://nextjs.org/docs/advanced-features/dynamic-import
could you please consider picking up this issue once again?
ctoth commentedon Jul 20, 2021
Was lead here experiencing this exact issue. Nothing really useful to say other than that.
ibanlopez commentedon Nov 6, 2023
Is still moving from 3.6 up to 5?