Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Design Meeting Notes, 3/12/2021 #43219

Closed
DanielRosenwasser opened this issue Mar 12, 2021 · 0 comments
Closed

Design Meeting Notes, 3/12/2021 #43219

DanielRosenwasser opened this issue Mar 12, 2021 · 0 comments
Labels
Design Notes Notes from our design meetings

Comments

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Mar 12, 2021

Promise Truthiness Check Issues

#43071

@RyanCavanaugh took notes for this section:

  • Non-strict class property initialization means some things declared truthily might actually not be
    • Same for array access
  • But we error because we think the property is always initialized thus always truthy
    • Many of these "should have" been declared optional
  • Also can happen for late-initialized lets accesed in a callback (where we don't warn on lack of initialization)
  • Uncalled functions and unawaited promises should never be an error when the same identifier is referenced in the truthy block
  • Unclear how to fix "probably truthy" expressions like array access, class properties, and lets in callbacks
  • Fix just the obvious bugs here

Narrowing Generic Types Constrained to Unions in Control Flow Analysis

#43183

function f<T extends "a" | "b">(x: T) {
    if (x === "a") {
        x // ?
        takeA(x) // this should work
    }
    else {
        x // ?
    }
}
  • Could imagine x has type T & "a", but then creates huge intersections!

    • Especially in the else case.
  • Talked with Pyright team, they have a similar issue.

    • They try the declared (generic) type first, then try to get the constraint and operate on that (double-check with them).

    • We do something similar with

      function f<T extends Item | undefined>(x: T) {
          if (x) {
              obj.propName // works
          }
      }
  • So idea: if an x is/contains a T extends Some | Union, when x is in a position where it's contextually typed by C, and C doesn't have any direct generic types

    • then we can narrow x using the constraint of T.
  • This makes us more complete, but adds surpises when you start moving code around.

    function f<T extends Foo | undefined>(x: T) {
        if (x) {
            takeFoo(x); // works
    
            let y = x;
            takeFoo(y); // doesn't! 😬
        }
    }
    • Because the declaration of y doesn't provide a contextual type to x, so it just gets type T (which is un-narrowed).
  • Do we actually cache?

    • Yes! And that's the cool part, our cache key incorporates both the current reference as well as the declared type. So you can cache information about x with respect to both its declared type (T extends Union) and its declared type's constraint (Union itself).
  • Last time we leveraged contextual typing with CFA, we ended up with cycles. Dealt with?

    • getConstraintForLocation usual place, but for identifiers, property accesses, etc., getConstraintForReference.
  • Is there a reason why the constraint has to be a union before we consider narrowing?

    • Nothing to gain if it's not a union.

Narrowing Intersections of Primitives & Generics

#43131

  • When we have union types that sit "below" intersection types, we go wrong in our reasoning. Happens when you have generics constrained to unions.

    function f<T extends string | number>(x: T) {
        if (x === "hello") {
            x // want this to be `string` or `"hello"`
        }
    }
    • In a sense, T extends string | number can be seen as T & (string | number).
    • Here, you have a situation where you want to see this as T & (string | number) & "hello", but but it's hard to normalize this after the fact.
    • If you did, you'd end up with T & string & "hello" | T & number & "hello" which would simplify to T & "hello".
  • So we've modified the comparable relationship to at least just recognize this pattern, without rewriting the types at any point.

Narrowing Record Types with in

#41478

  • Idea: "foo" in x is something like a type guard that creates (typeof x) & Record<"foo", unknown>.
    • Does this work with the join at the bottom of the CFG?
    • As long as you don't do anything in the negative case - then it'll just vanish.
  • "Core premise is not wonky."
  • Only do this in not-a-union cases.
  • Does the Record map to any or unknown?
    • The notes a few lines above already say unknown so I've assumed unknown. 😅
    • Yes, that's the only safe thing, don't add new holes in the system.
@DanielRosenwasser DanielRosenwasser added the Design Notes Notes from our design meetings label Mar 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Notes Notes from our design meetings
Projects
None yet
Development

No branches or pull requests

1 participant