Skip to content

Exhaustiveness check fails for tagged union with a partial discriminant. #278

@jb-asi

Description

@jb-asi

Describe the bug
Do we see any issue with the first match below, or no? Another reported issue followed up with .otherwise; but - in my case - we use .exhaustive and end up throwing this error at runtime:

Error: Pattern matching error: no pattern matches value {}

Curiously, though, there is no compile-time type error as you'd usually expect to be coming from .exhaustive.

Thinking this may be caused by using a tagged union type with a partial tag (meaning one of the unioned types has an optional tagging property). The match..with..exhaustive expression seems to be fine with { type: undefined } but then throws at runtime. Only when swapping to { type: P.optional(undefined) } does the runtime error subside; but the main issue is that .exhaustive does not seem to be checking for this edge case.

import { match, P } from 'ts-pattern'

// a tagged union where `type` serves as the tag but is optional for the "default" type.
type One = {
  // default
  type?: 'one'; 
};
type Two = {
  type: 'two';
};
type Thing = One | Two;

const thing: Thing = {};

function iThrow(thing: Thing): Thing['type'] {
  return match(thing)
    .with({ type: 'one' }, () => 'one' as const)
    .with({ type: 'two' }, () => 'two' as const)
    // will throw at runtime without a compile-time error
    .with({ type: undefined }, () => undefined) 
    .exhaustive();
}

function imOk(thing: Thing): Thing['type'] {
  return match(thing)
    .with({ type: 'one' }, () => 'one' as const)
    .with({ type: 'two' }, () => 'two' as const)
    .with({ type: P.optional(undefined) }, () => undefined) 
    .exhaustive();
}

console.log(iThrow(thing));
console.log(imOk(thing));

As another data point, if we change the types like so:

type One = {
  // default
  type?: undefined; 
};
type Two = {
  type: undefined;
};

There is an .exhaustive error in that case.

Really appreciate this library. If this seems simple enough for a public contributor to assist with in some capacity, let me know.

Versions

  • TypeScript version: 5.4.5
  • ts-pattern version: 5.1.1
  • environment: Chrome 128.0.6613.114

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions