Skip to content

Enhance Control‑Flow Narrowing for never‑returning Functions Passed as Parameters #62235

@mattiamalonni

Description

@mattiamalonni

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions