ITADN

Type inference issue with discriminated unions in .otherwise() - remaining types not properly narrowed

#324ClosedThenkei 创建于 2025-06-25
enhancement
T
Thenkeicommented
## Description When pattern matching on discriminated unions with specific literal types, the .otherwise() clause should only include the remaining unmatched cases. However, `ts-pattern` currently shows the parameter type as the full original union instead of just the remaining unmatched types. ## Reproduction ```ts import { match } from "ts-pattern"; // Simple discriminated union type Animal = | { type: "dog"; breed: string } | { type: "cat"; color: string } | { type: "bird"; wingspan: number }; // Problem case let animal: Animal; match(animal) .with({ type: "dog" }, ({ breed }) => { // This correctly matches { type: "dog"; breed: string } console.log(`Dog breed: ${breed}`); }) .with({ type: "cat" }, ({ color }) => { // This correctly matches { type: "cat"; color: string } console.log(`Cat color: ${color}`); }) .otherwise((remaining) => { // ISSUE: `remaining` should be typed as { type: "bird"; wingspan: number } // But it's currently typed as the full Animal union // TypeScript should know that only "bird" is possible here console.log(remaining.type); // Should know this can only be "bird" // remaining.wingspan should be accessible without type assertion }); ``` ## Expected Behavior The parameter in .otherwise() should be typed as { type: "bird"; wingspan: number } since: 1. { type: "dog" } cases are handled by the first pattern 2. { type: "cat" } cases are handled by the second pattern 3. Only { type: "bird" } cases remain unmatched ## Actual Behavior The parameter in .otherwise() is typed as the full Animal union type, which includes all variants even though some have already been matched. ### Even Simpler Example ```ts type Status = "loading" | "success" | "error"; let status: Status; match(status) .with("loading", () => console.log("Loading...")) .with("success", () => console.log("Success!")) .otherwise((remaining) => { // ISSUE: `remaining` should be typed as "error" // But it's currently typed as "loading" | "success" | "error" console.log(remaining); // Should know this can only be "error" }); ``` ## Analysis This appears to be a limitation in how `ts-pattern` computes the "remaining" or "unmatched" types for exhaustiveness checking. The library should be able to: - Track which specific union members have been explicitly matched - Compute the remaining unmatched union members - Apply this narrowing to the .otherwise() parameter type This is particularly important for discriminated unions where precise type narrowing enables better type safety and developer experience. ### Solution Use the same trick as `exhaustive()` ``` otherwise<c>( handler: (value: DeepExcludeAll<i, handledCases>) => PickReturnValue<o, c> ): PickReturnValue<o, Union<inferredOutput, c>>; ``` ## Already open thread - https://github.com/gvergnaud/ts-pattern/issues/235
关闭于 2025-07-27 4 条评论