Repository navigation
Sub-optimal type parameter inference with strictFunctionTypes enabled #52111
Description
Activity
I think everything works if you slightly tweak my suggestion here to instead introduce an overload with three type parameters to handle cases where the input and output types share a supertype but aren't otherwise related:
declare function assertNode<T extends Node, U extends T>(node: T | undefined, test: (node: T) => node is U): asserts node is U; declare function assertNode<T extends N, U extends N, N extends Node>(node: T | undefined, test: (node: N) => node is U): asserts node is U; declare function cast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut; declare function cast<TOut extends T, TIn extends T, T>(value: TIn | undefined, test: (value: T) => value is TOut): TOut;
Here's a modified example where everything appears to work as expected.
This is the workaround I mentioned that I tried and reverted; if you look at that playground, you'll see the assertion function has an error on its return because it doesn't like the relationship of the input and output.
The change in cast also has the unfortunate side effect of letting you cast anything to anything else, since it'll happily choose unknown/any as a supertype:
declare function isNumber(x: unknown): x is number; declare const nodeArray: NodeArray<Node>; const x = cast(nodeArray, isNumber); // ^? number
Which is "fine" because it'll just throw, but does mean you can't know if you're attempting to cast to a totally unrelated type.
First a simplified example:
interface A { a: string } interface B extends A { b: string } interface C extends A { c: string } declare function cast<T, U extends T>(x: T, test: (x: T) => x is U): U; declare function isC(x: A): x is C; function f1(a: A, b: B) { const x1 = cast(a, isC); // cast<A, C> const x2 = cast(b, isC); // cast<A, C> }
Currently, the
cast(b, isC)call fails because we inferBfor bothTandU. In cases where we have inferences from both co- and contra-variant positions, we prefer the co-variant inference when it is a subtype of the contra-variant inference (i.e. when it is more specific). In the example we have two inference candidates,AandB, forT, and we chooseBbecause it is more specific thanA. However, we also have a requirement that our inference forUmust extend our inference forT, but we fail to consider that as we're choosing. In the example,CextendsA, but notB, so reallyAis the only meaningful inference forT.I'm going to put up a PR that adds this extra consideration to type inference.
Reacted by Jake Bailey- addedFix AvailableA PR has been opened for this issueA PR has been opened for this issue
on Jan 6, 2023 - locked as resolved and limited conversation to collaborators
on Oct 22, 2025
These are cases I've extracted from #49929; they are cases where if you don't specify the type parameters yourself, the inference isn't good.
This is potentially a full duplicate of #49924, however, the fix suggested in that issue does not work properly in our codebase. To test these, it may be easiest to just clone #49929 with the explicit type parameters removed and see what works and what doesn't.
Output
Compiler Options
{ "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictPropertyInitialization": true, "strictBindCallApply": true, "noImplicitThis": true, "noImplicitReturns": true, "alwaysStrict": true, "esModuleInterop": true, "declaration": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "target": "ES2017", "jsx": "react", "module": "ESNext", "moduleResolution": "node" } }Playground Link: Provided