microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.33k stars 12.53k forks source link

Unions of assertion functions do not act as assertion functions #59707

Open AlexPaven opened 3 months ago

AlexPaven commented 3 months ago

πŸ”Ž Search Terms

narrowing, assertion function

πŸ•— Version & Regression Information

⏯ Playground Link

https://tsplay.dev/Wv3DYw

πŸ’» Code

  class box<T> {
    constructor(public value: T){}

    check(): this is box<string> {
      return typeof this.value == 'string';
    }

    assert(): asserts this is box<string> {
      if (typeof this.value != 'string') throw new Error();
    }

    private test() {
      this.assert();
      // type correctly narrowed
      this.value.substring(0);
    }
  }

  function make() : box<string> | box<number> {
    return new box('a');
  }

  function assert(b: box<string> | box<number>): asserts b is box<string> {
    if (typeof b.value != 'string') throw new Error();
  }

  const b = make();

  if (b.check()) {
    // type correctly narrowed
    b.value.substring(0);
  }

  b.assert();

  // type not narrowed (substring does not exist on type 'string | number')
  b.value.substring(0);

  assert(b);

  // type correctly narrowed
  b.value.substring(0);

πŸ™ Actual behavior

Type is not narrowed after method call

πŸ™‚ Expected behavior

I expected the type to be narrowed after method call since it is narrowed within other methods or when using a function external to the type.

Additional information about the issue

https://stackoverflow.com/questions/78879014/typescript-type-assertion-does-not-narrow-class-instance-from-union

Andarist commented 3 months ago

According to the source code, it's deliberate (link):

// Constituent type predicates must all have matching kinds. We don't create composite type predicates for assertions.
jcalz commented 3 months ago

If it's not supported it would be nice if it produced an error similar to #33622 instead of just silently failing.

Andarist commented 3 months ago

@RyanCavanaugh could you clarify what is the bug here? should it work or should it error in a visible way like @jcalz is suggesting? I think the latter but it would be great to have confirmation