Skip to content

Commit e8dc435

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 358968e commit e8dc435

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
@@ -20327,9 +20328,20 @@ type cycleYield struct {
2032720328
savedResolutionStart int
2032820329
savedVisibleStart int
2032920330
savedDepths [3]depthLimit
20331+
savedReverseMapped reverseMappedState
20332+
savedContextual []ContextualInfo
20333+
savedInference []InferenceContextInfo
20334+
floor int
2033020335
shadowed []shadowedResolving
2033120336
}
2033220337

20338+
type reverseMappedState struct {
20339+
sourceStack []*Type
20340+
targetStack []*Type
20341+
expandingFlags ExpandingFlags
20342+
start int32
20343+
}
20344+
2033320345
type shadowedResolving struct {
2033420346
key memoKey
2033520347
index int32
@@ -20513,7 +20525,15 @@ func (c *Checker) beginCycleYield(target TypeSystemEntity, propertyName TypeSyst
2051320525
savedResolutionStart: c.resolutionStart,
2051420526
savedVisibleStart: c.visibleStart,
2051520527
savedDepths: [3]depthLimit{c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth},
20528+
savedReverseMapped: reverseMappedState{c.reverseMappedSourceStack, c.reverseMappedTargetStack, c.reverseExpandingFlags, c.reverseMappedStart},
20529+
savedContextual: c.contextualInfos,
20530+
savedInference: c.inferenceContextInfos,
20531+
floor: floor,
2051620532
})
20533+
// The contexts the hidden resolutions entered are hidden too, so that the
20534+
// recomputation is from scratch.
20535+
c.reverseMappedSourceStack, c.reverseMappedTargetStack, c.reverseExpandingFlags, c.reverseMappedStart = nil, nil, 0, 0
20536+
c.contextualInfos, c.inferenceContextInfos = nil, nil
2051720537
c.resolutionStart = n
2051820538
c.visibleStart = n
2051920539
c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth = depthLimit{}, depthLimit{}, depthLimit{}
@@ -20524,6 +20544,9 @@ func (c *Checker) endCycleYield() {
2052420544
c.resolutionStart = y.savedResolutionStart
2052520545
c.visibleStart = y.savedVisibleStart
2052620546
c.serializationLevel, c.inlineLevel, c.conditionalConstraintDepth = y.savedDepths[0], y.savedDepths[1], y.savedDepths[2]
20547+
r := y.savedReverseMapped
20548+
c.reverseMappedSourceStack, c.reverseMappedTargetStack, c.reverseExpandingFlags, c.reverseMappedStart = r.sourceStack, r.targetStack, r.expandingFlags, r.start
20549+
c.contextualInfos, c.inferenceContextInfos = y.savedContextual, y.savedInference
2052720550
for _, sh := range y.shadowed {
2052820551
c.resolving.set(sh.key, sh.index)
2052920552
}
@@ -20789,6 +20812,42 @@ func (c *Checker) markTypeResolutionCircular(start int) {
2078920812
c.markCircular(start)
2079020813
}
2079120814

20815+
// keepsResultInCycle reports whether the type resolution at index n, which
20816+
// observed the cycles whose lowest head is observed (see Checker.observedHead),
20817+
// computes what it computes first from scratch although a cycle is in
20818+
// progress, so that its result is kept as a participant of the cycle:
20819+
// - The resolution re-entered a hidden resolution of itself, which a
20820+
// computation from scratch of the outer one always does, and what it
20821+
// observed in progress was all entered by the outer one, so its result is
20822+
// what the outer one computes first from scratch.
20823+
// - The resolution is the one a cycle yield computes again from scratch,
20824+
// with everything below it hidden, and it observed nothing hidden.
20825+
// - The resolution is inside the outermost measurement of variances, which
20826+
// hides what is below it and is measured from the canonical entry of its
20827+
// circularity, so what it computes is the same whatever led to it, and it
20828+
// only observed its incomplete variances. Computing it first from scratch
20829+
// reaches the measurement and computes it again inside, the same way.
20830+
func (c *Checker) keepsResultInCycle(n int, f *resolution, observed int32) bool {
20831+
r := &f.typeResolution
20832+
if r.outerSelf != 0 && observed != 0 && observed >= r.outerSelf && int(observed) <= n {
20833+
return true
20834+
}
20835+
if len(c.cycleYields) != 0 {
20836+
y := &c.cycleYields[len(c.cycleYields)-1]
20837+
if n >= y.floor && int(f.prevTypeResolution) <= y.floor && (observed == 0 || int(observed) > y.floor) {
20838+
return true
20839+
}
20840+
}
20841+
return c.observedOnlyOutermostMeasurement(n)
20842+
}
20843+
20844+
func minObservedHead(a int32, b int32) int32 {
20845+
if a == 0 || b != 0 && b < a {
20846+
return b
20847+
}
20848+
return a
20849+
}
20850+
2079220851
// observedOnlyOutermostMeasurement reports whether the cycle in progress is
2079320852
// the one headed by the outermost measurement of variances, which the
2079420853
// resolution at index n is inside, and no other.
@@ -20847,21 +20906,9 @@ func (c *Checker) popTypeResolution() bool {
2084720906
debug.Assert(c.typeResolutionTop == n+1, "resolutions left above a type resolution")
2084820907
f := &c.resolutions[n]
2084920908
result := f.typeResolution.result
20850-
if r := &f.typeResolution; r.outerSelf != 0 && result && c.circularityStart != 0 && c.circularityStart <= n && c.circularityStart >= int(r.outerSelf) && !r.participant {
20851-
// The resolution re-entered a hidden resolution of itself, which a
20852-
// computation from scratch of the outer one always does, and what it
20853-
// observed in progress was all entered by the outer one, so its result
20854-
// is what the outer one computes first from scratch: the outer one
20855-
// keeps it, as must the cycle.
20856-
r.participant = true
20857-
c.cycleParticipants = append(c.cycleParticipants, *r)
20858-
} else if r := &f.typeResolution; result && !r.participant && c.observedOnlyOutermostMeasurement(n) {
20859-
// The outermost measurement of variances hides what is below it and
20860-
// is measured from the canonical entry of its circularity, so what
20861-
// it computes is the same whatever led to it. This resolution only
20862-
// observed its incomplete variances, so its result is what it
20863-
// computes first from scratch too, which reaches the measurement and
20864-
// computes it again inside, as main keeps the first result set.
20909+
observed := c.observedHead
20910+
c.observedHead = minObservedHead(f.prevObservedHead, observed)
20911+
if r := &f.typeResolution; result && !r.participant && c.circularityStart != 0 && c.keepsResultInCycle(n, f, observed) {
2086520912
r.participant = true
2086620913
c.cycleParticipants = append(c.cycleParticipants, *r)
2086720914
}
@@ -20893,6 +20940,8 @@ func (c *Checker) beginTypeResolution(target TypeSystemEntity, propertyName Type
2089320940
f.prevMemo = int32(c.memoTop)
2089420941
f.isTypeResolution = true
2089520942
f.prevTypeResolution = int32(c.typeResolutionTop)
20943+
f.prevObservedHead = c.observedHead
20944+
c.observedHead = 0
2089620945
f.typeResolution.target = target
2089720946
f.typeResolution.propertyName = propertyName
2089820947
f.typeResolution.result = true

‎tsc/internal/checker/memo.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,6 +1926,7 @@ func (c *Checker) discardCycle(mark int, retain bool) {
19261926
// is incomplete is discarded when it finishes, as for the outermost, rather
19271927
// than read by the rest of the outer cycle.
19281928
func (c *Checker) extendCircularity(head int) {
1929+
c.observedHead = minObservedHead(c.observedHead, int32(head))
19291930
if c.checking && c.memoTop != 0 {
19301931
c.recordFromCheck()
19311932
}

‎tsc/internal/checker/resolutions.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type resolution struct {
4545
typeResolution TypeResolution
4646
isTypeResolution bool
4747
prevTypeResolution int32 // Checker.typeResolutionTop on entry
48+
prevObservedHead int32 // Checker.observedHead on entry
4849
// Checker.searchableTop on entry, for a type resolution other than that
4950
// of a current node.
5051
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)