Skip to content

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

Closed as not planned
@kaioodutra

Description

@kaioodutra

🔎 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.

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