Repository navigation
Intra expression inference doesn't work within reverse mapped types #53018
Description
Bug Report
🔎 Search Terms
intra expression inference reverse mapped type
🕗 Version & Regression Information
- This is the behavior in every version I tried
⏯ Playground Link
Playground link with relevant code (object variant)
Playground link with relevant code (tuple variant)
💻 Code
declare function f<T>(
arg: {
[K in keyof T]: {
produce: (n: string) => T[K];
consume: (x: T[K]) => void;
};
}
): void;
// Works
f({
a: {
produce: () => "hello",
consume: (x) => x.toLowerCase(),
},
});
// Works
f({
a: {
produce: (n: string) => n,
consume: (x) => x.toLowerCase(),
},
});
// still doesn't work, even with "Improved Function Inference in Objects and Methods" aka intra-expression inference sites
f({
a: {
produce: (n) => n,
consume: (x) => x.toLowerCase(),
},
});
// still doesn't work, even with "Improved Function Inference in Objects and Methods" aka intra-expression inference sites
f({
a: {
produce: function () {
return "hello";
},
consume: (x) => x.toLowerCase(),
},
});
// still doesn't work, even with "Improved Function Inference in Objects and Methods" aka intra-expression inference sites
f({
a: {
produce() {
return "hello";
},
consume: (x) => x.toLowerCase(),
},
});🙁 Actual behavior
This kind of inference that was improved in #48538 doesn't work within a reverse mapped type.
🙂 Expected behavior
It should work in the same way as it does outside of the reverse mapped type. The code in the playground is almost literally taken from the 4.7 announcement blog post about this improvement (here). The only difference is that the produce/consume "pair" is within a property of reverse mapped type
Part of the problem is likely in that inferReverseMappedType calls inferTypes with separate inferences here:
https://github.dev/microsoft/TypeScript/blob/e2283e99b47942b863d016c65a3e430dca1549b9/src/compiler/checker.ts#L23938-L23939
and that is not linked anyhow to the current inference context and inferFromIntraExpressionSites tries to infer this kind of stuff from context.intraExpressionInferenceSites+context.inferences and the latter won't be present for the computed reverse mapped type inference:
https://github.dev/microsoft/TypeScript/blob/e2283e99b47942b863d016c65a3e430dca1549b9/src/compiler/checker.ts#L23756-L23763
However, I also noticed that inferReverseMappedType is not called at all in this case so those inferences for it aren't even gathered in the first place.
Activity
fatcerberus commentedon Feb 28, 2023
What is a “reverse mapped type”? I looked at the repro code but that just looks like a regular mapped type to me.
Andarist commentedon Feb 28, 2023
A reverse mapped type is a type parameter that has to be inferred from usage when the usage is a mapped type - at least that's my mental model around this, this definition doesn't have to be 100% precise. So, here - the
Tis the reversed mapped type (not the mapped type at the argument position). It's called like that because TS has "reverse" the mapped type at the argument position to construct "what thisTcould look like to satisfy this mapped type?". It's how it's called internally in the codebase - take a look at the inferReverseMappedType.Andarist commentedon Mar 1, 2023
So I guess this is related to the umbrella issue here as that is still open. I think though that this one here is more of a bug/unhandled case within the original fix than a new feature request. This umbrella issue stays open because there are still other situations for which the original fix didn't do much but this issue here is very similar to the ones fixed by that PR. I think that those other situations could be put into a "different kind of an expression" category - whereas here we deal with the same types of expressions, they are just contained in a mapped type. Inferring reverse mapped types is implemented sufficiently differently that this still manifests for them.
bautistaaa commentedon Apr 17, 2023
throwing in a bug recently discovered that appears to be related: playground
looks like an explicit type annotation is needed to keep inference. further, not using an arrow function appears to break it as well
cc @nickmccurdy
Andarist commentedon Apr 17, 2023
If you mean that it also doesn't work when you use a regular function expression (
async function (request: Request) {}) then it's because such a function is also context-sensitive (just like the unannotated arrow). A regular function expression always has an implicitthisparameter. That's also why it starts to work again when you annotate that parameter:async function (this: undefined, request: Request) {}Nick-Lucas commentedon Apr 22, 2023
Just building on the possibly related react-query issue, here's a minimal example of that:
https://www.typescriptlang.org/play?ts=4.9.5#code/CYUwxgNghgTiAEAzArgOzAFwJYHtVIB4BBAGngCEA+ACgCh55YBzALngG96H4w8BnZAFsQbalDZEAlPAC8lCgG4uDAA4wcwZGBHxq0ufCJLuPKBAgAjKGADWoi23L75ANxxZgXAL61JbNx5KtAD0wfAA6jgwNny0iNScJmoaWjp6svIARAAWIOY4mSTKPPxCaVDOjAB0ECCoTBjZRSZgZpbWdroWlYkmJah8OLU1OEzU3cU+XpJBofAAqqg2qDgA7qhxCcWt5la29j3FDLwDQyAjYxMmXs3cJwLCohUZ1bX1jbeq6praopU5eQgBSK0wUQA
For some reason showing up as unknown and not a typechecking error here, but the order does matter.