Skip to content

silentNeverType leak through return type inferenceΒ #62824

@Andarist

Description

@Andarist

πŸ”Ž Search Terms

silent never leak reverse mapped types nested calls return type inference

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried

⏯ Playground Link

https://www.typescriptlang.org/play/?noEmit=true&ts=6.0.0-dev.20251202#code/C4TwDgpgBAaghgGwK4QM4B4AqA+KBeKTAbQGsIQB7AM0IF0BuAKEdEigFk4BjACwEsAdhADCFAcAgAPYPigAlCFwoAnACbpUwZYIDmAGihwBIbE0aCJyqt2gAFOMrgBbCJb4AvCKoDyAIwBWijIA3oxQUKwQAFxQmtoCOkzhYA7OqAD8MUgCJAIUAO4CTAC+zJFQAIJcwHxiAGLZ1bUC6GGEouJSMl0QAqqoHNz8Qh0S0nptmPaOTgM9fQPTzq4Q2p4+AUFEAEQpM6jbtFAAPlDZqhBUgl4T4ZhVNWJQ8-1QSy5u636B1RO4BKFwgAKaqSGKYUZdAx7NLg96oACUMQAbhQ+KoklAAPoUJDALH3JpiTKEB7NEpmcqYCjvFZrLzfIKtO60z5eThgZ7SXqvBRKNTM8KxLS6W5C1mrDwMzbVHYw2aHE5nPqXa6qNrYP6yeDINDoQFQIgAaSggigZEoNCmqQ+kvWHKgADJhfEdLQYgbwpEYkbMckbag4Ta6VLVBzjQw2sUSqYyuBoKIEAggmSxBhJpDpFyJAtBrxrpngGLrftszzFsG2RsfsA5QHFadzqqhKotQQgW1PVAlJ1xm1wr0AI4oFBi4oe-vdsRjYDgwt+54CYcQFAxIHcR4CNcI-C4VHond4PdojFRsXywOEeETQ-H9FmC5cBAOaBURqbxfLlCpgTpu6Fstc04fMRmnKFJnhQDXglelqy2XZ6yORsVSuFtix-KCKxmEMvhlGQCBg0NGV+RhsA7cIlCTIIYkTZNqh-DAITA8YrwDTVGCRSoiQEBoBG4rBCwMEs0iEn9Y0YR9n2UV932aWJXCQMBBSY3tum5IChgLZii0mBjMPkRQVHUSc4lFSdCNwms632BtlQuVCvA1WRgmKP4gQNSJUDFDdmi8xhxygDz4wyD0p1UkkVJnKBozaHy0xJLtjVNARzXIahSW41B3S4zdeP4yd-20sUhQyzdUAjYqhWpCzpRrLAGOwSdY3CGLik4-dT0YVAFLAdzYsyicStUCgAGVgCQKgqBiIcRwgBigSBLFoQDGIBCQJxfFWW9ArascJjasxuvG3qDTi39BqFYaxomqbP1m+bFuW-ZVvWzblG2lyEUq4bvGAHhVmuya1yWqALxiUyEg+1yz38hFDp6vrwjOy8uyu8agbulc5syhaQbBqA1o2rbdx2r7Jx+v6AfR27FpiIwQCe2EXV0KG9thpggA

πŸ’» Code

type Values<T> = T[keyof T];

type MachineContext = Record<string, any>;

interface ParameterizedObject {
  type: string;
  params?: unknown;
}

type ActionFunction<
  TContext extends MachineContext,
  TParams extends ParameterizedObject["params"] | undefined,
  TAction extends ParameterizedObject,
> = {
  (ctx: TContext, params: TParams): void;
  _out_TAction?: TAction;
};

type ToParameterizedObject<
  TParameterizedMap extends Record<
    string,
    ParameterizedObject["params"] | undefined
  >,
> = Values<{
  [K in keyof TParameterizedMap & string]: {
    type: K;
    params: TParameterizedMap[K];
  };
}>;

type CollectActions<
  TContext extends MachineContext,
  TParams extends ParameterizedObject["params"] | undefined,
> = (
  {
    context,
    enqueue,
  }: {
    context: TContext;
    enqueue: (action: () => void) => void;
  },
  params: TParams,
) => void;

declare function enqueueActions<
  TContext extends MachineContext,
  TParams extends ParameterizedObject["params"] | undefined,
  TAction extends ParameterizedObject = ParameterizedObject,
>(
  collect: CollectActions<TContext, TParams>,
): ActionFunction<TContext, TParams, TAction>;

declare function setup<
  TContext extends MachineContext,
  TActions extends Record<
    string,
    ParameterizedObject["params"] | undefined
  > = {},
>({
  types,
  actions,
}: {
  types?: { context?: TContext };
  actions?: {
    [K in keyof TActions]: ActionFunction<
      TContext,
      TActions[K],
      ToParameterizedObject<TActions>
    >;
  };
}): void;

setup({
  actions: {
    doStuff: enqueueActions((_, params: number) => {}),
  },
});

setup({
  actions: {
    doStuff: enqueueActions((_, params: number) => {}),
    doOtherStuff: (_, params: string) => {},
  },
});

setup({
  actions: {
    doStuff: enqueueActions((_, params: number) => {}),
    doOtherStuff: (_: any, params: string) => {},
  },
});

πŸ™ Actual behavior

First 2 calls mention such a relationship check failure in the error message:

Type 'ActionFunction<MachineContext, number, { type: string; params: never; }>' is not assignable to type 'ActionFunction<MachineContext, number, { type: "doStuff"; params: number; }>'.

Notice never there. This is quite weird an unexpected as there is no never in sight here. It turns out this is a silentNeverType that leaked through.

πŸ™‚ Expected behavior

At the very least, I would expect it to error with consistent error messages mentioning unknown instead of never. Even better, if it could behave closer to a very similar version of this code: TS playground. In there the third error still errors but the first two infers nicely.

Additional information about the issue

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptHelp WantedYou can do this

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions