Skip to content

Commit 8c62572

Browse files
committed
Keep results a cycle yield computes from scratch
When a query enters a cycle somewhere other than its program-order entry, the cycle yields to its first member and recomputes it with the resolutions below hidden. Two things made that recomputation differ from computing the member first: - The reverse mapping stacks, contextual types and inference contexts of the hidden resolutions stayed visible, so the recomputation saw contexts that a computation from scratch never has. A yield now hides them along with the resolutions and restores them when it ends. - Results were kept as cycle participants only by comparing the outermost cycle head in progress against the resolution. A cycle headed below the yield, which the yield cannot observe, caused the yield root and the hidden re-entries of itself to be discarded, so the member was computed again and genuinely re-entered itself. Each type resolution now tracks the lowest cycle head it observed, and a result is kept when what it observed was entered from scratch: by the outer resolution of itself, by the yield it is the root of, or by the outermost measurement of variances. (seen in circularScopeThunkReverseMapped)
1 parent 6e6a287 commit 8c62572

6 files changed

Lines changed: 101 additions & 82 deletions

‎tsc/internal/checker/checker.go‎

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,7 @@ type Checker struct {
708708
circularityMark int // Length of discard log when the cycle in progress was detected
709709
innerCycles []innerCycle // Cycles detected inside the one in progress; see extendCircularity
710710
flowDepthFailures map[FlowLoopKey]int // Largest depth budget known to be too small for a flow walk from a node; see getTypeAtFlowNode
711+
observedHead int32 // 1 + index in resolutions of the lowest head of a cycle observed by the innermost type resolution in progress, or 0
711712
cycleParticipants []TypeResolution // Computations that observed a cycle in progress
712713
effectsMarkDepth int // 1 + resolution index of the innermost effects mark that may be discarded, or 0
713714
varianceStack []VarianceStackEntry
@@ -20335,9 +20336,20 @@ type cycleYield struct {
2033520336
savedResolutionStart int
2033620337
savedVisibleStart int
2033720338
savedDepths [3]depthLimit
20339+
savedReverseMapped reverseMappedState
20340+
savedContextual []ContextualInfo
20341+
savedInference []InferenceContextInfo
20342+
floor int
2033820343
shadowed []shadowedResolving
2033920344
}
2034020345

20346+
type reverseMappedState struct {
20347+
sourceStack []*Type
20348+
targetStack []*Type
20349+
expandingFlags ExpandingFlags
20350+
start int32
20351+
}
20352+
2034120353
type shadowedResolving struct {
2034220354
key memoKey
2034320355
index int32
@@ -20521,7 +20533,15 @@ func (c *Checker) beginCycleYield(target TypeSystemEntity, propertyName TypeSyst
2052120533
savedResolutionStart: c.resolutionStart,
2052220534
savedVisibleStart: c.visibleStart,
2052320535
savedDepths: [3]depthLimit{c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth},
20536+
savedReverseMapped: reverseMappedState{c.reverseMappedSourceStack, c.reverseMappedTargetStack, c.reverseExpandingFlags, c.reverseMappedStart},
20537+
savedContextual: c.contextualInfos,
20538+
savedInference: c.inferenceContextInfos,
20539+
floor: floor,
2052420540
})
20541+
// The contexts the hidden resolutions entered are hidden too, so that the
20542+
// recomputation is from scratch.
20543+
c.reverseMappedSourceStack, c.reverseMappedTargetStack, c.reverseExpandingFlags, c.reverseMappedStart = nil, nil, 0, 0
20544+
c.contextualInfos, c.inferenceContextInfos = nil, nil
2052520545
c.resolutionStart = n
2052620546
c.visibleStart = n
2052720547
c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth = depthLimit{}, depthLimit{}, depthLimit{}
@@ -20532,6 +20552,9 @@ func (c *Checker) endCycleYield() {
2053220552
c.resolutionStart = y.savedResolutionStart
2053320553
c.visibleStart = y.savedVisibleStart
2053420554
c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth = y.savedDepths[0], y.savedDepths[1], y.savedDepths[2]
20555+
r := y.savedReverseMapped
20556+
c.reverseMappedSourceStack, c.reverseMappedTargetStack, c.reverseExpandingFlags, c.reverseMappedStart = r.sourceStack, r.targetStack, r.expandingFlags, r.start
20557+
c.contextualInfos, c.inferenceContextInfos = y.savedContextual, y.savedInference
2053520558
for _, sh := range y.shadowed {
2053620559
c.resolving.set(sh.key, sh.index)
2053720560
}
@@ -20797,6 +20820,42 @@ func (c *Checker) markTypeResolutionCircular(start int) {
2079720820
c.markCircular(start)
2079820821
}
2079920822

20823+
// keepsResultInCycle reports whether the type resolution at index n, which
20824+
// observed the cycles whose lowest head is observed (see Checker.observedHead),
20825+
// computes what it computes first from scratch although a cycle is in
20826+
// progress, so that its result is kept as a participant of the cycle:
20827+
// - The resolution re-entered a hidden resolution of itself, which a
20828+
// computation from scratch of the outer one always does, and what it
20829+
// observed in progress was all entered by the outer one, so its result is
20830+
// what the outer one computes first from scratch.
20831+
// - The resolution is the one a cycle yield computes again from scratch,
20832+
// with everything below it hidden, and it observed nothing hidden.
20833+
// - The resolution is inside the outermost measurement of variances, which
20834+
// hides what is below it and is measured from the canonical entry of its
20835+
// circularity, so what it computes is the same whatever led to it, and it
20836+
// only observed its incomplete variances. Computing it first from scratch
20837+
// reaches the measurement and computes it again inside, the same way.
20838+
func (c *Checker) keepsResultInCycle(n int, f *resolution, observed int32) bool {
20839+
r := &f.typeResolution
20840+
if r.outerSelf != 0 && observed != 0 && observed >= r.outerSelf && int(observed) <= n {
20841+
return true
20842+
}
20843+
if len(c.cycleYields) != 0 {
20844+
y := &c.cycleYields[len(c.cycleYields)-1]
20845+
if n >= y.floor && int(f.prevTypeResolution) <= y.floor && (observed == 0 || int(observed) > y.floor) {
20846+
return true
20847+
}
20848+
}
20849+
return c.observedOnlyOutermostMeasurement(n)
20850+
}
20851+
20852+
func minObservedHead(a int32, b int32) int32 {
20853+
if a == 0 || b != 0 && b < a {
20854+
return b
20855+
}
20856+
return a
20857+
}
20858+
2080020859
// observedOnlyOutermostMeasurement reports whether the cycle in progress is
2080120860
// the one headed by the outermost measurement of variances, which the
2080220861
// resolution at index n is inside, and no other.
@@ -20855,21 +20914,9 @@ func (c *Checker) popTypeResolution() bool {
2085520914
debug.Assert(c.typeResolutionTop == n+1, "resolutions left above a type resolution")
2085620915
f := &c.resolutions[n]
2085720916
result := f.typeResolution.result
20858-
if r := &f.typeResolution; r.outerSelf != 0 && result && c.circularityStart != 0 && c.circularityStart <= n && c.circularityStart >= int(r.outerSelf) && !r.participant {
20859-
// The resolution re-entered a hidden resolution of itself, which a
20860-
// computation from scratch of the outer one always does, and what it
20861-
// observed in progress was all entered by the outer one, so its result
20862-
// is what the outer one computes first from scratch: the outer one
20863-
// keeps it, as must the cycle.
20864-
r.participant = true
20865-
c.cycleParticipants = append(c.cycleParticipants, *r)
20866-
} else if r := &f.typeResolution; result && !r.participant && c.observedOnlyOutermostMeasurement(n) {
20867-
// The outermost measurement of variances hides what is below it and
20868-
// is measured from the canonical entry of its circularity, so what
20869-
// it computes is the same whatever led to it. This resolution only
20870-
// observed its incomplete variances, so its result is what it
20871-
// computes first from scratch too, which reaches the measurement and
20872-
// computes it again inside, as main keeps the first result set.
20917+
observed := c.observedHead
20918+
c.observedHead = minObservedHead(f.prevObservedHead, observed)
20919+
if r := &f.typeResolution; result && !r.participant && c.circularityStart != 0 && c.keepsResultInCycle(n, f, observed) {
2087320920
r.participant = true
2087420921
c.cycleParticipants = append(c.cycleParticipants, *r)
2087520922
}
@@ -20901,6 +20948,8 @@ func (c *Checker) beginTypeResolution(target TypeSystemEntity, propertyName Type
2090120948
f.prevMemo = int32(c.memoTop)
2090220949
f.isTypeResolution = true
2090320950
f.prevTypeResolution = int32(c.typeResolutionTop)
20951+
f.prevObservedHead = c.observedHead
20952+
c.observedHead = 0
2090420953
f.typeResolution.target = target
2090520954
f.typeResolution.propertyName = propertyName
2090620955
f.typeResolution.result = true

‎tsc/internal/checker/memo.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1984,6 +1984,7 @@ func (c *Checker) discardCycle(mark int, retain bool) {
19841984
// is incomplete is discarded when it finishes, as for the outermost, rather
19851985
// than read by the rest of the outer cycle.
19861986
func (c *Checker) extendCircularity(head int) {
1987+
c.observedHead = minObservedHead(c.observedHead, int32(head))
19871988
if c.checking && c.memoTop != 0 {
19881989
c.recordFromCheck()
19891990
}

‎tsc/internal/checker/resolutions.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type resolution struct {
5151
typeResolution TypeResolution
5252
isTypeResolution bool
5353
prevTypeResolution int32 // Checker.typeResolutionTop on entry
54+
prevObservedHead int32 // Checker.observedHead on entry
5455
// Checker.searchableTop on entry, for a type resolution other than that
5556
// of a current node.
5657
prevSearchable int32

‎tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).errors.txt‎

Lines changed: 0 additions & 40 deletions
This file was deleted.

‎tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).symbols‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ export const $ = scope({
8282

8383
b: () => $.type("a").pipe(function (n) { return n === 0; }),
8484
>b : Symbol(b, Decl(circularScopeThunkReverseMapped.ts, 12, 62))
85+
>$.type("a").pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19))
86+
>$.type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22))
8587
>$ : Symbol($, Decl(circularScopeThunkReverseMapped.ts, 11, 12))
88+
>type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22))
89+
>pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19))
8690
>n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 13, 40))
8791
>n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 13, 40))
8892

@@ -105,7 +109,11 @@ export const $2 = scope({
105109

106110
b: () => $2.type("a").pipe((n: number) => n === 0),
107111
>b : Symbol(b, Decl(circularScopeThunkReverseMapped.ts, 18, 62))
112+
>$2.type("a").pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19))
113+
>$2.type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22))
108114
>$2 : Symbol($2, Decl(circularScopeThunkReverseMapped.ts, 17, 12))
115+
>type : Symbol(Scope.type, Decl(circularScopeThunkReverseMapped.ts, 2, 22))
116+
>pipe : Symbol(Type.pipe, Decl(circularScopeThunkReverseMapped.ts, 5, 19))
109117
>n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 19, 32))
110118
>n : Symbol(n, Decl(circularScopeThunkReverseMapped.ts, 19, 32))
111119

‎tsc/testdata/baselines/reference/compiler/circularScopeThunkReverseMapped(checkorder=typesfirst).types‎

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ declare function type<T>(x: T): Type<T>;
2727
>x : T
2828

2929
export const $ = scope({
30-
>$ : any
31-
>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),}) : Scope<{ readonly a: Type<number>; readonly b: () => any; }>
30+
>$ : Scope<{ a: Type<number>; b: () => Type<boolean>; }>
31+
>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),}) : Scope<{ readonly a: Type<number>; readonly b: () => Type<boolean>; }>
3232
>scope : <const def>(def: Validate<def>) => Scope<def>
33-
>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),} : { a: Type<number>; b: () => any; }
33+
>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $.type("a").pipe(function (n) { return n === 0; }),} : { a: Type<number>; b: () => Type<boolean>; }
3434

