-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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,TandUare 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.forEachis 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),awould 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
forEachcase 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
neverparameters from these "synthesized" signatures?- Seems like the wrong thing to focus on.
- Out of time - seems like we're leaning towards overloads.