Skip to content

Design Meeting Notes, 5/13/2022 #49137

Closed
Closed
@DanielRosenwasser

Description

@DanielRosenwasser

Limiting Type Argument Inference from Binding Patterns

#49086

declare function f<T>(): T;

// Inference from binding patterns makes suspicious code like this work.
const { foo } = f(); // T: { foo: any }

// Motivation
declare function pick<T, K extends keyof T>(keys: K[], obj?: T): Pick<T, K>;
const _ = pick(["b"], { a: "a", b: "b", }); // T = "b"
const {} = pick(["b"], { a: "a", b: "b", }); // T = "b" | "a" (!?) (before fix)
  • Arguably, these anys should be implicit anys that get an error under noImplicitAny.

  • Inference from return types is generally suspicious, but binding patterns are kind of in the same domain.

  • Idea last time: binding patterns are useful for tuples, but not objects.

    declare function f<T>(cb: () => T): T;
    const [e1, e2, e3] = f(() => [1, "hi", true]);
    • But then

      declare function f<T>(): T;
      const [foo] = f(); // ?
  • So when does an object binding pattern provide a useful contextual type?

    • If the binding pattern has a default.

    • For literals it seems useless.

    • More complex defaults?

      // Union of literals
      declare const oneOrZero: 0 | 1;
      const { b = oneOrZero } = f({ b: 0 });
      
      // Contextually sensitive parameters
      function doSomething(x: string): string;
      const { func = doSomething } = f({ func: x => x });
  • Is this another level of inference priority?

    • No, we just have two sets of inference contexts.
  • Conclusion: do it, get reviewed

Narrowing Type Parameters (un)Constrained to unknown

#49091
#49119

function deepEquals<T extends unknown>(a: T, b: T): boolean {
    if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
        return false;
    }
    if (Array.isArray(a) || Array.isArray(b)) {
        return false;
    }
    if (Object.keys(a).length !== Object.keys(b).length) { // Error here
        return false;
    }
    return true;
}
  • Given someUnknown: unknown, typeof someUnknown === "object today narrows to object | null, but it doesn't narrow a T extends unknown
  • Added some changes to do this, then added some changes so keyof NonNullable<T>keyof T succeeds.
    • Needed this when you index into these types, you need to preserve new behavior on higher order NonNullable<T> that you get (rather than on the previous T).
  • Have a parallel effort to improve how intersections with {} operate - could unlock the same scenarios.
    • Ideally, NonNullable<T> would just be T & {}, especially because intersections compose way better than conditional types.
    • With more aggressive subtype supertype reduction with {}, we can get there.
    • Also, need to be able to allow unknown to be split between object, null, undefined, and {} and remerged in CFA.
  • Can we do this without more aggressive subtype reduction?
    • Actually, this isn't doing more aggressive subtype reduction, it's just supertype reduction.
    • [[Above notes now reflect this]]
  • Two approaches here, one focuses on the negative cases, the other on the positive cases.
    • Both apply new narrowing
    • Each PR has different "fallout", and has mitigations for the fallout.
  • Conclusion: See if we can smush these changes together and take the best of each?

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