3535
a: type("string").pipe(function (s) { return s.length; }),
3636
>a : Type<number>
@@ -47,30 +47,30 @@ export const $ = scope({
4747
>length : number
4848

4949
b: () => $.type("a").pipe(function (n) { return n === 0; }),
50-
>b : () => any
51-
>() => $.type("a").pipe(function (n) { return n === 0; }) : () => any
52-
>$.type("a").pipe(function (n) { return n === 0; }) : any
53-
>$.type("a").pipe : any
54-
>$.type("a") : any
55-
>$.type : any
56-
>$ : any
57-
>type : any
50+
>b : () => Type<boolean>
51+
>() => $.type("a").pipe(function (n) { return n === 0; }) : () => Type<boolean>
52+
>$.type("a").pipe(function (n) { return n === 0; }) : Type<boolean>
53+
>$.type("a").pipe : <U>(f: (x: number) => U) => Type<U>
54+
>$.type("a") : Type<number>
55+
>$.type : <k_1 extends "a" | "b">(k: k_1) => Resolve<{ a: Type<number>; b: () => Type<boolean>; }[k_1]>
56+
>$ : Scope<{ a: Type<number>; b: () => Type<boolean>; }>
57+
>type : <k_1 extends "a" | "b">(k: k_1) => Resolve<{ a: Type<number>; b: () => Type<boolean>; }[k_1]>
5858
>"a" : "a"
59-
>pipe : any
60-
>function (n) { return n === 0; } : (n: any) => boolean
61-
>n : any
59+
>pipe : <U>(f: (x: number) => U) => Type<U>
60+
>function (n) { return n === 0; } : (n: number) => n is 0
61+
>n : number
6262
>n === 0 : boolean
63-
>n : any
63+
>n : number
6464
>0 : 0
6565

6666
});
6767

