Skip to content

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

Description

🔎 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 === 'ja@existe.com') {
      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.

Activity

  1. MartinJohns commented on May 3, 2025

    @MartinJohns
    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.
  2. kaioodutra commented on May 5, 2025

    @kaioodutra
    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.

  3. MartinJohns commented on May 6, 2025

    @MartinJohns
    Contributor

    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?

  4. typescript-bot commented on May 9, 2025

    @typescript-bot
    Contributor

    This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  5. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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