[wip] Fix phi inference, expose InferMutableRange issue
> Update: this is now passing all tests. The approach is likely wrong, and even if it's fine it needs some cleanup. Putting up for review as folks (esp @gsathya) have time. ## Background InferTypes was intended to infer types for phi identifiers, but by accident we ended up storing the inferred type on `phi.type` instead of `phi.id.type`, which is the type that usages of the phi will reference. Because of this, we weren't actually inferring types for several cases, for example if both if/else branches assign `x` to an array literal, we'd ideally like the corresponding phi id to be typed as a BuiltInArray: ```javascript let x; let y = { ... }; if (cond) { x = []; } else { x = []; } // x should be BuiltnArray here. We inferred that on Phi.type but the x here wouldn't get that type previously x.push(y); ``` ## Circular Types I started by removing the `Phi.type` property and updating inference to store the result of phi unification on `phi.id.type` — but this revealed other issues. First was this can create circular types when there are loops. The solution is to basically allow circular types _for phis only_, and when we detect them we remove the cycle. Basically whenever we have a situation where we have some type variable X, and a type Y that is a (nested) phi type one of whose transitive operands contains X, we remove X from the transitive type and attempt to collapse the phi type upwards if all of its remaining operands are the same: ``` X=Type(1) Y=Phi [ Type(2), Type(3) = Phi [ Type(1), // <-- cycle but we can prune this Type(2), Type(2), ] ] => X=Type(1) Y=Phi [ Type(2), Type(3) = Phi [ // all remaining operands are the same, we can prune this Type(2), Type(2), ] ] => X=Type(1) Y=Phi [ // all remaining operands are the same, we can prune this Type(2), Type(2), ] => X=Type(1) Y=Type(2) ``` We have to do this not just doing unify(), but also in `get()` since there are cases where we don't know yet which type variables we can remove from a phi. Without also doing the pruning in get, we get an infinite loop. ## Reactive Scope Alignment The above fixed the circular types, but exposed some new cases that can occur in terms of mutable ranges and ast structures: it wasn't possible before to have a Store on a phi node in practice, since that relied on type information which we didn't have for phis. The new validation that all instructions for a scope are part of that scope caught a couple issues, which were basically like this: ``` [1] Sequence ... [9] StoreLocal x@0[9:28] [10] ... ``` Note that scope 0 starts at instruction 9, but that instruction is not at the block scope level. The first instruction at the block scope level that is within the range of scope 0 is instruction 10, which is after the scope should have started! So I also had to update AlignScopesToBlockScopes to handle the case of logical, conditional, and sequence expressions: we sometime need to adjust a scope start earlier in case they contain instructions that should start a scope.