6868
// Entered from the function in the first property when queried in reverse
6969
export const $2 = scope({
70-
>$2 : any
71-
>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),}) : Scope<{ readonly a: Type<number>; readonly b: () => any; }>
70+
>$2 : Scope<{ a: Type<number>; b: () => Type<boolean>; }>
71+
>scope({ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),}) : Scope<{ readonly a: Type<number>; readonly b: () => Type<boolean>; }>
7272
>scope : <const def>(def: Validate<def>) => Scope<def>
73-
>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),} : { a: Type<number>; b: () => any; }
73+
>{ a: type("string").pipe(function (s) { return s.length; }), b: () => $2.type("a").pipe((n: number) => n === 0),} : { a: Type<number>; b: () => Type<boolean>; }
7474

7575
a: type("string").pipe(function (s) { return s.length; }),
7676
>a : Type<number>
@@ -87,16 +87,16 @@ export const $2 = scope({
8787
>length : number
8888

8989
b: () => $2.type("a").pipe((n: number) => n === 0),
90-
>b : () => any
91-
>() => $2.type("a").pipe((n: number) => n === 0) : () => any
92-
>$2.type("a").pipe((n: number) => n === 0) : any
93-
>$2.type("a").pipe : any
94-
>$2.type("a") : any
95-
>$2.type : any
96-
>$2 : any
97-
>type : any
90+
>b : () => Type<boolean>
91+
>() => $2.type("a").pipe((n: number) => n === 0) : () => Type<boolean>
92+
>$2.type("a").pipe((n: number) => n === 0) : Type<boolean>
93+
>$2.type("a").pipe : <U>(f: (x: number) => U) => Type<U>
94+
>$2.type("a") : Type<number>
95+
>$2.type : <k_1 extends "a" | "b">(k: k_1) => Resolve<{ a: Type<number>; b: () => Type<boolean>; }[k_1]>
96+
>$2 : Scope<{ a: Type<number>; b: () => Type<boolean>; }>
97+
>type : <k_1 extends "a" | "b">(k: k_1) => Resolve<{ a: Type<number>; b: () => Type<boolean>; }[k_1]>
9898
>"a" : "a"
99-
>pipe : any
99+
>pipe : <U>(f: (x: number) => U) => Type<U>
100100
>(n: number) => n === 0 : (n: number) => n is 0
101101
>n : number
102102
>n === 0 : boolean

0 commit comments

Comments
 (0)