Skip to content

Cross-module inference issue: never-returning function doesn't prevent further type checking in async context #61649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
kaioodutra opened this issue May 3, 2025 · 3 comments

Comments

@kaioodutra
Copy link

🔎 Search Terms

"never function not inferred", "async function never return type", "cross-module never return inference", "throw function doesn't stop execution", "ApolloError inferred as possible return"

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about control flow inference across modules and never functions.

⏯ Playground Link

No response

💻 Code

//throwErrorHandler.ts
class CustomError extends Error {
  constructor(public readonly data: any) {
    super(data.message);
  }
}

export const throwErrorHandler = (type: string, data: any): never => {
  throw new CustomError(data);
};


//createUserService.ts
import { throwErrorHandler } from './throwErrorHandler';

export const createUserService = async (data: any) => {
  if (!data?.email) {
    return throwErrorHandler('INPUT', { message: 'E-mail is required!' });
  }

  try {
    if (data.email === '[email protected]') {
      return throwErrorHandler('INPUT', { message: 'E-mail already exists!' });
    }

    return {
      token: 'token-fake',
      user: {
        id: '1',
        email: data.email,
        name: data.name,
      },
    };
  } catch (error) {
    return throwErrorHandler('APOLLO', { message: 'Erro interno', metadata: error });
  }
};

🙁 Actual behavior

TypeScript does not understand that throwTypedError() will never return. Even though the return type is correctly typed as never, and it throws immediately, the compiler still demands a return statement after it — especially in async functions returning Promise.

This happens only when the function is imported from another file or module. If the same function is inlined, TS understands the control flow and does not require the extra return.

🙂 Expected behavior

TypeScript should propagate the never type properly across modules and treat throwTypedError() as the termination of the control flow path. No further return or unreachable code warning should be necessary after such calls.

Additional information about the issue

This limitation affects async services that call reusable throw functions, such as throwErrorHandler(...) in systems like Apollo GraphQL, resulting in unnecessary boilerplate return undefined as never statements or unreachable code just to satisfy the compiler.

We understand this might be a deep inference challenge across modules, but a fix or compiler hint (@terminates, @NoReturn, etc.) would improve developer ergonomics.

@MartinJohns
Copy link
Contributor

TypeScript does not understand that throwTypedError() will never return.

There is no such function in your code. If you actually mean throwErrorHandler, then this is working and completely unrelated to modules or async. The behavior will be the same within the same file.

See #61464 and many many many more.

From #32695:

A function call is analyzed as an assertion call or never-returning call when

  • the call occurs as a top-level expression statement, and
  • the call specifies a single identifier or a dotted sequence of identifiers for the function name, and
  • each identifier in the function name references an entity with an explicit type, and
  • the function name resolves to a function type with an asserts return type or an explicit never return type annotation.

@kaioodutra
Copy link
Author

The problem is that when you have an asynchronous function from another module that returns never, TypeScript ignores this fact and gives a typing error right below the call.

@MartinJohns
Copy link
Contributor

MartinJohns commented May 6, 2025

I'm not sure what you mean. An async function can't return never, it needs to return a promise.

Can you provide a minimal example that actually shows the problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants