Commonly Used Terminology
Contextual type: This refers to the type that a code position must have based on the surrounding type it is assigned to.
Context-sensitive function: A function expression with untyped parameters whose types are derived from the contextual type.
Illustrative Example
type Fn = (s: string) => number
const fn : Fn = s => s.length
// The above line is an example of a context-sensitive function
// The contextual type of `s` is `string`, obtained from the definition of `Fn`
Understanding Type Inference Mechanism
The TypeScript compiler does type inference in two phases:
First, it infers all non-context-sensitive functions.
In the second phase, the results of inference for type parameters (such as T
) are used to infer context-sensitive functions.
If there are no inference candidates from phase 1 available for use in phase 2, you might encounter a type issue - like when T
ends up being inferred as unknown
.
Application to Practical Scenarios
Scenario 1:
myFn({
a: () => ({ n: 0 }),
b: o => { o.n }
})
The scenario works fine because only b
is context-sensitive and a
is not, allowing proper inference of type T
in this case.
Scenario 2:
myFn({
a: i => ({ n: 0 }), // Parameter i is utilized
b: o => { o.n }, // Error at o: Object is of type 'unknown'.ts(2571)
})
In this case, since both a
and b
are context-sensitive, they are analyzed independently in phase 2, resulting in issues related to parameter type in b
.
This situation leads to the type of T
becoming fixed at unknown
.
Scenario 3:
const myFn = <T,>(p: {
a: (n: number) => T,
b: <U extends T /* EXTRA U generic */>(o: U) => void,
}) => {
// ...
}
This workaround introduces a new type parameter U
to produce separate inferences for U
and T
, avoiding potential issues encountered in earlier scenarios.
Alternate Suggestion
To handle complex type inference edge cases effectively, ensure that at least one function with type parameters is not context-sensitive by explicitly typing the function parameter.
For instance, explicitly typing n: number
within a
can resolve certain type inference challenges:
myFn({
a: (n: number) => ({ n: 0 }),
b: o => { o.n },
})
Access Playground Code Here