Description
Control Flow Analysis for Destructuring Assignments
let { x } = obj;
// *should* be equivalent to
let x = obj.x;
And this holds for any level of nesting.
let { x: { y } } = obj;
// *should* be equivalent to
let y = obj.x.y;
And the same for object pattern assignments
({ x } = obj);
// *should* be equivalent to
x = obj.x;
- Today, we don't track these assignments as part of control flow analysis.
- Trying to do so is actually pretty complex.
- As a solution, we're considering a new "synthetic property access"
Unification
-
Consider
function compose<A, B, C>(f1: (x: A) => B, f2: (x: B) => C): (x: A) => C { /*...*/ } function arrayToList<T>(x: T[]): List<T> { /*...*/ } function box<U>(x: U): { value: U } { /*...*/ } function first<W>(xs: List<W>): W { /*...*/ }
-
What if you wanted to write a function that
box
'd a list converted from an array? Ideally you could write the following:compose(arrayToList, box)
and you'd get the type
<T>(xs: T[]) => { value: List<T> }
- Today the problem is that when our inference process sees no inferences for a type parameter, it infers its upper bound (which is implicitly
{}
). - Instead, what you really want to do is see that there are free type variables that are captured in the output, and attach them to the output type.
- Starting off with this idea comes close. You get
<T, U>(xs: T) => U
, but of course, this doesn't propagate the original structures of inferences. Furthermore,T
andU
are unrelated. So what you want to do is unify the type variables during the first inference pass.
- Today the problem is that when our inference process sees no inferences for a type parameter, it infers its upper bound (which is implicitly
-
When you could do this is tough; you can't just replace the current inference process with unification. No telling what could break. But we can revisit that.
-
So saying "unification would do this" is fine, but how that happens is glossed over. There's some difficult cases.
-
A : T[] B : List<T>, SomeOtherList<W> C : W
-
Could say we only unify on named types.
- What about object literal types?
- It's a structural type system; y'can't backpedal on that.
-
Today we fix type parameters once they're requested in an anonymous function. How does this work with contextual typing?
-
Say it's there for single-signature types?
- It's not about overloads.
-
Would we need to add type variables for anonymous functions?
-
Also, building up constraints could get gross:
compose(arrayToList, x => x.get(0));
-
Could defer things.
-
The type might look gross for users.
{ get(): T }
is not so bad, but imagine a more complex function body.
-
-
When you you're editing, what happens when the anonymous function comes first?
- Hah.
- Same downsides as today.
- Functional users won't be happy with that experience.
- "Daniel's not wrong"
- [[First time anyone's ever said that]]
- "Daniel's not wrong"
-
Seems like there's a lot of open questions.
Call signatures on union types
- We erred on the safe side much of the time to say that signatures had to be identical.
- In the last release, we expanded this to have signatures that totally subsumed other signatures available to callers.
- When we unified JSX logic with call resolution logic, React users got broken because we were more permissive in React.
- Okay, so we methods available a lot of the time when you have two union types; however, it doesn't work great for methods that take callbacks.
- For example, if you have
xs: string[] | number[]
, trying to callxs.forEach
is possible, but lambda functions don't undergo contextual typing (one of our inference processes). - The reason why is that the contextual type is something like
((x: string) => void) & ((y: number) => void)
. - You'd think that would be fine and in
xs.forEach(a => a)
,a
would have the typestring | number
, but what instead happens is that((x: string) => void) & ((y: number) => void)
forms something that looks like an overload list, and lambdas aren't contextually typed by overloads.
- For example, if you have
- Three solutions:
- Change contextual typing to be contextually typed by overload lists.
- Could come up with an underlying "intersection signature" (we have something similar for unions).
- Means there's a difference between contextual typing and relationship checking.
- Take the current fix (Allow calls on unions of dissimilar signatures #29011) as-is.
- The
forEach
case is still broken, but at least we're better.
- The
- With the intersection signature strategy, what about an intersection where one type has no call signatures, and the other has many?
- You'd have no "intersection signature", but a bunch of overloads.
- One thing we didn't do here is try to come up with an intersection signature for multiple overloads; seems like a combinatorial explosion.
- With the overload strategy, what about UX? What about huge overload lists like in the DOM?
- Should we try to remove impossible
never
parameters from these "synthesized" signatures?- Seems like the wrong thing to focus on.
- Out of time - seems like we're leaning towards overloads.