From 6a0a26a712dacf752e67574144f351db0bd658fe Mon Sep 17 00:00:00 2001 From: milkcask <1572863+milkcask@users.noreply.github.com> Date: Wed, 16 Sep 2026 03:09:54 +0100 Subject: [PATCH 1/2] Iterate antecedent pass so self-assignment in a loop correctly narrows A union-typed variable reassigned in a loop from an expression that reads it, such as `cur = cur.next`, now narrows to every constituent the loop can reach. Previously the loop body saw one step less than it should: for a chain of three classes `cur` was typed as the first two, so a property present only on the third was rejected and a property missing from it was accepted. Longer chains lost exactly the same step. The antecedent pass over a loop label is one step of an approximation toward the correct type, and, since 4.3.5, it was only ever run once. To type the assignment it asks the expression checker for the assigned type, which starts a nested flow analysis of the same variable. That analysis reaches the loop label still on the flow loop stack, takes the partial union gathered so far, and returns it with its incomplete marker dropped at the analysis boundary. The label unions that in and caches the result as final, so the number of narrowing steps was the depth of that recursion rather than the number needed to converge. Add a `reentered` field to `FlowLoopInfo`, the flow loop stack entry. It is set when the checker computes the flow type of a variable while that variable's own assignment is being evaluated, as in `currentParent = currentParent.parent`. The antecedent pass is then wrapped in an outer loop: push the loop label flow node onto the flow loop stack, run the pass, and if the `reentered` flag was set and the resulting union grew, run the pass again, as a fixed-point iteration, until nothing is added. At that point the approximation is exact. Only union declared types iterate, since only they narrow by assigned type; re-seeding an evolving array would degrade it to `any[]`. Passes are bounded by the union's size, falling back to the declared type. Add a test with chains of 3, 4 and 50 levels, a discriminant narrowing that used to become `never`, a property access that must error, and an evolving array that must keep its element type. Fixes microsoft/TypeScript#59715 --- tsc/internal/checker/checker.go | 5 +- tsc/internal/checker/flow.go | 111 +++- ...owLoopSelfReferentialAssignment.errors.txt | 146 +++++ ...lFlowLoopSelfReferentialAssignment.symbols | 606 ++++++++++++++++++ ...rolFlowLoopSelfReferentialAssignment.types | 498 ++++++++++++++ ...ontrolFlowLoopSelfReferentialAssignment.ts | 140 ++++ 6 files changed, 1469 insertions(+), 37 deletions(-) create mode 100644 tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.errors.txt create mode 100644 tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.symbols create mode 100644 tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.types create mode 100644 tsc/testdata/tests/cases/conformance/controlFlow/controlFlowLoopSelfReferentialAssignment.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index d19cf6b214b79..54fbfb06aae69 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -256,8 +256,9 @@ type FlowLoopKey struct { } type FlowLoopInfo struct { - key FlowLoopKey - types []*Type + key FlowLoopKey + types []*Type + reentered bool // set when the in-process types were observed by a nested flow analysis of the same reference } // InferenceFlags diff --git a/tsc/internal/checker/flow.go b/tsc/internal/checker/flow.go index d08254cc8ef88..5f2c2fb6ccbaa 100644 --- a/tsc/internal/checker/flow.go +++ b/tsc/internal/checker/flow.go @@ -1344,56 +1344,97 @@ func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowT // a non-empty in-process array for the outer loop and eventually terminate because // the first antecedent of a loop junction is always the non-looping control flow // path that leads to the top. - for _, loopInfo := range c.flowLoopStack { + for i := range c.flowLoopStack { + loopInfo := &c.flowLoopStack[i] if loopInfo.key == key && len(loopInfo.types) != 0 { + // Record that the in-process result was observed so that the junction being + // computed knows its looping antecedents were derived from a partial type. + loopInfo.reentered = true return c.newFlowType(c.getUnionOrEvolvingArrayType(f, loopInfo.types, UnionReductionLiteral), true /*incomplete*/) } } // Add the flow loop junction and reference to the in-process stack and analyze // each antecedent code path. + // + // When the declared type is a union, an assignment in the loop body narrows the declared + // type by the assigned type, and computing the assigned type may read the reference itself + // (e.g. 'x = x.next'). Such a read re-enters this junction and observes the in-process + // (partial) union, and the incomplete marker on that observation is lost once it crosses + // the expression checker boundary. A single pass therefore computes only one application + // of the loop's transfer function. To reach a fixpoint, we re-run the antecedent pass with + // the current result as the in-process approximation whenever a looping antecedent re-entered + // this junction and the result grew. Each pass can only add constituents of the declared + // union, so the number of passes is bounded by the union's size; if the bound is reached, + // we fall back to the declared type. + iterate := f.declaredType.flags&TypeFlagsUnion != 0 + maxPasses := 1 + if iterate { + maxPasses = len(f.declaredType.Types()) + 1 + } + sharedFlowsAtEntry := len(c.sharedFlows) antecedentTypes := make([]*Type, 0, 4) subtypeReduction := false var firstAntecedentType FlowType - for list := flow.Antecedents; list != nil; list = list.Next { - var flowType FlowType - if firstAntecedentType.isNil() { - // The first antecedent of a loop junction is always the non-looping control - // flow path that leads to the top. - firstAntecedentType = c.getTypeAtFlowNode(f, list.Flow) - flowType = firstAntecedentType - } else { - // All but the first antecedent are the looping control flow paths that lead - // back to the loop junction. We track these on the flow loop stack. - c.flowLoopStack = append(c.flowLoopStack, FlowLoopInfo{key: key, types: antecedentTypes}) - saveFlowTypeCache := c.flowTypeCache - c.flowTypeCache = nil - flowType = c.getTypeAtFlowNode(f, list.Flow) - c.flowTypeCache = saveFlowTypeCache - c.flowLoopStack = c.flowLoopStack[:len(c.flowLoopStack)-1] - // If we see a value appear in the cache it is a sign that control flow analysis - // was restarted and completed by checkExpressionCached. We can simply pick up - // the resulting type and bail out. - if cached := c.flowLoopCache[key]; cached != nil { - return FlowType{t: cached} - } - } - antecedentTypes = core.AppendIfUnique(antecedentTypes, flowType.t) - // If an antecedent type is not a subset of the declared type, we need to perform - // subtype reduction. This happens when a "foreign" type is injected into the control - // flow using the instanceof operator or a user defined type predicate. - if !c.isTypeSubsetOf(flowType.t, f.initialType) { - subtypeReduction = true + var result *Type + for pass := 1; ; pass++ { + reentered := false + firstAntecedentType = FlowType{} + for list := flow.Antecedents; list != nil; list = list.Next { + var flowType FlowType + if firstAntecedentType.isNil() { + // The first antecedent of a loop junction is always the non-looping control + // flow path that leads to the top. + firstAntecedentType = c.getTypeAtFlowNode(f, list.Flow) + flowType = firstAntecedentType + } else { + // All but the first antecedent are the looping control flow paths that lead + // back to the loop junction. We track these on the flow loop stack. + c.flowLoopStack = append(c.flowLoopStack, FlowLoopInfo{key: key, types: antecedentTypes}) + saveFlowTypeCache := c.flowTypeCache + c.flowTypeCache = nil + flowType = c.getTypeAtFlowNode(f, list.Flow) + c.flowTypeCache = saveFlowTypeCache + top := len(c.flowLoopStack) - 1 + reentered = reentered || c.flowLoopStack[top].reentered + c.flowLoopStack = c.flowLoopStack[:top] + // If we see a value appear in the cache it is a sign that control flow analysis + // was restarted and completed by checkExpressionCached. We can simply pick up + // the resulting type and bail out. + if cached := c.flowLoopCache[key]; cached != nil { + return FlowType{t: cached} + } + } + antecedentTypes = core.AppendIfUnique(antecedentTypes, flowType.t) + // If an antecedent type is not a subset of the declared type, we need to perform + // subtype reduction. This happens when a "foreign" type is injected into the control + // flow using the instanceof operator or a user defined type predicate. + if !c.isTypeSubsetOf(flowType.t, f.initialType) { + subtypeReduction = true + } + // If the type at a particular antecedent path is the declared type there is no + // reason to process more antecedents since the only possible outcome is subtypes + // that will be removed in the final union type anyway. + if flowType.t == f.declaredType { + break + } + } + previous := result + result = c.getUnionOrEvolvingArrayType(f, antecedentTypes, core.IfElse(subtypeReduction, UnionReductionSubtype, UnionReductionLiteral)) + // Stop once the result no longer grows. Results that differ only in literal freshness are + // considered equal, since another pass would observe the same set of constituents. + if !iterate || !reentered || firstAntecedentType.incomplete || previous != nil && c.getRegularTypeOfLiteralType(result) == c.getRegularTypeOfLiteralType(previous) { + break } - // If the type at a particular antecedent path is the declared type there is no - // reason to process more antecedents since the only possible outcome is subtypes - // that will be removed in the final union type anyway. - if flowType.t == f.declaredType { + if pass >= maxPasses { + result = f.declaredType break } + // Discard shared flow node types computed from the partial result during this pass. + c.sharedFlows = c.sharedFlows[:sharedFlowsAtEntry] + antecedentTypes = []*Type{result} } // The result is incomplete if the first antecedent (the non-looping control flow path) // is incomplete. - result := c.getUnionOrEvolvingArrayType(f, antecedentTypes, core.IfElse(subtypeReduction, UnionReductionSubtype, UnionReductionLiteral)) if firstAntecedentType.incomplete { return c.newFlowType(result, true /*incomplete*/) } diff --git a/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.errors.txt b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.errors.txt new file mode 100644 index 0000000000000..cb6ed5bbde743 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.errors.txt @@ -0,0 +1,146 @@ +controlFlowLoopSelfReferentialAssignment.ts(53,31): error TS2339: Property 'prop' does not exist on type 'WithProp1 | WithProp2 | WithProp3'. + Property 'prop' does not exist on type 'WithProp3'. + + +==== controlFlowLoopSelfReferentialAssignment.ts (1 errors) ==== + // Repro from #59715 + + declare class Child { parent: Parent | null; } + declare class Parent { parent: GrandParent | null; } + declare class GrandParent { parent: GreatGrandParent | null; } + declare class GreatGrandParent { parent: null; } + declare const child: Child; + + function three() { + let currentParent: GreatGrandParent | GrandParent | Parent | null = child.parent; + while (currentParent) { + currentParent; // should be Parent | GrandParent | GreatGrandParent + currentParent = currentParent.parent; + } + } + + // Four levels: also wrong before #43183 (one transfer step short per nesting depth) + + declare class A1 { nom: "a1"; next(): A2 | null; } + declare class A2 { nom: "a2"; next(): A3 | null; } + declare class A3 { nom: "a3"; next(): A4 | null; } + declare class A4 { nom: "a4"; next(): null; } + type All = A1 | A2 | A3 | A4; + + function four(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4 + if (p.nom === "a4") { + p; // should be A4, not never + } + p = p.next(); + } + } + + // for-statement form from the issue thread + + function forLoop(a: A1) { + for (let q: All | null = a; q !== null; q = q.next()) { + q; // should be A1 | A2 | A3 | A4 + } + } + + // Same chain via a property access reference, and soundness: this must be an error. + + declare class WithProp1 { parent: WithProp2 | null; prop: number; } + declare class WithProp2 { parent: WithProp3 | null; prop: number; } + declare class WithProp3 { parent: null; } + + function unsound(start: WithProp1 | null) { + let cur: WithProp1 | WithProp2 | WithProp3 | null = start; + while (cur) { + const n: number = cur.prop; // should be an error: WithProp3 has no prop + ~~~~ +!!! error TS2339: Property 'prop' does not exist on type 'WithProp1 | WithProp2 | WithProp3'. +!!! error TS2339: Property 'prop' does not exist on type 'WithProp3'. + cur = cur.parent; + } + } + + // Evolving array created in an outer loop and pushed in an inner loop. The fix must not + // iterate for auto-typed variables: doing so finalizes the evolving array and degrades it to any[]. + + declare const outer: number[][]; + function evolving() { + let result; + for (const list of outer) { + result ||= []; + for (const n of list) result.push(n); + } + return result; // should be number[] | undefined + } + + // Fifty levels deep. Each pass over the loop junction adds at least one constituent of the + // declared union, so the number of passes scales linearly with the depth of the chain, and + // the bound (union size + 1) guarantees convergence at any depth. + + declare class D1 { depth: 1; parent: D2 | null; } + declare class D2 { depth: 2; parent: D3 | null; } + declare class D3 { depth: 3; parent: D4 | null; } + declare class D4 { depth: 4; parent: D5 | null; } + declare class D5 { depth: 5; parent: D6 | null; } + declare class D6 { depth: 6; parent: D7 | null; } + declare class D7 { depth: 7; parent: D8 | null; } + declare class D8 { depth: 8; parent: D9 | null; } + declare class D9 { depth: 9; parent: D10 | null; } + declare class D10 { depth: 10; parent: D11 | null; } + declare class D11 { depth: 11; parent: D12 | null; } + declare class D12 { depth: 12; parent: D13 | null; } + declare class D13 { depth: 13; parent: D14 | null; } + declare class D14 { depth: 14; parent: D15 | null; } + declare class D15 { depth: 15; parent: D16 | null; } + declare class D16 { depth: 16; parent: D17 | null; } + declare class D17 { depth: 17; parent: D18 | null; } + declare class D18 { depth: 18; parent: D19 | null; } + declare class D19 { depth: 19; parent: D20 | null; } + declare class D20 { depth: 20; parent: D21 | null; } + declare class D21 { depth: 21; parent: D22 | null; } + declare class D22 { depth: 22; parent: D23 | null; } + declare class D23 { depth: 23; parent: D24 | null; } + declare class D24 { depth: 24; parent: D25 | null; } + declare class D25 { depth: 25; parent: D26 | null; } + declare class D26 { depth: 26; parent: D27 | null; } + declare class D27 { depth: 27; parent: D28 | null; } + declare class D28 { depth: 28; parent: D29 | null; } + declare class D29 { depth: 29; parent: D30 | null; } + declare class D30 { depth: 30; parent: D31 | null; } + declare class D31 { depth: 31; parent: D32 | null; } + declare class D32 { depth: 32; parent: D33 | null; } + declare class D33 { depth: 33; parent: D34 | null; } + declare class D34 { depth: 34; parent: D35 | null; } + declare class D35 { depth: 35; parent: D36 | null; } + declare class D36 { depth: 36; parent: D37 | null; } + declare class D37 { depth: 37; parent: D38 | null; } + declare class D38 { depth: 38; parent: D39 | null; } + declare class D39 { depth: 39; parent: D40 | null; } + declare class D40 { depth: 40; parent: D41 | null; } + declare class D41 { depth: 41; parent: D42 | null; } + declare class D42 { depth: 42; parent: D43 | null; } + declare class D43 { depth: 43; parent: D44 | null; } + declare class D44 { depth: 44; parent: D45 | null; } + declare class D45 { depth: 45; parent: D46 | null; } + declare class D46 { depth: 46; parent: D47 | null; } + declare class D47 { depth: 47; parent: D48 | null; } + declare class D48 { depth: 48; parent: D49 | null; } + declare class D49 { depth: 49; parent: D50 | null; } + declare class D50 { depth: 50; parent: null; } + type Deep = D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9 | D10 | D11 | D12 | D13 | D14 | D15 | D16 | D17 | D18 | D19 | D20 | D21 | D22 | D23 | D24 | D25 | D26 | D27 | D28 | D29 | D30 | D31 | D32 | D33 | D34 | D35 | D36 | D37 | D38 | D39 | D40 | D41 | D42 | D43 | D44 | D45 | D46 | D47 | D48 | D49 | D50; + declare const deepStart: D1 | null; + + function fifty() { + let cur: Deep | null = deepStart; + while (cur) { + cur; // should be all of D1 through D50 + if (cur.depth === 50) { + cur; // should be D50, not never + } + cur = cur.parent; + } + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.symbols b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.symbols new file mode 100644 index 0000000000000..f05ed70d4d914 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.symbols @@ -0,0 +1,606 @@ +//// [tests/cases/conformance/controlFlow/controlFlowLoopSelfReferentialAssignment.ts] //// + +=== controlFlowLoopSelfReferentialAssignment.ts === +// Repro from #59715 + +declare class Child { parent: Parent | null; } +>Child : Symbol(Child, Decl(controlFlowLoopSelfReferentialAssignment.ts, 0, 0)) +>parent : Symbol(Child.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 2, 21)) +>Parent : Symbol(Parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 2, 46)) + +declare class Parent { parent: GrandParent | null; } +>Parent : Symbol(Parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 2, 46)) +>parent : Symbol(Parent.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 3, 22)) +>GrandParent : Symbol(GrandParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 3, 52)) + +declare class GrandParent { parent: GreatGrandParent | null; } +>GrandParent : Symbol(GrandParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 3, 52)) +>parent : Symbol(GrandParent.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 4, 27)) +>GreatGrandParent : Symbol(GreatGrandParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 4, 62)) + +declare class GreatGrandParent { parent: null; } +>GreatGrandParent : Symbol(GreatGrandParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 4, 62)) +>parent : Symbol(GreatGrandParent.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 5, 32)) + +declare const child: Child; +>child : Symbol(child, Decl(controlFlowLoopSelfReferentialAssignment.ts, 6, 13)) +>Child : Symbol(Child, Decl(controlFlowLoopSelfReferentialAssignment.ts, 0, 0)) + +function three() { +>three : Symbol(three, Decl(controlFlowLoopSelfReferentialAssignment.ts, 6, 27)) + + let currentParent: GreatGrandParent | GrandParent | Parent | null = child.parent; +>currentParent : Symbol(currentParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 9, 7)) +>GreatGrandParent : Symbol(GreatGrandParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 4, 62)) +>GrandParent : Symbol(GrandParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 3, 52)) +>Parent : Symbol(Parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 2, 46)) +>child.parent : Symbol(Child.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 2, 21)) +>child : Symbol(child, Decl(controlFlowLoopSelfReferentialAssignment.ts, 6, 13)) +>parent : Symbol(Child.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 2, 21)) + + while (currentParent) { +>currentParent : Symbol(currentParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 9, 7)) + + currentParent; // should be Parent | GrandParent | GreatGrandParent +>currentParent : Symbol(currentParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 9, 7)) + + currentParent = currentParent.parent; +>currentParent : Symbol(currentParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 9, 7)) +>currentParent.parent : Symbol(parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 4, 27), Decl(controlFlowLoopSelfReferentialAssignment.ts, 5, 32), Decl(controlFlowLoopSelfReferentialAssignment.ts, 3, 22)) +>currentParent : Symbol(currentParent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 9, 7)) +>parent : Symbol(parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 4, 27), Decl(controlFlowLoopSelfReferentialAssignment.ts, 5, 32), Decl(controlFlowLoopSelfReferentialAssignment.ts, 3, 22)) + } +} + +// Four levels: also wrong before #43183 (one transfer step short per nesting depth) + +declare class A1 { nom: "a1"; next(): A2 | null; } +>A1 : Symbol(A1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 14, 1)) +>nom : Symbol(A1.nom, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 18)) +>next : Symbol(A1.next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29)) +>A2 : Symbol(A2, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 50)) + +declare class A2 { nom: "a2"; next(): A3 | null; } +>A2 : Symbol(A2, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 50)) +>nom : Symbol(A2.nom, Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 18)) +>next : Symbol(A2.next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29)) +>A3 : Symbol(A3, Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 50)) + +declare class A3 { nom: "a3"; next(): A4 | null; } +>A3 : Symbol(A3, Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 50)) +>nom : Symbol(A3.nom, Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 18)) +>next : Symbol(A3.next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29)) +>A4 : Symbol(A4, Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 50)) + +declare class A4 { nom: "a4"; next(): null; } +>A4 : Symbol(A4, Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 50)) +>nom : Symbol(A4.nom, Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 18)) +>next : Symbol(A4.next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) + +type All = A1 | A2 | A3 | A4; +>All : Symbol(All, Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 45)) +>A1 : Symbol(A1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 14, 1)) +>A2 : Symbol(A2, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 50)) +>A3 : Symbol(A3, Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 50)) +>A4 : Symbol(A4, Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 50)) + +function four(a: A1) { +>four : Symbol(four, Decl(controlFlowLoopSelfReferentialAssignment.ts, 22, 29)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 24, 14)) +>A1 : Symbol(A1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 14, 1)) + + let p: All | null = a; +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 25, 7)) +>All : Symbol(All, Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 45)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 24, 14)) + + while (p) { +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 25, 7)) + + p; // should be A1 | A2 | A3 | A4 +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 25, 7)) + + if (p.nom === "a4") { +>p.nom : Symbol(nom, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 18), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 18), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 18), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 18)) +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 25, 7)) +>nom : Symbol(nom, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 18), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 18), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 18), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 18)) + + p; // should be A4, not never +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 25, 7)) + } + p = p.next(); +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 25, 7)) +>p.next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 25, 7)) +>next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) + } +} + +// for-statement form from the issue thread + +function forLoop(a: A1) { +>forLoop : Symbol(forLoop, Decl(controlFlowLoopSelfReferentialAssignment.ts, 33, 1)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 37, 17)) +>A1 : Symbol(A1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 14, 1)) + + for (let q: All | null = a; q !== null; q = q.next()) { +>q : Symbol(q, Decl(controlFlowLoopSelfReferentialAssignment.ts, 38, 12)) +>All : Symbol(All, Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 45)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 37, 17)) +>q : Symbol(q, Decl(controlFlowLoopSelfReferentialAssignment.ts, 38, 12)) +>q : Symbol(q, Decl(controlFlowLoopSelfReferentialAssignment.ts, 38, 12)) +>q.next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) +>q : Symbol(q, Decl(controlFlowLoopSelfReferentialAssignment.ts, 38, 12)) +>next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) + + q; // should be A1 | A2 | A3 | A4 +>q : Symbol(q, Decl(controlFlowLoopSelfReferentialAssignment.ts, 38, 12)) + } +} + +// Same chain via a property access reference, and soundness: this must be an error. + +declare class WithProp1 { parent: WithProp2 | null; prop: number; } +>WithProp1 : Symbol(WithProp1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 41, 1)) +>parent : Symbol(WithProp1.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 45, 25)) +>WithProp2 : Symbol(WithProp2, Decl(controlFlowLoopSelfReferentialAssignment.ts, 45, 67)) +>prop : Symbol(WithProp1.prop, Decl(controlFlowLoopSelfReferentialAssignment.ts, 45, 51)) + +declare class WithProp2 { parent: WithProp3 | null; prop: number; } +>WithProp2 : Symbol(WithProp2, Decl(controlFlowLoopSelfReferentialAssignment.ts, 45, 67)) +>parent : Symbol(WithProp2.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 46, 25)) +>WithProp3 : Symbol(WithProp3, Decl(controlFlowLoopSelfReferentialAssignment.ts, 46, 67)) +>prop : Symbol(WithProp2.prop, Decl(controlFlowLoopSelfReferentialAssignment.ts, 46, 51)) + +declare class WithProp3 { parent: null; } +>WithProp3 : Symbol(WithProp3, Decl(controlFlowLoopSelfReferentialAssignment.ts, 46, 67)) +>parent : Symbol(WithProp3.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 47, 25)) + +function unsound(start: WithProp1 | null) { +>unsound : Symbol(unsound, Decl(controlFlowLoopSelfReferentialAssignment.ts, 47, 41)) +>start : Symbol(start, Decl(controlFlowLoopSelfReferentialAssignment.ts, 49, 17)) +>WithProp1 : Symbol(WithProp1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 41, 1)) + + let cur: WithProp1 | WithProp2 | WithProp3 | null = start; +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 50, 7)) +>WithProp1 : Symbol(WithProp1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 41, 1)) +>WithProp2 : Symbol(WithProp2, Decl(controlFlowLoopSelfReferentialAssignment.ts, 45, 67)) +>WithProp3 : Symbol(WithProp3, Decl(controlFlowLoopSelfReferentialAssignment.ts, 46, 67)) +>start : Symbol(start, Decl(controlFlowLoopSelfReferentialAssignment.ts, 49, 17)) + + while (cur) { +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 50, 7)) + + const n: number = cur.prop; // should be an error: WithProp3 has no prop +>n : Symbol(n, Decl(controlFlowLoopSelfReferentialAssignment.ts, 52, 13)) +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 50, 7)) + + cur = cur.parent; +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 50, 7)) +>cur.parent : Symbol(parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 45, 25), Decl(controlFlowLoopSelfReferentialAssignment.ts, 46, 25), Decl(controlFlowLoopSelfReferentialAssignment.ts, 47, 25)) +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 50, 7)) +>parent : Symbol(parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 45, 25), Decl(controlFlowLoopSelfReferentialAssignment.ts, 46, 25), Decl(controlFlowLoopSelfReferentialAssignment.ts, 47, 25)) + } +} + +// Evolving array created in an outer loop and pushed in an inner loop. The fix must not +// iterate for auto-typed variables: doing so finalizes the evolving array and degrades it to any[]. + +declare const outer: number[][]; +>outer : Symbol(outer, Decl(controlFlowLoopSelfReferentialAssignment.ts, 60, 13)) + +function evolving() { +>evolving : Symbol(evolving, Decl(controlFlowLoopSelfReferentialAssignment.ts, 60, 32)) + + let result; +>result : Symbol(result, Decl(controlFlowLoopSelfReferentialAssignment.ts, 62, 7)) + + for (const list of outer) { +>list : Symbol(list, Decl(controlFlowLoopSelfReferentialAssignment.ts, 63, 14)) +>outer : Symbol(outer, Decl(controlFlowLoopSelfReferentialAssignment.ts, 60, 13)) + + result ||= []; +>result : Symbol(result, Decl(controlFlowLoopSelfReferentialAssignment.ts, 62, 7)) + + for (const n of list) result.push(n); +>n : Symbol(n, Decl(controlFlowLoopSelfReferentialAssignment.ts, 65, 18)) +>list : Symbol(list, Decl(controlFlowLoopSelfReferentialAssignment.ts, 63, 14)) +>result.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>result : Symbol(result, Decl(controlFlowLoopSelfReferentialAssignment.ts, 62, 7)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>n : Symbol(n, Decl(controlFlowLoopSelfReferentialAssignment.ts, 65, 18)) + } + return result; // should be number[] | undefined +>result : Symbol(result, Decl(controlFlowLoopSelfReferentialAssignment.ts, 62, 7)) +} + +// Fifty levels deep. Each pass over the loop junction adds at least one constituent of the +// declared union, so the number of passes scales linearly with the depth of the chain, and +// the bound (union size + 1) guarantees convergence at any depth. + +declare class D1 { depth: 1; parent: D2 | null; } +>D1 : Symbol(D1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 68, 1)) +>depth : Symbol(D1.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 74, 18)) +>parent : Symbol(D1.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 74, 28)) +>D2 : Symbol(D2, Decl(controlFlowLoopSelfReferentialAssignment.ts, 74, 49)) + +declare class D2 { depth: 2; parent: D3 | null; } +>D2 : Symbol(D2, Decl(controlFlowLoopSelfReferentialAssignment.ts, 74, 49)) +>depth : Symbol(D2.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 75, 18)) +>parent : Symbol(D2.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 75, 28)) +>D3 : Symbol(D3, Decl(controlFlowLoopSelfReferentialAssignment.ts, 75, 49)) + +declare class D3 { depth: 3; parent: D4 | null; } +>D3 : Symbol(D3, Decl(controlFlowLoopSelfReferentialAssignment.ts, 75, 49)) +>depth : Symbol(D3.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 76, 18)) +>parent : Symbol(D3.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 76, 28)) +>D4 : Symbol(D4, Decl(controlFlowLoopSelfReferentialAssignment.ts, 76, 49)) + +declare class D4 { depth: 4; parent: D5 | null; } +>D4 : Symbol(D4, Decl(controlFlowLoopSelfReferentialAssignment.ts, 76, 49)) +>depth : Symbol(D4.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 77, 18)) +>parent : Symbol(D4.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 77, 28)) +>D5 : Symbol(D5, Decl(controlFlowLoopSelfReferentialAssignment.ts, 77, 49)) + +declare class D5 { depth: 5; parent: D6 | null; } +>D5 : Symbol(D5, Decl(controlFlowLoopSelfReferentialAssignment.ts, 77, 49)) +>depth : Symbol(D5.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 78, 18)) +>parent : Symbol(D5.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 78, 28)) +>D6 : Symbol(D6, Decl(controlFlowLoopSelfReferentialAssignment.ts, 78, 49)) + +declare class D6 { depth: 6; parent: D7 | null; } +>D6 : Symbol(D6, Decl(controlFlowLoopSelfReferentialAssignment.ts, 78, 49)) +>depth : Symbol(D6.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 79, 18)) +>parent : Symbol(D6.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 79, 28)) +>D7 : Symbol(D7, Decl(controlFlowLoopSelfReferentialAssignment.ts, 79, 49)) + +declare class D7 { depth: 7; parent: D8 | null; } +>D7 : Symbol(D7, Decl(controlFlowLoopSelfReferentialAssignment.ts, 79, 49)) +>depth : Symbol(D7.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 80, 18)) +>parent : Symbol(D7.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 80, 28)) +>D8 : Symbol(D8, Decl(controlFlowLoopSelfReferentialAssignment.ts, 80, 49)) + +declare class D8 { depth: 8; parent: D9 | null; } +>D8 : Symbol(D8, Decl(controlFlowLoopSelfReferentialAssignment.ts, 80, 49)) +>depth : Symbol(D8.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 81, 18)) +>parent : Symbol(D8.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 81, 28)) +>D9 : Symbol(D9, Decl(controlFlowLoopSelfReferentialAssignment.ts, 81, 49)) + +declare class D9 { depth: 9; parent: D10 | null; } +>D9 : Symbol(D9, Decl(controlFlowLoopSelfReferentialAssignment.ts, 81, 49)) +>depth : Symbol(D9.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 82, 18)) +>parent : Symbol(D9.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 82, 28)) +>D10 : Symbol(D10, Decl(controlFlowLoopSelfReferentialAssignment.ts, 82, 50)) + +declare class D10 { depth: 10; parent: D11 | null; } +>D10 : Symbol(D10, Decl(controlFlowLoopSelfReferentialAssignment.ts, 82, 50)) +>depth : Symbol(D10.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 83, 19)) +>parent : Symbol(D10.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 83, 30)) +>D11 : Symbol(D11, Decl(controlFlowLoopSelfReferentialAssignment.ts, 83, 52)) + +declare class D11 { depth: 11; parent: D12 | null; } +>D11 : Symbol(D11, Decl(controlFlowLoopSelfReferentialAssignment.ts, 83, 52)) +>depth : Symbol(D11.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 84, 19)) +>parent : Symbol(D11.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 84, 30)) +>D12 : Symbol(D12, Decl(controlFlowLoopSelfReferentialAssignment.ts, 84, 52)) + +declare class D12 { depth: 12; parent: D13 | null; } +>D12 : Symbol(D12, Decl(controlFlowLoopSelfReferentialAssignment.ts, 84, 52)) +>depth : Symbol(D12.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 85, 19)) +>parent : Symbol(D12.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 85, 30)) +>D13 : Symbol(D13, Decl(controlFlowLoopSelfReferentialAssignment.ts, 85, 52)) + +declare class D13 { depth: 13; parent: D14 | null; } +>D13 : Symbol(D13, Decl(controlFlowLoopSelfReferentialAssignment.ts, 85, 52)) +>depth : Symbol(D13.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 86, 19)) +>parent : Symbol(D13.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 86, 30)) +>D14 : Symbol(D14, Decl(controlFlowLoopSelfReferentialAssignment.ts, 86, 52)) + +declare class D14 { depth: 14; parent: D15 | null; } +>D14 : Symbol(D14, Decl(controlFlowLoopSelfReferentialAssignment.ts, 86, 52)) +>depth : Symbol(D14.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 87, 19)) +>parent : Symbol(D14.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 87, 30)) +>D15 : Symbol(D15, Decl(controlFlowLoopSelfReferentialAssignment.ts, 87, 52)) + +declare class D15 { depth: 15; parent: D16 | null; } +>D15 : Symbol(D15, Decl(controlFlowLoopSelfReferentialAssignment.ts, 87, 52)) +>depth : Symbol(D15.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 88, 19)) +>parent : Symbol(D15.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 88, 30)) +>D16 : Symbol(D16, Decl(controlFlowLoopSelfReferentialAssignment.ts, 88, 52)) + +declare class D16 { depth: 16; parent: D17 | null; } +>D16 : Symbol(D16, Decl(controlFlowLoopSelfReferentialAssignment.ts, 88, 52)) +>depth : Symbol(D16.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 89, 19)) +>parent : Symbol(D16.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 89, 30)) +>D17 : Symbol(D17, Decl(controlFlowLoopSelfReferentialAssignment.ts, 89, 52)) + +declare class D17 { depth: 17; parent: D18 | null; } +>D17 : Symbol(D17, Decl(controlFlowLoopSelfReferentialAssignment.ts, 89, 52)) +>depth : Symbol(D17.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 90, 19)) +>parent : Symbol(D17.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 90, 30)) +>D18 : Symbol(D18, Decl(controlFlowLoopSelfReferentialAssignment.ts, 90, 52)) + +declare class D18 { depth: 18; parent: D19 | null; } +>D18 : Symbol(D18, Decl(controlFlowLoopSelfReferentialAssignment.ts, 90, 52)) +>depth : Symbol(D18.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 91, 19)) +>parent : Symbol(D18.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 91, 30)) +>D19 : Symbol(D19, Decl(controlFlowLoopSelfReferentialAssignment.ts, 91, 52)) + +declare class D19 { depth: 19; parent: D20 | null; } +>D19 : Symbol(D19, Decl(controlFlowLoopSelfReferentialAssignment.ts, 91, 52)) +>depth : Symbol(D19.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 92, 19)) +>parent : Symbol(D19.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 92, 30)) +>D20 : Symbol(D20, Decl(controlFlowLoopSelfReferentialAssignment.ts, 92, 52)) + +declare class D20 { depth: 20; parent: D21 | null; } +>D20 : Symbol(D20, Decl(controlFlowLoopSelfReferentialAssignment.ts, 92, 52)) +>depth : Symbol(D20.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 93, 19)) +>parent : Symbol(D20.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 93, 30)) +>D21 : Symbol(D21, Decl(controlFlowLoopSelfReferentialAssignment.ts, 93, 52)) + +declare class D21 { depth: 21; parent: D22 | null; } +>D21 : Symbol(D21, Decl(controlFlowLoopSelfReferentialAssignment.ts, 93, 52)) +>depth : Symbol(D21.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 94, 19)) +>parent : Symbol(D21.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 94, 30)) +>D22 : Symbol(D22, Decl(controlFlowLoopSelfReferentialAssignment.ts, 94, 52)) + +declare class D22 { depth: 22; parent: D23 | null; } +>D22 : Symbol(D22, Decl(controlFlowLoopSelfReferentialAssignment.ts, 94, 52)) +>depth : Symbol(D22.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 95, 19)) +>parent : Symbol(D22.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 95, 30)) +>D23 : Symbol(D23, Decl(controlFlowLoopSelfReferentialAssignment.ts, 95, 52)) + +declare class D23 { depth: 23; parent: D24 | null; } +>D23 : Symbol(D23, Decl(controlFlowLoopSelfReferentialAssignment.ts, 95, 52)) +>depth : Symbol(D23.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 96, 19)) +>parent : Symbol(D23.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 96, 30)) +>D24 : Symbol(D24, Decl(controlFlowLoopSelfReferentialAssignment.ts, 96, 52)) + +declare class D24 { depth: 24; parent: D25 | null; } +>D24 : Symbol(D24, Decl(controlFlowLoopSelfReferentialAssignment.ts, 96, 52)) +>depth : Symbol(D24.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 97, 19)) +>parent : Symbol(D24.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 97, 30)) +>D25 : Symbol(D25, Decl(controlFlowLoopSelfReferentialAssignment.ts, 97, 52)) + +declare class D25 { depth: 25; parent: D26 | null; } +>D25 : Symbol(D25, Decl(controlFlowLoopSelfReferentialAssignment.ts, 97, 52)) +>depth : Symbol(D25.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 98, 19)) +>parent : Symbol(D25.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 98, 30)) +>D26 : Symbol(D26, Decl(controlFlowLoopSelfReferentialAssignment.ts, 98, 52)) + +declare class D26 { depth: 26; parent: D27 | null; } +>D26 : Symbol(D26, Decl(controlFlowLoopSelfReferentialAssignment.ts, 98, 52)) +>depth : Symbol(D26.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 99, 19)) +>parent : Symbol(D26.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 99, 30)) +>D27 : Symbol(D27, Decl(controlFlowLoopSelfReferentialAssignment.ts, 99, 52)) + +declare class D27 { depth: 27; parent: D28 | null; } +>D27 : Symbol(D27, Decl(controlFlowLoopSelfReferentialAssignment.ts, 99, 52)) +>depth : Symbol(D27.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 100, 19)) +>parent : Symbol(D27.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 100, 30)) +>D28 : Symbol(D28, Decl(controlFlowLoopSelfReferentialAssignment.ts, 100, 52)) + +declare class D28 { depth: 28; parent: D29 | null; } +>D28 : Symbol(D28, Decl(controlFlowLoopSelfReferentialAssignment.ts, 100, 52)) +>depth : Symbol(D28.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 101, 19)) +>parent : Symbol(D28.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 101, 30)) +>D29 : Symbol(D29, Decl(controlFlowLoopSelfReferentialAssignment.ts, 101, 52)) + +declare class D29 { depth: 29; parent: D30 | null; } +>D29 : Symbol(D29, Decl(controlFlowLoopSelfReferentialAssignment.ts, 101, 52)) +>depth : Symbol(D29.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 102, 19)) +>parent : Symbol(D29.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 102, 30)) +>D30 : Symbol(D30, Decl(controlFlowLoopSelfReferentialAssignment.ts, 102, 52)) + +declare class D30 { depth: 30; parent: D31 | null; } +>D30 : Symbol(D30, Decl(controlFlowLoopSelfReferentialAssignment.ts, 102, 52)) +>depth : Symbol(D30.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 103, 19)) +>parent : Symbol(D30.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 103, 30)) +>D31 : Symbol(D31, Decl(controlFlowLoopSelfReferentialAssignment.ts, 103, 52)) + +declare class D31 { depth: 31; parent: D32 | null; } +>D31 : Symbol(D31, Decl(controlFlowLoopSelfReferentialAssignment.ts, 103, 52)) +>depth : Symbol(D31.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 104, 19)) +>parent : Symbol(D31.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 104, 30)) +>D32 : Symbol(D32, Decl(controlFlowLoopSelfReferentialAssignment.ts, 104, 52)) + +declare class D32 { depth: 32; parent: D33 | null; } +>D32 : Symbol(D32, Decl(controlFlowLoopSelfReferentialAssignment.ts, 104, 52)) +>depth : Symbol(D32.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 105, 19)) +>parent : Symbol(D32.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 105, 30)) +>D33 : Symbol(D33, Decl(controlFlowLoopSelfReferentialAssignment.ts, 105, 52)) + +declare class D33 { depth: 33; parent: D34 | null; } +>D33 : Symbol(D33, Decl(controlFlowLoopSelfReferentialAssignment.ts, 105, 52)) +>depth : Symbol(D33.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 106, 19)) +>parent : Symbol(D33.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 106, 30)) +>D34 : Symbol(D34, Decl(controlFlowLoopSelfReferentialAssignment.ts, 106, 52)) + +declare class D34 { depth: 34; parent: D35 | null; } +>D34 : Symbol(D34, Decl(controlFlowLoopSelfReferentialAssignment.ts, 106, 52)) +>depth : Symbol(D34.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 107, 19)) +>parent : Symbol(D34.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 107, 30)) +>D35 : Symbol(D35, Decl(controlFlowLoopSelfReferentialAssignment.ts, 107, 52)) + +declare class D35 { depth: 35; parent: D36 | null; } +>D35 : Symbol(D35, Decl(controlFlowLoopSelfReferentialAssignment.ts, 107, 52)) +>depth : Symbol(D35.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 108, 19)) +>parent : Symbol(D35.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 108, 30)) +>D36 : Symbol(D36, Decl(controlFlowLoopSelfReferentialAssignment.ts, 108, 52)) + +declare class D36 { depth: 36; parent: D37 | null; } +>D36 : Symbol(D36, Decl(controlFlowLoopSelfReferentialAssignment.ts, 108, 52)) +>depth : Symbol(D36.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 109, 19)) +>parent : Symbol(D36.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 109, 30)) +>D37 : Symbol(D37, Decl(controlFlowLoopSelfReferentialAssignment.ts, 109, 52)) + +declare class D37 { depth: 37; parent: D38 | null; } +>D37 : Symbol(D37, Decl(controlFlowLoopSelfReferentialAssignment.ts, 109, 52)) +>depth : Symbol(D37.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 110, 19)) +>parent : Symbol(D37.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 110, 30)) +>D38 : Symbol(D38, Decl(controlFlowLoopSelfReferentialAssignment.ts, 110, 52)) + +declare class D38 { depth: 38; parent: D39 | null; } +>D38 : Symbol(D38, Decl(controlFlowLoopSelfReferentialAssignment.ts, 110, 52)) +>depth : Symbol(D38.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 111, 19)) +>parent : Symbol(D38.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 111, 30)) +>D39 : Symbol(D39, Decl(controlFlowLoopSelfReferentialAssignment.ts, 111, 52)) + +declare class D39 { depth: 39; parent: D40 | null; } +>D39 : Symbol(D39, Decl(controlFlowLoopSelfReferentialAssignment.ts, 111, 52)) +>depth : Symbol(D39.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 112, 19)) +>parent : Symbol(D39.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 112, 30)) +>D40 : Symbol(D40, Decl(controlFlowLoopSelfReferentialAssignment.ts, 112, 52)) + +declare class D40 { depth: 40; parent: D41 | null; } +>D40 : Symbol(D40, Decl(controlFlowLoopSelfReferentialAssignment.ts, 112, 52)) +>depth : Symbol(D40.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 113, 19)) +>parent : Symbol(D40.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 113, 30)) +>D41 : Symbol(D41, Decl(controlFlowLoopSelfReferentialAssignment.ts, 113, 52)) + +declare class D41 { depth: 41; parent: D42 | null; } +>D41 : Symbol(D41, Decl(controlFlowLoopSelfReferentialAssignment.ts, 113, 52)) +>depth : Symbol(D41.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 114, 19)) +>parent : Symbol(D41.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 114, 30)) +>D42 : Symbol(D42, Decl(controlFlowLoopSelfReferentialAssignment.ts, 114, 52)) + +declare class D42 { depth: 42; parent: D43 | null; } +>D42 : Symbol(D42, Decl(controlFlowLoopSelfReferentialAssignment.ts, 114, 52)) +>depth : Symbol(D42.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 115, 19)) +>parent : Symbol(D42.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 115, 30)) +>D43 : Symbol(D43, Decl(controlFlowLoopSelfReferentialAssignment.ts, 115, 52)) + +declare class D43 { depth: 43; parent: D44 | null; } +>D43 : Symbol(D43, Decl(controlFlowLoopSelfReferentialAssignment.ts, 115, 52)) +>depth : Symbol(D43.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 116, 19)) +>parent : Symbol(D43.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 116, 30)) +>D44 : Symbol(D44, Decl(controlFlowLoopSelfReferentialAssignment.ts, 116, 52)) + +declare class D44 { depth: 44; parent: D45 | null; } +>D44 : Symbol(D44, Decl(controlFlowLoopSelfReferentialAssignment.ts, 116, 52)) +>depth : Symbol(D44.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 117, 19)) +>parent : Symbol(D44.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 117, 30)) +>D45 : Symbol(D45, Decl(controlFlowLoopSelfReferentialAssignment.ts, 117, 52)) + +declare class D45 { depth: 45; parent: D46 | null; } +>D45 : Symbol(D45, Decl(controlFlowLoopSelfReferentialAssignment.ts, 117, 52)) +>depth : Symbol(D45.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 118, 19)) +>parent : Symbol(D45.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 118, 30)) +>D46 : Symbol(D46, Decl(controlFlowLoopSelfReferentialAssignment.ts, 118, 52)) + +declare class D46 { depth: 46; parent: D47 | null; } +>D46 : Symbol(D46, Decl(controlFlowLoopSelfReferentialAssignment.ts, 118, 52)) +>depth : Symbol(D46.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 119, 19)) +>parent : Symbol(D46.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 119, 30)) +>D47 : Symbol(D47, Decl(controlFlowLoopSelfReferentialAssignment.ts, 119, 52)) + +declare class D47 { depth: 47; parent: D48 | null; } +>D47 : Symbol(D47, Decl(controlFlowLoopSelfReferentialAssignment.ts, 119, 52)) +>depth : Symbol(D47.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 120, 19)) +>parent : Symbol(D47.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 120, 30)) +>D48 : Symbol(D48, Decl(controlFlowLoopSelfReferentialAssignment.ts, 120, 52)) + +declare class D48 { depth: 48; parent: D49 | null; } +>D48 : Symbol(D48, Decl(controlFlowLoopSelfReferentialAssignment.ts, 120, 52)) +>depth : Symbol(D48.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 121, 19)) +>parent : Symbol(D48.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 121, 30)) +>D49 : Symbol(D49, Decl(controlFlowLoopSelfReferentialAssignment.ts, 121, 52)) + +declare class D49 { depth: 49; parent: D50 | null; } +>D49 : Symbol(D49, Decl(controlFlowLoopSelfReferentialAssignment.ts, 121, 52)) +>depth : Symbol(D49.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 122, 19)) +>parent : Symbol(D49.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 122, 30)) +>D50 : Symbol(D50, Decl(controlFlowLoopSelfReferentialAssignment.ts, 122, 52)) + +declare class D50 { depth: 50; parent: null; } +>D50 : Symbol(D50, Decl(controlFlowLoopSelfReferentialAssignment.ts, 122, 52)) +>depth : Symbol(D50.depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 123, 19)) +>parent : Symbol(D50.parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 123, 30)) + +type Deep = D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9 | D10 | D11 | D12 | D13 | D14 | D15 | D16 | D17 | D18 | D19 | D20 | D21 | D22 | D23 | D24 | D25 | D26 | D27 | D28 | D29 | D30 | D31 | D32 | D33 | D34 | D35 | D36 | D37 | D38 | D39 | D40 | D41 | D42 | D43 | D44 | D45 | D46 | D47 | D48 | D49 | D50; +>Deep : Symbol(Deep, Decl(controlFlowLoopSelfReferentialAssignment.ts, 123, 46)) +>D1 : Symbol(D1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 68, 1)) +>D2 : Symbol(D2, Decl(controlFlowLoopSelfReferentialAssignment.ts, 74, 49)) +>D3 : Symbol(D3, Decl(controlFlowLoopSelfReferentialAssignment.ts, 75, 49)) +>D4 : Symbol(D4, Decl(controlFlowLoopSelfReferentialAssignment.ts, 76, 49)) +>D5 : Symbol(D5, Decl(controlFlowLoopSelfReferentialAssignment.ts, 77, 49)) +>D6 : Symbol(D6, Decl(controlFlowLoopSelfReferentialAssignment.ts, 78, 49)) +>D7 : Symbol(D7, Decl(controlFlowLoopSelfReferentialAssignment.ts, 79, 49)) +>D8 : Symbol(D8, Decl(controlFlowLoopSelfReferentialAssignment.ts, 80, 49)) +>D9 : Symbol(D9, Decl(controlFlowLoopSelfReferentialAssignment.ts, 81, 49)) +>D10 : Symbol(D10, Decl(controlFlowLoopSelfReferentialAssignment.ts, 82, 50)) +>D11 : Symbol(D11, Decl(controlFlowLoopSelfReferentialAssignment.ts, 83, 52)) +>D12 : Symbol(D12, Decl(controlFlowLoopSelfReferentialAssignment.ts, 84, 52)) +>D13 : Symbol(D13, Decl(controlFlowLoopSelfReferentialAssignment.ts, 85, 52)) +>D14 : Symbol(D14, Decl(controlFlowLoopSelfReferentialAssignment.ts, 86, 52)) +>D15 : Symbol(D15, Decl(controlFlowLoopSelfReferentialAssignment.ts, 87, 52)) +>D16 : Symbol(D16, Decl(controlFlowLoopSelfReferentialAssignment.ts, 88, 52)) +>D17 : Symbol(D17, Decl(controlFlowLoopSelfReferentialAssignment.ts, 89, 52)) +>D18 : Symbol(D18, Decl(controlFlowLoopSelfReferentialAssignment.ts, 90, 52)) +>D19 : Symbol(D19, Decl(controlFlowLoopSelfReferentialAssignment.ts, 91, 52)) +>D20 : Symbol(D20, Decl(controlFlowLoopSelfReferentialAssignment.ts, 92, 52)) +>D21 : Symbol(D21, Decl(controlFlowLoopSelfReferentialAssignment.ts, 93, 52)) +>D22 : Symbol(D22, Decl(controlFlowLoopSelfReferentialAssignment.ts, 94, 52)) +>D23 : Symbol(D23, Decl(controlFlowLoopSelfReferentialAssignment.ts, 95, 52)) +>D24 : Symbol(D24, Decl(controlFlowLoopSelfReferentialAssignment.ts, 96, 52)) +>D25 : Symbol(D25, Decl(controlFlowLoopSelfReferentialAssignment.ts, 97, 52)) +>D26 : Symbol(D26, Decl(controlFlowLoopSelfReferentialAssignment.ts, 98, 52)) +>D27 : Symbol(D27, Decl(controlFlowLoopSelfReferentialAssignment.ts, 99, 52)) +>D28 : Symbol(D28, Decl(controlFlowLoopSelfReferentialAssignment.ts, 100, 52)) +>D29 : Symbol(D29, Decl(controlFlowLoopSelfReferentialAssignment.ts, 101, 52)) +>D30 : Symbol(D30, Decl(controlFlowLoopSelfReferentialAssignment.ts, 102, 52)) +>D31 : Symbol(D31, Decl(controlFlowLoopSelfReferentialAssignment.ts, 103, 52)) +>D32 : Symbol(D32, Decl(controlFlowLoopSelfReferentialAssignment.ts, 104, 52)) +>D33 : Symbol(D33, Decl(controlFlowLoopSelfReferentialAssignment.ts, 105, 52)) +>D34 : Symbol(D34, Decl(controlFlowLoopSelfReferentialAssignment.ts, 106, 52)) +>D35 : Symbol(D35, Decl(controlFlowLoopSelfReferentialAssignment.ts, 107, 52)) +>D36 : Symbol(D36, Decl(controlFlowLoopSelfReferentialAssignment.ts, 108, 52)) +>D37 : Symbol(D37, Decl(controlFlowLoopSelfReferentialAssignment.ts, 109, 52)) +>D38 : Symbol(D38, Decl(controlFlowLoopSelfReferentialAssignment.ts, 110, 52)) +>D39 : Symbol(D39, Decl(controlFlowLoopSelfReferentialAssignment.ts, 111, 52)) +>D40 : Symbol(D40, Decl(controlFlowLoopSelfReferentialAssignment.ts, 112, 52)) +>D41 : Symbol(D41, Decl(controlFlowLoopSelfReferentialAssignment.ts, 113, 52)) +>D42 : Symbol(D42, Decl(controlFlowLoopSelfReferentialAssignment.ts, 114, 52)) +>D43 : Symbol(D43, Decl(controlFlowLoopSelfReferentialAssignment.ts, 115, 52)) +>D44 : Symbol(D44, Decl(controlFlowLoopSelfReferentialAssignment.ts, 116, 52)) +>D45 : Symbol(D45, Decl(controlFlowLoopSelfReferentialAssignment.ts, 117, 52)) +>D46 : Symbol(D46, Decl(controlFlowLoopSelfReferentialAssignment.ts, 118, 52)) +>D47 : Symbol(D47, Decl(controlFlowLoopSelfReferentialAssignment.ts, 119, 52)) +>D48 : Symbol(D48, Decl(controlFlowLoopSelfReferentialAssignment.ts, 120, 52)) +>D49 : Symbol(D49, Decl(controlFlowLoopSelfReferentialAssignment.ts, 121, 52)) +>D50 : Symbol(D50, Decl(controlFlowLoopSelfReferentialAssignment.ts, 122, 52)) + +declare const deepStart: D1 | null; +>deepStart : Symbol(deepStart, Decl(controlFlowLoopSelfReferentialAssignment.ts, 125, 13)) +>D1 : Symbol(D1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 68, 1)) + +function fifty() { +>fifty : Symbol(fifty, Decl(controlFlowLoopSelfReferentialAssignment.ts, 125, 35)) + + let cur: Deep | null = deepStart; +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 128, 7)) +>Deep : Symbol(Deep, Decl(controlFlowLoopSelfReferentialAssignment.ts, 123, 46)) +>deepStart : Symbol(deepStart, Decl(controlFlowLoopSelfReferentialAssignment.ts, 125, 13)) + + while (cur) { +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 128, 7)) + + cur; // should be all of D1 through D50 +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 128, 7)) + + if (cur.depth === 50) { +>cur.depth : Symbol(depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 74, 18), Decl(controlFlowLoopSelfReferentialAssignment.ts, 83, 19), Decl(controlFlowLoopSelfReferentialAssignment.ts, 84, 19), Decl(controlFlowLoopSelfReferentialAssignment.ts, 85, 19), Decl(controlFlowLoopSelfReferentialAssignment.ts, 86, 19) ... and 45 more) +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 128, 7)) +>depth : Symbol(depth, Decl(controlFlowLoopSelfReferentialAssignment.ts, 74, 18), Decl(controlFlowLoopSelfReferentialAssignment.ts, 83, 19), Decl(controlFlowLoopSelfReferentialAssignment.ts, 84, 19), Decl(controlFlowLoopSelfReferentialAssignment.ts, 85, 19), Decl(controlFlowLoopSelfReferentialAssignment.ts, 86, 19) ... and 45 more) + + cur; // should be D50, not never +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 128, 7)) + } + cur = cur.parent; +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 128, 7)) +>cur.parent : Symbol(parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 74, 28), Decl(controlFlowLoopSelfReferentialAssignment.ts, 83, 30), Decl(controlFlowLoopSelfReferentialAssignment.ts, 84, 30), Decl(controlFlowLoopSelfReferentialAssignment.ts, 85, 30), Decl(controlFlowLoopSelfReferentialAssignment.ts, 86, 30) ... and 45 more) +>cur : Symbol(cur, Decl(controlFlowLoopSelfReferentialAssignment.ts, 128, 7)) +>parent : Symbol(parent, Decl(controlFlowLoopSelfReferentialAssignment.ts, 74, 28), Decl(controlFlowLoopSelfReferentialAssignment.ts, 83, 30), Decl(controlFlowLoopSelfReferentialAssignment.ts, 84, 30), Decl(controlFlowLoopSelfReferentialAssignment.ts, 85, 30), Decl(controlFlowLoopSelfReferentialAssignment.ts, 86, 30) ... and 45 more) + } +} + diff --git a/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.types b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.types new file mode 100644 index 0000000000000..83988f55285ce --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.types @@ -0,0 +1,498 @@ +//// [tests/cases/conformance/controlFlow/controlFlowLoopSelfReferentialAssignment.ts] //// + +=== controlFlowLoopSelfReferentialAssignment.ts === +// Repro from #59715 + +declare class Child { parent: Parent | null; } +>Child : Child +>parent : Parent | null + +declare class Parent { parent: GrandParent | null; } +>Parent : Parent +>parent : GrandParent | null + +declare class GrandParent { parent: GreatGrandParent | null; } +>GrandParent : GrandParent +>parent : GreatGrandParent | null + +declare class GreatGrandParent { parent: null; } +>GreatGrandParent : GreatGrandParent +>parent : null + +declare const child: Child; +>child : Child + +function three() { +>three : () => void + + let currentParent: GreatGrandParent | GrandParent | Parent | null = child.parent; +>currentParent : GrandParent | GreatGrandParent | Parent | null +>child.parent : Parent | null +>child : Child +>parent : Parent | null + + while (currentParent) { +>currentParent : GrandParent | GreatGrandParent | Parent | null + + currentParent; // should be Parent | GrandParent | GreatGrandParent +>currentParent : GrandParent | GreatGrandParent | Parent + + currentParent = currentParent.parent; +>currentParent = currentParent.parent : GrandParent | GreatGrandParent | null +>currentParent : GrandParent | GreatGrandParent | Parent | null +>currentParent.parent : GrandParent | GreatGrandParent | null +>currentParent : GrandParent | GreatGrandParent | Parent +>parent : GrandParent | GreatGrandParent | null + } +} + +// Four levels: also wrong before #43183 (one transfer step short per nesting depth) + +declare class A1 { nom: "a1"; next(): A2 | null; } +>A1 : A1 +>nom : "a1" +>next : () => A2 | null + +declare class A2 { nom: "a2"; next(): A3 | null; } +>A2 : A2 +>nom : "a2" +>next : () => A3 | null + +declare class A3 { nom: "a3"; next(): A4 | null; } +>A3 : A3 +>nom : "a3" +>next : () => A4 | null + +declare class A4 { nom: "a4"; next(): null; } +>A4 : A4 +>nom : "a4" +>next : () => null + +type All = A1 | A2 | A3 | A4; +>All : All + +function four(a: A1) { +>four : (a: A1) => void +>a : A1 + + let p: All | null = a; +>p : All | null +>a : A1 + + while (p) { +>p : All | null + + p; // should be A1 | A2 | A3 | A4 +>p : All + + if (p.nom === "a4") { +>p.nom === "a4" : boolean +>p.nom : "a1" | "a2" | "a3" | "a4" +>p : All +>nom : "a1" | "a2" | "a3" | "a4" +>"a4" : "a4" + + p; // should be A4, not never +>p : A4 + } + p = p.next(); +>p = p.next() : A2 | A3 | A4 | null +>p : All | null +>p.next() : A2 | A3 | A4 | null +>p.next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) +>p : A1 | A2 | A3 | A4 +>next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) + } +} + +// for-statement form from the issue thread + +function forLoop(a: A1) { +>forLoop : (a: A1) => void +>a : A1 + + for (let q: All | null = a; q !== null; q = q.next()) { +>q : All | null +>a : A1 +>q !== null : boolean +>q : All | null +>q = q.next() : A2 | A3 | A4 | null +>q : All | null +>q.next() : A2 | A3 | A4 | null +>q.next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) +>q : All +>next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) + + q; // should be A1 | A2 | A3 | A4 +>q : All + } +} + +// Same chain via a property access reference, and soundness: this must be an error. + +declare class WithProp1 { parent: WithProp2 | null; prop: number; } +>WithProp1 : WithProp1 +>parent : WithProp2 | null +>prop : number + +declare class WithProp2 { parent: WithProp3 | null; prop: number; } +>WithProp2 : WithProp2 +>parent : WithProp3 | null +>prop : number + +declare class WithProp3 { parent: null; } +>WithProp3 : WithProp3 +>parent : null + +function unsound(start: WithProp1 | null) { +>unsound : (start: WithProp1 | null) => void +>start : WithProp1 | null + + let cur: WithProp1 | WithProp2 | WithProp3 | null = start; +>cur : WithProp1 | WithProp2 | WithProp3 | null +>start : WithProp1 | null + + while (cur) { +>cur : WithProp1 | WithProp2 | WithProp3 | null + + const n: number = cur.prop; // should be an error: WithProp3 has no prop +>n : number +>cur.prop : any +>cur : WithProp1 | WithProp2 | WithProp3 +>prop : any + + cur = cur.parent; +>cur = cur.parent : WithProp2 | WithProp3 | null +>cur : WithProp1 | WithProp2 | WithProp3 | null +>cur.parent : WithProp2 | WithProp3 | null +>cur : WithProp1 | WithProp2 | WithProp3 +>parent : WithProp2 | WithProp3 | null + } +} + +// Evolving array created in an outer loop and pushed in an inner loop. The fix must not +// iterate for auto-typed variables: doing so finalizes the evolving array and degrades it to any[]. + +declare const outer: number[][]; +>outer : number[][] + +function evolving() { +>evolving : () => number[] | undefined + + let result; +>result : any + + for (const list of outer) { +>list : number[] +>outer : number[][] + + result ||= []; +>result ||= [] : any +>result : any +>[] : never[] + + for (const n of list) result.push(n); +>n : number +>list : number[] +>result.push(n) : number +>result.push : (...items: any[]) => number +>result : any[] +>push : (...items: any[]) => number +>n : number + } + return result; // should be number[] | undefined +>result : number[] | undefined +} + +// Fifty levels deep. Each pass over the loop junction adds at least one constituent of the +// declared union, so the number of passes scales linearly with the depth of the chain, and +// the bound (union size + 1) guarantees convergence at any depth. + +declare class D1 { depth: 1; parent: D2 | null; } +>D1 : D1 +>depth : 1 +>parent : D2 | null + +declare class D2 { depth: 2; parent: D3 | null; } +>D2 : D2 +>depth : 2 +>parent : D3 | null + +declare class D3 { depth: 3; parent: D4 | null; } +>D3 : D3 +>depth : 3 +>parent : D4 | null + +declare class D4 { depth: 4; parent: D5 | null; } +>D4 : D4 +>depth : 4 +>parent : D5 | null + +declare class D5 { depth: 5; parent: D6 | null; } +>D5 : D5 +>depth : 5 +>parent : D6 | null + +declare class D6 { depth: 6; parent: D7 | null; } +>D6 : D6 +>depth : 6 +>parent : D7 | null + +declare class D7 { depth: 7; parent: D8 | null; } +>D7 : D7 +>depth : 7 +>parent : D8 | null + +declare class D8 { depth: 8; parent: D9 | null; } +>D8 : D8 +>depth : 8 +>parent : D9 | null + +declare class D9 { depth: 9; parent: D10 | null; } +>D9 : D9 +>depth : 9 +>parent : D10 | null + +declare class D10 { depth: 10; parent: D11 | null; } +>D10 : D10 +>depth : 10 +>parent : D11 | null + +declare class D11 { depth: 11; parent: D12 | null; } +>D11 : D11 +>depth : 11 +>parent : D12 | null + +declare class D12 { depth: 12; parent: D13 | null; } +>D12 : D12 +>depth : 12 +>parent : D13 | null + +declare class D13 { depth: 13; parent: D14 | null; } +>D13 : D13 +>depth : 13 +>parent : D14 | null + +declare class D14 { depth: 14; parent: D15 | null; } +>D14 : D14 +>depth : 14 +>parent : D15 | null + +declare class D15 { depth: 15; parent: D16 | null; } +>D15 : D15 +>depth : 15 +>parent : D16 | null + +declare class D16 { depth: 16; parent: D17 | null; } +>D16 : D16 +>depth : 16 +>parent : D17 | null + +declare class D17 { depth: 17; parent: D18 | null; } +>D17 : D17 +>depth : 17 +>parent : D18 | null + +declare class D18 { depth: 18; parent: D19 | null; } +>D18 : D18 +>depth : 18 +>parent : D19 | null + +declare class D19 { depth: 19; parent: D20 | null; } +>D19 : D19 +>depth : 19 +>parent : D20 | null + +declare class D20 { depth: 20; parent: D21 | null; } +>D20 : D20 +>depth : 20 +>parent : D21 | null + +declare class D21 { depth: 21; parent: D22 | null; } +>D21 : D21 +>depth : 21 +>parent : D22 | null + +declare class D22 { depth: 22; parent: D23 | null; } +>D22 : D22 +>depth : 22 +>parent : D23 | null + +declare class D23 { depth: 23; parent: D24 | null; } +>D23 : D23 +>depth : 23 +>parent : D24 | null + +declare class D24 { depth: 24; parent: D25 | null; } +>D24 : D24 +>depth : 24 +>parent : D25 | null + +declare class D25 { depth: 25; parent: D26 | null; } +>D25 : D25 +>depth : 25 +>parent : D26 | null + +declare class D26 { depth: 26; parent: D27 | null; } +>D26 : D26 +>depth : 26 +>parent : D27 | null + +declare class D27 { depth: 27; parent: D28 | null; } +>D27 : D27 +>depth : 27 +>parent : D28 | null + +declare class D28 { depth: 28; parent: D29 | null; } +>D28 : D28 +>depth : 28 +>parent : D29 | null + +declare class D29 { depth: 29; parent: D30 | null; } +>D29 : D29 +>depth : 29 +>parent : D30 | null + +declare class D30 { depth: 30; parent: D31 | null; } +>D30 : D30 +>depth : 30 +>parent : D31 | null + +declare class D31 { depth: 31; parent: D32 | null; } +>D31 : D31 +>depth : 31 +>parent : D32 | null + +declare class D32 { depth: 32; parent: D33 | null; } +>D32 : D32 +>depth : 32 +>parent : D33 | null + +declare class D33 { depth: 33; parent: D34 | null; } +>D33 : D33 +>depth : 33 +>parent : D34 | null + +declare class D34 { depth: 34; parent: D35 | null; } +>D34 : D34 +>depth : 34 +>parent : D35 | null + +declare class D35 { depth: 35; parent: D36 | null; } +>D35 : D35 +>depth : 35 +>parent : D36 | null + +declare class D36 { depth: 36; parent: D37 | null; } +>D36 : D36 +>depth : 36 +>parent : D37 | null + +declare class D37 { depth: 37; parent: D38 | null; } +>D37 : D37 +>depth : 37 +>parent : D38 | null + +declare class D38 { depth: 38; parent: D39 | null; } +>D38 : D38 +>depth : 38 +>parent : D39 | null + +declare class D39 { depth: 39; parent: D40 | null; } +>D39 : D39 +>depth : 39 +>parent : D40 | null + +declare class D40 { depth: 40; parent: D41 | null; } +>D40 : D40 +>depth : 40 +>parent : D41 | null + +declare class D41 { depth: 41; parent: D42 | null; } +>D41 : D41 +>depth : 41 +>parent : D42 | null + +declare class D42 { depth: 42; parent: D43 | null; } +>D42 : D42 +>depth : 42 +>parent : D43 | null + +declare class D43 { depth: 43; parent: D44 | null; } +>D43 : D43 +>depth : 43 +>parent : D44 | null + +declare class D44 { depth: 44; parent: D45 | null; } +>D44 : D44 +>depth : 44 +>parent : D45 | null + +declare class D45 { depth: 45; parent: D46 | null; } +>D45 : D45 +>depth : 45 +>parent : D46 | null + +declare class D46 { depth: 46; parent: D47 | null; } +>D46 : D46 +>depth : 46 +>parent : D47 | null + +declare class D47 { depth: 47; parent: D48 | null; } +>D47 : D47 +>depth : 47 +>parent : D48 | null + +declare class D48 { depth: 48; parent: D49 | null; } +>D48 : D48 +>depth : 48 +>parent : D49 | null + +declare class D49 { depth: 49; parent: D50 | null; } +>D49 : D49 +>depth : 49 +>parent : D50 | null + +declare class D50 { depth: 50; parent: null; } +>D50 : D50 +>depth : 50 +>parent : null + +type Deep = D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9 | D10 | D11 | D12 | D13 | D14 | D15 | D16 | D17 | D18 | D19 | D20 | D21 | D22 | D23 | D24 | D25 | D26 | D27 | D28 | D29 | D30 | D31 | D32 | D33 | D34 | D35 | D36 | D37 | D38 | D39 | D40 | D41 | D42 | D43 | D44 | D45 | D46 | D47 | D48 | D49 | D50; +>Deep : Deep + +declare const deepStart: D1 | null; +>deepStart : D1 | null + +function fifty() { +>fifty : () => void + + let cur: Deep | null = deepStart; +>cur : Deep | null +>deepStart : D1 | null + + while (cur) { +>cur : Deep | null + + cur; // should be all of D1 through D50 +>cur : Deep + + if (cur.depth === 50) { +>cur.depth === 50 : boolean +>cur.depth : 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 +>cur : Deep +>depth : 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 +>50 : 50 + + cur; // should be D50, not never +>cur : D50 + } + cur = cur.parent; +>cur = cur.parent : D10 | D11 | D12 | D13 | D14 | D15 | D16 | D17 | D18 | D19 | D2 | D20 | D21 | D22 | D23 | D24 | D25 | D26 | D27 | D28 | D29 | D3 | D30 | D31 | D32 | D33 | D34 | D35 | D36 | D37 | D38 | D39 | D4 | D40 | D41 | D42 | D43 | D44 | D45 | D46 | D47 | D48 | D49 | D5 | D50 | D6 | D7 | D8 | D9 | null +>cur : Deep | null +>cur.parent : D10 | D11 | D12 | D13 | D14 | D15 | D16 | D17 | D18 | D19 | D2 | D20 | D21 | D22 | D23 | D24 | D25 | D26 | D27 | D28 | D29 | D3 | D30 | D31 | D32 | D33 | D34 | D35 | D36 | D37 | D38 | D39 | D4 | D40 | D41 | D42 | D43 | D44 | D45 | D46 | D47 | D48 | D49 | D5 | D50 | D6 | D7 | D8 | D9 | null +>cur : D1 | D10 | D11 | D12 | D13 | D14 | D15 | D16 | D17 | D18 | D19 | D2 | D20 | D21 | D22 | D23 | D24 | D25 | D26 | D27 | D28 | D29 | D3 | D30 | D31 | D32 | D33 | D34 | D35 | D36 | D37 | D38 | D39 | D4 | D40 | D41 | D42 | D43 | D44 | D45 | D46 | D47 | D48 | D49 | D5 | D50 | D6 | D7 | D8 | D9 +>parent : D10 | D11 | D12 | D13 | D14 | D15 | D16 | D17 | D18 | D19 | D2 | D20 | D21 | D22 | D23 | D24 | D25 | D26 | D27 | D28 | D29 | D3 | D30 | D31 | D32 | D33 | D34 | D35 | D36 | D37 | D38 | D39 | D4 | D40 | D41 | D42 | D43 | D44 | D45 | D46 | D47 | D48 | D49 | D5 | D50 | D6 | D7 | D8 | D9 | null + } +} + diff --git a/tsc/testdata/tests/cases/conformance/controlFlow/controlFlowLoopSelfReferentialAssignment.ts b/tsc/testdata/tests/cases/conformance/controlFlow/controlFlowLoopSelfReferentialAssignment.ts new file mode 100644 index 0000000000000..6aacf508297db --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/controlFlow/controlFlowLoopSelfReferentialAssignment.ts @@ -0,0 +1,140 @@ +// @strict: true +// @noEmit: true + +// Repro from #59715 + +declare class Child { parent: Parent | null; } +declare class Parent { parent: GrandParent | null; } +declare class GrandParent { parent: GreatGrandParent | null; } +declare class GreatGrandParent { parent: null; } +declare const child: Child; + +function three() { + let currentParent: GreatGrandParent | GrandParent | Parent | null = child.parent; + while (currentParent) { + currentParent; // should be Parent | GrandParent | GreatGrandParent + currentParent = currentParent.parent; + } +} + +// Four levels: also wrong before #43183 (one transfer step short per nesting depth) + +declare class A1 { nom: "a1"; next(): A2 | null; } +declare class A2 { nom: "a2"; next(): A3 | null; } +declare class A3 { nom: "a3"; next(): A4 | null; } +declare class A4 { nom: "a4"; next(): null; } +type All = A1 | A2 | A3 | A4; + +function four(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4 + if (p.nom === "a4") { + p; // should be A4, not never + } + p = p.next(); + } +} + +// for-statement form from the issue thread + +function forLoop(a: A1) { + for (let q: All | null = a; q !== null; q = q.next()) { + q; // should be A1 | A2 | A3 | A4 + } +} + +// Same chain via a property access reference, and soundness: this must be an error. + +declare class WithProp1 { parent: WithProp2 | null; prop: number; } +declare class WithProp2 { parent: WithProp3 | null; prop: number; } +declare class WithProp3 { parent: null; } + +function unsound(start: WithProp1 | null) { + let cur: WithProp1 | WithProp2 | WithProp3 | null = start; + while (cur) { + const n: number = cur.prop; // should be an error: WithProp3 has no prop + cur = cur.parent; + } +} + +// Evolving array created in an outer loop and pushed in an inner loop. The fix must not +// iterate for auto-typed variables: doing so finalizes the evolving array and degrades it to any[]. + +declare const outer: number[][]; +function evolving() { + let result; + for (const list of outer) { + result ||= []; + for (const n of list) result.push(n); + } + return result; // should be number[] | undefined +} + +// Fifty levels deep. Each pass over the loop junction adds at least one constituent of the +// declared union, so the number of passes scales linearly with the depth of the chain, and +// the bound (union size + 1) guarantees convergence at any depth. + +declare class D1 { depth: 1; parent: D2 | null; } +declare class D2 { depth: 2; parent: D3 | null; } +declare class D3 { depth: 3; parent: D4 | null; } +declare class D4 { depth: 4; parent: D5 | null; } +declare class D5 { depth: 5; parent: D6 | null; } +declare class D6 { depth: 6; parent: D7 | null; } +declare class D7 { depth: 7; parent: D8 | null; } +declare class D8 { depth: 8; parent: D9 | null; } +declare class D9 { depth: 9; parent: D10 | null; } +declare class D10 { depth: 10; parent: D11 | null; } +declare class D11 { depth: 11; parent: D12 | null; } +declare class D12 { depth: 12; parent: D13 | null; } +declare class D13 { depth: 13; parent: D14 | null; } +declare class D14 { depth: 14; parent: D15 | null; } +declare class D15 { depth: 15; parent: D16 | null; } +declare class D16 { depth: 16; parent: D17 | null; } +declare class D17 { depth: 17; parent: D18 | null; } +declare class D18 { depth: 18; parent: D19 | null; } +declare class D19 { depth: 19; parent: D20 | null; } +declare class D20 { depth: 20; parent: D21 | null; } +declare class D21 { depth: 21; parent: D22 | null; } +declare class D22 { depth: 22; parent: D23 | null; } +declare class D23 { depth: 23; parent: D24 | null; } +declare class D24 { depth: 24; parent: D25 | null; } +declare class D25 { depth: 25; parent: D26 | null; } +declare class D26 { depth: 26; parent: D27 | null; } +declare class D27 { depth: 27; parent: D28 | null; } +declare class D28 { depth: 28; parent: D29 | null; } +declare class D29 { depth: 29; parent: D30 | null; } +declare class D30 { depth: 30; parent: D31 | null; } +declare class D31 { depth: 31; parent: D32 | null; } +declare class D32 { depth: 32; parent: D33 | null; } +declare class D33 { depth: 33; parent: D34 | null; } +declare class D34 { depth: 34; parent: D35 | null; } +declare class D35 { depth: 35; parent: D36 | null; } +declare class D36 { depth: 36; parent: D37 | null; } +declare class D37 { depth: 37; parent: D38 | null; } +declare class D38 { depth: 38; parent: D39 | null; } +declare class D39 { depth: 39; parent: D40 | null; } +declare class D40 { depth: 40; parent: D41 | null; } +declare class D41 { depth: 41; parent: D42 | null; } +declare class D42 { depth: 42; parent: D43 | null; } +declare class D43 { depth: 43; parent: D44 | null; } +declare class D44 { depth: 44; parent: D45 | null; } +declare class D45 { depth: 45; parent: D46 | null; } +declare class D46 { depth: 46; parent: D47 | null; } +declare class D47 { depth: 47; parent: D48 | null; } +declare class D48 { depth: 48; parent: D49 | null; } +declare class D49 { depth: 49; parent: D50 | null; } +declare class D50 { depth: 50; parent: null; } +type Deep = D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9 | D10 | D11 | D12 | D13 | D14 | D15 | D16 | D17 | D18 | D19 | D20 | D21 | D22 | D23 | D24 | D25 | D26 | D27 | D28 | D29 | D30 | D31 | D32 | D33 | D34 | D35 | D36 | D37 | D38 | D39 | D40 | D41 | D42 | D43 | D44 | D45 | D46 | D47 | D48 | D49 | D50; +declare const deepStart: D1 | null; + +function fifty() { + let cur: Deep | null = deepStart; + while (cur) { + cur; // should be all of D1 through D50 + if (cur.depth === 50) { + cur; // should be D50, not never + } + cur = cur.parent; + } +} From cb298e9a2b0ac6dcc7f04305d2d58afcad5e703b Mon Sep 17 00:00:00 2001 From: milkcask <1572863+milkcask@users.noreply.github.com> Date: Wed, 16 Sep 2026 05:46:24 +0100 Subject: [PATCH 2/2] Skip caching nested loop types derived from a partial union. Add inner loop test. --- tsc/internal/checker/flow.go | 10 + ...owLoopSelfReferentialAssignment.errors.txt | 64 ++++++ ...lFlowLoopSelfReferentialAssignment.symbols | 180 +++++++++++++++ ...rolFlowLoopSelfReferentialAssignment.types | 205 ++++++++++++++++++ ...ontrolFlowLoopSelfReferentialAssignment.ts | 64 ++++++ 5 files changed, 523 insertions(+) diff --git a/tsc/internal/checker/flow.go b/tsc/internal/checker/flow.go index 5f2c2fb6ccbaa..a2021ceb9fddf 100644 --- a/tsc/internal/checker/flow.go +++ b/tsc/internal/checker/flow.go @@ -1372,6 +1372,7 @@ func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowT maxPasses = len(f.declaredType.Types()) + 1 } sharedFlowsAtEntry := len(c.sharedFlows) + flowLoopStackDepthAtEntry := len(c.flowLoopStack) antecedentTypes := make([]*Type, 0, 4) subtypeReduction := false var firstAntecedentType FlowType @@ -1438,6 +1439,15 @@ func (c *Checker) getTypeAtFlowLoopLabel(f *FlowState, flow *ast.FlowNode) FlowT if firstAntecedentType.incomplete { return c.newFlowType(result, true /*incomplete*/) } + // If an enclosing loop junction's in-process types were observed while this junction was + // being computed, the result may derive from a partial union whose incomplete marker was + // lost across an expression boundary. Don't cache it: the enclosing junction may retry, + // and once it completes a later query recomputes this junction from the final type. + for i := range c.flowLoopStack[:flowLoopStackDepthAtEntry] { + if c.flowLoopStack[i].reentered { + return FlowType{t: result} + } + } c.flowLoopCache[key] = result return FlowType{t: result} } diff --git a/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.errors.txt b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.errors.txt index cb6ed5bbde743..5c7e0861bd683 100644 --- a/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.errors.txt +++ b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.errors.txt @@ -143,4 +143,68 @@ controlFlowLoopSelfReferentialAssignment.ts(53,31): error TS2339: Property 'prop cur = cur.parent; } } + + // An inner loop between the self-assignment and the back edge. The inner loop junction is + // reached while the outer one is still being computed and must not keep a cached type derived + // from the outer junction's partial union. + + function innerLoopAfter(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4 + p = p.next(); + for (let i = 0; i < 1; i++) {} + } + } + + function innerLoopReads(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4 + p = p.next(); + for (let i = 0; i < 1; i++) { + p; // should be A2 | A3 | A4 | null + } + } + } + + function innerWhileAfter(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4 + p = p.next(); + while (Math.random()) {} + } + } + + // Three loop levels. Cache entries for both inner junctions are created while the outer + // junction is in progress and must be invalidated on the outer retry. + + function threeLevelsAfter(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4, alias All + p = p.next(); + for (let i = 0; i < 1; i++) { + for (let j = 0; j < 1; j++) { + p; // should be A2 | A3 | A4 | null + } + } + } + } + + function threeLevelsInnermostAssigns(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4, alias All + for (let i = 0; i < 1; i++) { + for (let j = 0; j < 1; j++) { + if (p) { // p here is All | null: this loop's own back edge brings the null back + p = p.next(); // the target shows the declared All | null; the assigned value is A2 | A3 | A4 | null + p; // should be A2 | A3 | A4 | null, the narrowed type after the assignment + } + } + } + } + } \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.symbols b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.symbols index f05ed70d4d914..9e85d1f3f3532 100644 --- a/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.symbols +++ b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.symbols @@ -604,3 +604,183 @@ function fifty() { } } +// An inner loop between the self-assignment and the back edge. The inner loop junction is +// reached while the outer one is still being computed and must not keep a cached type derived +// from the outer junction's partial union. + +function innerLoopAfter(a: A1) { +>innerLoopAfter : Symbol(innerLoopAfter, Decl(controlFlowLoopSelfReferentialAssignment.ts, 136, 1)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 142, 24)) +>A1 : Symbol(A1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 14, 1)) + + let p: All | null = a; +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 143, 7)) +>All : Symbol(All, Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 45)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 142, 24)) + + while (p) { +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 143, 7)) + + p; // should be A1 | A2 | A3 | A4 +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 143, 7)) + + p = p.next(); +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 143, 7)) +>p.next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 143, 7)) +>next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) + + for (let i = 0; i < 1; i++) {} +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 147, 16)) +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 147, 16)) +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 147, 16)) + } +} + +function innerLoopReads(a: A1) { +>innerLoopReads : Symbol(innerLoopReads, Decl(controlFlowLoopSelfReferentialAssignment.ts, 149, 1)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 151, 24)) +>A1 : Symbol(A1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 14, 1)) + + let p: All | null = a; +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 152, 7)) +>All : Symbol(All, Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 45)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 151, 24)) + + while (p) { +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 152, 7)) + + p; // should be A1 | A2 | A3 | A4 +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 152, 7)) + + p = p.next(); +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 152, 7)) +>p.next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 152, 7)) +>next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) + + for (let i = 0; i < 1; i++) { +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 156, 16)) +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 156, 16)) +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 156, 16)) + + p; // should be A2 | A3 | A4 | null +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 152, 7)) + } + } +} + +function innerWhileAfter(a: A1) { +>innerWhileAfter : Symbol(innerWhileAfter, Decl(controlFlowLoopSelfReferentialAssignment.ts, 160, 1)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 162, 25)) +>A1 : Symbol(A1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 14, 1)) + + let p: All | null = a; +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 163, 7)) +>All : Symbol(All, Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 45)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 162, 25)) + + while (p) { +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 163, 7)) + + p; // should be A1 | A2 | A3 | A4 +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 163, 7)) + + p = p.next(); +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 163, 7)) +>p.next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 163, 7)) +>next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) + + while (Math.random()) {} +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.float16.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + } +} + +// Three loop levels. Cache entries for both inner junctions are created while the outer +// junction is in progress and must be invalidated on the outer retry. + +function threeLevelsAfter(a: A1) { +>threeLevelsAfter : Symbol(threeLevelsAfter, Decl(controlFlowLoopSelfReferentialAssignment.ts, 169, 1)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 174, 26)) +>A1 : Symbol(A1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 14, 1)) + + let p: All | null = a; +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 175, 7)) +>All : Symbol(All, Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 45)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 174, 26)) + + while (p) { +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 175, 7)) + + p; // should be A1 | A2 | A3 | A4, alias All +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 175, 7)) + + p = p.next(); +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 175, 7)) +>p.next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 175, 7)) +>next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) + + for (let i = 0; i < 1; i++) { +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 179, 16)) +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 179, 16)) +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 179, 16)) + + for (let j = 0; j < 1; j++) { +>j : Symbol(j, Decl(controlFlowLoopSelfReferentialAssignment.ts, 180, 20)) +>j : Symbol(j, Decl(controlFlowLoopSelfReferentialAssignment.ts, 180, 20)) +>j : Symbol(j, Decl(controlFlowLoopSelfReferentialAssignment.ts, 180, 20)) + + p; // should be A2 | A3 | A4 | null +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 175, 7)) + } + } + } +} + +function threeLevelsInnermostAssigns(a: A1) { +>threeLevelsInnermostAssigns : Symbol(threeLevelsInnermostAssigns, Decl(controlFlowLoopSelfReferentialAssignment.ts, 185, 1)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 187, 37)) +>A1 : Symbol(A1, Decl(controlFlowLoopSelfReferentialAssignment.ts, 14, 1)) + + let p: All | null = a; +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 188, 7)) +>All : Symbol(All, Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 45)) +>a : Symbol(a, Decl(controlFlowLoopSelfReferentialAssignment.ts, 187, 37)) + + while (p) { +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 188, 7)) + + p; // should be A1 | A2 | A3 | A4, alias All +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 188, 7)) + + for (let i = 0; i < 1; i++) { +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 191, 16)) +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 191, 16)) +>i : Symbol(i, Decl(controlFlowLoopSelfReferentialAssignment.ts, 191, 16)) + + for (let j = 0; j < 1; j++) { +>j : Symbol(j, Decl(controlFlowLoopSelfReferentialAssignment.ts, 192, 20)) +>j : Symbol(j, Decl(controlFlowLoopSelfReferentialAssignment.ts, 192, 20)) +>j : Symbol(j, Decl(controlFlowLoopSelfReferentialAssignment.ts, 192, 20)) + + if (p) { // p here is All | null: this loop's own back edge brings the null back +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 188, 7)) + + p = p.next(); // the target shows the declared All | null; the assigned value is A2 | A3 | A4 | null +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 188, 7)) +>p.next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 188, 7)) +>next : Symbol(next, Decl(controlFlowLoopSelfReferentialAssignment.ts, 18, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 19, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 20, 29), Decl(controlFlowLoopSelfReferentialAssignment.ts, 21, 29)) + + p; // should be A2 | A3 | A4 | null, the narrowed type after the assignment +>p : Symbol(p, Decl(controlFlowLoopSelfReferentialAssignment.ts, 188, 7)) + } + } + } + } +} + diff --git a/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.types b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.types index 83988f55285ce..dd43b56d7e671 100644 --- a/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.types +++ b/tsc/testdata/baselines/reference/conformance/controlFlowLoopSelfReferentialAssignment.types @@ -496,3 +496,208 @@ function fifty() { } } +// An inner loop between the self-assignment and the back edge. The inner loop junction is +// reached while the outer one is still being computed and must not keep a cached type derived +// from the outer junction's partial union. + +function innerLoopAfter(a: A1) { +>innerLoopAfter : (a: A1) => void +>a : A1 + + let p: All | null = a; +>p : All | null +>a : A1 + + while (p) { +>p : All | null + + p; // should be A1 | A2 | A3 | A4 +>p : All + + p = p.next(); +>p = p.next() : A2 | A3 | A4 | null +>p : All | null +>p.next() : A2 | A3 | A4 | null +>p.next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) +>p : All +>next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) + + for (let i = 0; i < 1; i++) {} +>i : number +>0 : 0 +>i < 1 : boolean +>i : number +>1 : 1 +>i++ : number +>i : number + } +} + +function innerLoopReads(a: A1) { +>innerLoopReads : (a: A1) => void +>a : A1 + + let p: All | null = a; +>p : All | null +>a : A1 + + while (p) { +>p : All | null + + p; // should be A1 | A2 | A3 | A4 +>p : All + + p = p.next(); +>p = p.next() : A2 | A3 | A4 | null +>p : All | null +>p.next() : A2 | A3 | A4 | null +>p.next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) +>p : All +>next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) + + for (let i = 0; i < 1; i++) { +>i : number +>0 : 0 +>i < 1 : boolean +>i : number +>1 : 1 +>i++ : number +>i : number + + p; // should be A2 | A3 | A4 | null +>p : A2 | A3 | A4 | null + } + } +} + +function innerWhileAfter(a: A1) { +>innerWhileAfter : (a: A1) => void +>a : A1 + + let p: All | null = a; +>p : All | null +>a : A1 + + while (p) { +>p : All | null + + p; // should be A1 | A2 | A3 | A4 +>p : All + + p = p.next(); +>p = p.next() : A2 | A3 | A4 | null +>p : All | null +>p.next() : A2 | A3 | A4 | null +>p.next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) +>p : All +>next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) + + while (Math.random()) {} +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number + } +} + +// Three loop levels. Cache entries for both inner junctions are created while the outer +// junction is in progress and must be invalidated on the outer retry. + +function threeLevelsAfter(a: A1) { +>threeLevelsAfter : (a: A1) => void +>a : A1 + + let p: All | null = a; +>p : All | null +>a : A1 + + while (p) { +>p : All | null + + p; // should be A1 | A2 | A3 | A4, alias All +>p : All + + p = p.next(); +>p = p.next() : A2 | A3 | A4 | null +>p : All | null +>p.next() : A2 | A3 | A4 | null +>p.next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) +>p : All +>next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) + + for (let i = 0; i < 1; i++) { +>i : number +>0 : 0 +>i < 1 : boolean +>i : number +>1 : 1 +>i++ : number +>i : number + + for (let j = 0; j < 1; j++) { +>j : number +>0 : 0 +>j < 1 : boolean +>j : number +>1 : 1 +>j++ : number +>j : number + + p; // should be A2 | A3 | A4 | null +>p : A2 | A3 | A4 | null + } + } + } +} + +function threeLevelsInnermostAssigns(a: A1) { +>threeLevelsInnermostAssigns : (a: A1) => void +>a : A1 + + let p: All | null = a; +>p : All | null +>a : A1 + + while (p) { +>p : All | null + + p; // should be A1 | A2 | A3 | A4, alias All +>p : All + + for (let i = 0; i < 1; i++) { +>i : number +>0 : 0 +>i < 1 : boolean +>i : number +>1 : 1 +>i++ : number +>i : number + + for (let j = 0; j < 1; j++) { +>j : number +>0 : 0 +>j < 1 : boolean +>j : number +>1 : 1 +>j++ : number +>j : number + + if (p) { // p here is All | null: this loop's own back edge brings the null back +>p : All | null + + p = p.next(); // the target shows the declared All | null; the assigned value is A2 | A3 | A4 | null +>p = p.next() : A2 | A3 | A4 | null +>p : All | null +>p.next() : A2 | A3 | A4 | null +>p.next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) +>p : All +>next : (() => A2 | null) | (() => A3 | null) | (() => A4 | null) | (() => null) + + p; // should be A2 | A3 | A4 | null, the narrowed type after the assignment +>p : A2 | A3 | A4 | null + } + } + } + } +} + diff --git a/tsc/testdata/tests/cases/conformance/controlFlow/controlFlowLoopSelfReferentialAssignment.ts b/tsc/testdata/tests/cases/conformance/controlFlow/controlFlowLoopSelfReferentialAssignment.ts index 6aacf508297db..0cf2754a5cfb4 100644 --- a/tsc/testdata/tests/cases/conformance/controlFlow/controlFlowLoopSelfReferentialAssignment.ts +++ b/tsc/testdata/tests/cases/conformance/controlFlow/controlFlowLoopSelfReferentialAssignment.ts @@ -138,3 +138,67 @@ function fifty() { cur = cur.parent; } } + +// An inner loop between the self-assignment and the back edge. The inner loop junction is +// reached while the outer one is still being computed and must not keep a cached type derived +// from the outer junction's partial union. + +function innerLoopAfter(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4 + p = p.next(); + for (let i = 0; i < 1; i++) {} + } +} + +function innerLoopReads(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4 + p = p.next(); + for (let i = 0; i < 1; i++) { + p; // should be A2 | A3 | A4 | null + } + } +} + +function innerWhileAfter(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4 + p = p.next(); + while (Math.random()) {} + } +} + +// Three loop levels. Cache entries for both inner junctions are created while the outer +// junction is in progress and must be invalidated on the outer retry. + +function threeLevelsAfter(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4, alias All + p = p.next(); + for (let i = 0; i < 1; i++) { + for (let j = 0; j < 1; j++) { + p; // should be A2 | A3 | A4 | null + } + } + } +} + +function threeLevelsInnermostAssigns(a: A1) { + let p: All | null = a; + while (p) { + p; // should be A1 | A2 | A3 | A4, alias All + for (let i = 0; i < 1; i++) { + for (let j = 0; j < 1; j++) { + if (p) { // p here is All | null: this loop's own back edge brings the null back + p = p.next(); // the target shows the declared All | null; the assigned value is A2 | A3 | A4 | null + p; // should be A2 | A3 | A4 | null, the narrowed type after the assignment + } + } + } + } +}