-
Notifications
You must be signed in to change notification settings - Fork 13k
Description
Rationale
TypeScript currently narrows types when a function known at compile time (e.g., a throwError() declared with return type never) is invoked. However, when the thrower function is injected via a parameter - even if typed to return never - TS does not narrow, because it cannot assume that caller will always pass in a never function. This limitation forces developers into workarounds and undermines ergonomics for generic code patterns.
Code
function throwError(message: string): never {
throw new Error(message);
}
function creatorFunction(fn: (data: unknown, error: typeof throwError) => Promise<any>) {
return async (data: unknown) => await fn(data, throwError);
}
const getUserName = creatorFunction(async (_, throwErrorFromInside) => {
// Simulating a user fetch operation
const user = Math.random() < 0.5 ? null : { name: "test" };
if (!user) throwErrorFromInside("User not found");
console.log(user.name); // ❌ TypeScript error: 'user' is possibly 'null'
if (!user) throwError("User not found");
console.log(user.name); // ✅ No error here
});
Actual behavior
TypeScript does not narrow the user variable after throwErrorFromInside(...) is called, even though:
- The parameter is typed as a never-returning function.
- The control flow after the call should be unreachable if the condition is true.
- The exact same code works when calling the throwError(...) function directly.
Expected behavior
TypeScript should treat:
if (!user) throwErrorFromInside(...);
as equivalent to:
if (!user) throwError(...);
and narrow user accordingly — i.e., allow user.name with no error afterward.
Technical Considerations / Trade-offs
- No changes to emitted JS; strictly compile-time.
- Minimal and safe extension to control-flow analysis.
- Could reuse existing mechanisms for recognizing class/global functions, but would need enhancements to support parameter references where the function is known to return never.
Backward Compatibility
No breaking changes; only narrowing behavior is enhanced for specific patterns.
Conclusion
The ask is modest but meaningful: allow TypeScript to narrow after calls to never-typed function parameters when safe. This eliminates common developer frustrations without runtime impact.