Repository navigation
Improve inference in the presence of context-sensitive expressionsΒ #47599
Description
Suggestion
π Search Terms
contextually context-sensitive generic inference unused parameter method object literal
β Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
β Suggestion
When generic inference is gathering candidates, context-sensitive expressions should still be considered if it's possible to do so without observing a type parameter from the call.
π Motivating Example
declare function callIt<T>(obj: {
produce: (n: number) => T,
consume: (x: T) => void
}): void;
// Works
callIt({
produce: () => 0,
consume: n => n.toFixed()
});
// Fails for no obvious reason
callIt({
produce: _a => 0,
consume: n => n.toFixed()
});
// Fails for no obvious reason
callIt({
produce() {
return 0;
},
consume: n => n.toFixed()
});π» Use Cases
Background: expressions can be "context-sensitive" for the purposes of inference -- this is a syntactic property of an expression that tells us whether or not an expression's type could be dependent on the inference of its contextual type. For _a => 0, this is true because the return expression might depend on _a (including in indirect ways). For produce() {, it's context-sensitive because (unlike an arrow function) it takes its parent object type for this, so could depend on the type parameter via a reference of this.
When it turns out that the type of the expression is not actually dependent on the inference of the type parameter (which would represent a true circularity, thus more understandably non-working), then this just looks busted for no obvious reason. This is a continuous source of surprise and annoyance. (TODO: link user reports)
Discussion
There are a few implementation strategies we could try, with varying trade-offs
- Proceed to do structural inference on context-sensitive expressions but mark the type parameters symbols as "off limits" with logic to back out. This is likely impractically invasive
- Perform a more robust check to determine "true" context-sensitivity -- for example,
produce: _a => 0should not be contextually sensitive because the relational target(n: number) => Tdoes not useTin a covariant position. There are multiple ways we could do this - Add an optional intermediate pass when inference collected no candidates when ignoring context-sensitive expressions where we go and collect from them anyway. This pass could safely fix parameters since they'd be going to
unknown. This entire bullet point might be wrong. - As a stopgap, consider function expressions to not be context-sensitive if all of their return expressions can be trivially seen to not depend on earlier lines of code (e.g. are literals, literals of literals,
null, orundefined). This might fix a surprisingly large class of reports - As a stopgap, consider an object literal method to not be context-sensitive if it doesn't reference
this. I believe we already have code for this and it would fix another large class of surprises.
Activity
[-]Improve inference in the presence of contextually-sensitive expressions[/-][+]Improve inference in the presence of context-sensitive expressions[/+]ahejlsberg commentedon Apr 3, 2022
Fix now available in #48538.
RyanCavanaugh commentedon Jun 21, 2022
Not all these cases work yet, so reopening
ShayDavidson commentedon Jul 20, 2022
Might be related to my issue: #49951, which still happens in 4.7/4.8
15 remaining items
geleto commentedon Aug 6, 2025
I'm not suggesting that
consttype parameters currently solve context-sensitive inference issues, but rather that we could extend the semantics to act as a developer signal to bypass conservative inference, similar to howas constworks at the expression level.Currently,
consttype parameters ensure that literal types are inferred without widening, which aligns with the goal of precise inference.Detecting "true" context-sensitivity remains complex, but if some day a perfect solution is implemented, the proposed
consttype parameters semantics would still remain valuable. It would let developers explicitly signal aggressive inference in cases where they know potential context-sensitivity wonβt lead to circular dependencies or other issues.martaver commentedon Aug 6, 2025
Andarist commentedon Aug 6, 2025
The
as constfeels very much like an accidental bug to me. I don't see a reason why it should break this inference: TS playgroundgeleto commentedon Aug 6, 2025
It doesn't break it - it fixes it. And my proposal is that '<const T' shall fix it too.
Playground
Andarist commentedon Aug 6, 2025
How
as constis fixing the above? Withas constit doesn't work when without it it does:geleto commentedon Aug 6, 2025
Probably with
as constTypeScript is looking at the object in isolation, with no external context. So it does not look insidetestand does not know thatargis of type'foo'. This is one example where context-sensitive inference is actually needed and shall not be disabled. This aligns with the nuanced role of const T in my proposal, it shall be used where it makes sense to disable context-sensitive inference.In my example
as constfixes the issue. In many of the related bugs - context-sensitive inference kicks in when it is not needed (for instance when inferring the parameter type from an object with a function property) and TypeScript throws the towel widening the type to constraint.geleto commentedon Aug 7, 2025
I did an AI analysis (so take it with a grain of salt) of all related issues mentioned in this thread - whether context-sensitive inference kicks in even though the functionβs output does not truly depend on the context, or not.
Note that we are talking about a proposed modification of how const generic parameters work, and NOT about the current behavior!
The results are:
Category 1 β context-sensitive inference unnecessary (fixable by
<const T>) β 36 issues#43371, #47547, #47577, #47599, #48279, #48466, #49618, #49951, #50258, #51831, #52114, #52269, #52786, #53033, #53695, #53776, #53924, #53954, #54438, #55124, #55489, #56067, #56297, #56855, #57021, #57185, #57572, #57921, #58630, #58651, #60058, #60071, #60366, #60382, #60720, #61361
Category 2 β context-sensitive inference required (not fixable by
<const T>) β 7 issues#51578, #53018, #55617, #59309, #59548, #60848, #60929
Category 3 β mixed / order-dependent scenarios β 3 issues
#56459, #57591, #60986
The conclusion is:
"Overall, many more examples are in Category 1 (fixable by
<const T>) than in Category 2, which suggests that introducing a const modifier on type parameters could resolve a majority of these surprising inference behaviors"See the whole analysis here: https://chatgpt.com/share/68947c8d-f19c-800e-b808-b7d325d353fe
Maybe I shall open a Feature request, as this would be useful for other purposes, not just as a workaround for this bug
Andarist commentedon Aug 7, 2025
I still believe you have a wrong perception of what a
constcontext means and that you are too focused on trying to justify semantic changes to it based on a single evidence of it fixing something. This needs a more systemic improvements, I don't thinkconstcontext was ever meant to change anything in regards to how inference treats context-sensitive function within that context. I'm not a TS team member so take my words with a grain of salt too.refactor(core): adapt resource params to reflect runtime types.
fix(core): reflect absence of param in the loader/stream typings
refactor(core): adapt resource params to reflect runtime types.
refactor(core): adapt resource params to reflect runtime types.
refactor(core): adapt resource params to reflect runtime types.
unknown#63545 as a duplicate of this issuevictorafael26 commentedon Sep 11, 2026
Adding a real-world data point that fits Angel Popov (@geleto)'s "Category 1" analysis above (fixable by
<const T>) β this is a very common shape in the wild: a generic helper that takes an object literal with a "producer" callback, where the callback is written inline without annotation.Concretely: a
React.lazy-style loader helper with apickadapter for named exports:Playground
This is essentially #31146's shape, so I know it's cross-linked already, but wanted to note it also lands squarely in Category 1 here: the callback's output (
{ default: T }) doesn't actually depend on the context being inferred (T), so<const T>(or any mechanism that lets context-sensitive-but-actually-independent callbacks contribute inference candidates) would fix this specific pattern too. This is a common enough helper shape (adapter/mapper callback passed alongside a factory) that I'd expect it affects a fair number of codebases using similar "lazy + pick" utilities, even if each individually files it as a one-off "generic inference broken" issue rather than connecting it to this thread.