Skip to content

Design Meeting Notes, 7/22/2022 #50008

Closed
Closed
@DanielRosenwasser

Description

@DanielRosenwasser

Inference Change Between Unions of undefined

#49938

Lots of examples

Here's a minimal break.

type B = { foo: string, };
type D = { foo: string, bar: number };

declare function equals<T>(a: T, b: T): boolean;

function f(b: B, d: D | undefined) {
    // Used to work, now infers `B` and fails.
    if (equals(b, d)) {

    }
}

Playground link

Narrowing Changes Against Top-ish Types

#49988

  • The isMap type predicate now narrows values to a Map<...> | ReadonlyMap<...> instead of Map<...> if that's what you already had.

  • Introduced by Improve narrowing logic for instanceof, type predicate functions, and assertion functions #49625

  • Start with this example.

    type Falsy = false | 0 | 0n | '' | null | undefined;
    
    declare function isFalsy(value: unknown): value is Falsy;
    
    function fx1(x: string | number | undefined) {
        if (isFalsy(x)) {
            x;  // "" | 0 | undefined, previously undefined
        }
    }
  • But people depend on the old behavior.

  • This is now different

    declare function isMap<T>(object: T | {}): object is T extends ReadonlyMap<any, any> ? (unknown extends T ? never : ReadonlyMap<any, any>) : Map<unknown, unknown>;
    declare const romor: ReadonlyMap<any, any> | Record<any, any>
    if (isMap(romor)) {
        romor; // Previously `ReadonlyMap<any, any>`, now `ReadonlyMap<any, any> | Map<any, any>`
    }
  • But ReadonlyMap<...> | Map<...> just provides all the same methods as ReadonlyMap<...>.

    • Are they sufficiently identical between their methods?
    • Often these signatures are not, which makes them effectively uncallable.
    • We think yes.
  • The isObject case is worse.

    • Previous behavior was weird! (see playground)

      declare function isObject(value: unknown): value is Record<string, unknown>;
      
      function f1(obj: {}) {
          if (isObject(obj)) {
              // Worked, obj is narrowed to Record<string, unknown>
              obj["stuff"]
          }
      }
      
      function f2(obj: {} | undefined) {
          if (isObject(obj)) {
              // Doesn't work obj is not narrowed.
              obj["stuff"]
          }
      }
    • Now neither example works. (see playground)

    • Will need to think through these.

  • WeakRef example

    • Very incorrect. T is never witnessed, and WeakRef is supposed to be a proxy around T itself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design NotesNotes from our design meetings

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions