1. 10
  1. 4

    We try to write an assertion type to confirm that the type guard’s internal logic is actually proving something at the type level. Otherwise you can have factually incorrect type guards because Typescript makes no effort to validate them. You’d be surprised at how many type safety regressions this catches during refactors.

    type Assert<Base, T extends Base> = T
    class Toad {
      config?: Config
      hasConfig(): this is { config: Config } {
        if (this.config) {
          // check logic actually narrowed our type
          type _assertHasConfig = Assert<{ config: Config }, this>
          return true;
        return false;

    (I’m not sure if this exact assertion works here as we don’t use much class at Notion.)

    1. 1

      I’m not sure I understand– can you demonstrate where this is necessary with a counterexample?

      1. 3

        I think he means that the if statement is arbitrary and typescript doesn’t enforce that you are actually narrowing the type to what the return type assertion says.

        See this playground example.

        In the if statement I’m checking the name instead of config, and everything compiles normally, which means that you can have runtime errors because you lied to the type system.

        If you apply jitl’s assertion, then the code doesn’t compile. See this other playground example

        But then if you fix the if statement the code still doesn’t compile: See this last playground example.

        It would be very interesting to see how this would be properly done.

        1. 2

          Yeah, I think the narrowing rules work differently or don’t work for this versus more normal references. I haven’t had time in front of a computer to make a good example — all my lobsters posting is from my phone.