Skip to content

Commit f0e5bd8

Browse files
authored
Common pattern for reused state objects (#649)
1 parent 34ec0b8 commit f0e5bd8

File tree

6 files changed

+105
-80
lines changed

6 files changed

+105
-80
lines changed

internal/checker/checker.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -721,8 +721,8 @@ type Checker struct {
721721
lastGetCombinedNodeFlagsResult ast.NodeFlags
722722
lastGetCombinedModifierFlagsNode *ast.Node
723723
lastGetCombinedModifierFlagsResult ast.ModifierFlags
724-
inferenceStates []InferenceState
725-
flowStates []FlowState
724+
freeinferenceState *InferenceState
725+
freeFlowState *FlowState
726726
flowLoopCache map[FlowLoopKey]*Type
727727
flowLoopStack []FlowLoopInfo
728728
sharedFlows []SharedFlow

internal/checker/flow.go

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,32 @@ type SharedFlow struct {
3636
}
3737

3838
type FlowState struct {
39-
reference *ast.Node
40-
declaredType *Type
41-
initialType *Type
42-
flowContainer *ast.Node
43-
refKey string
44-
depth int
45-
sharedFlowStart int
46-
reduceLabels []*ast.FlowReduceLabelData
47-
reduceLabelsBuffer [4]*ast.FlowReduceLabelData
39+
reference *ast.Node
40+
declaredType *Type
41+
initialType *Type
42+
flowContainer *ast.Node
43+
refKey string
44+
depth int
45+
sharedFlowStart int
46+
reduceLabels []*ast.FlowReduceLabelData
47+
next *FlowState
48+
}
49+
50+
func (c *Checker) getFlowState() *FlowState {
51+
f := c.freeFlowState
52+
if f == nil {
53+
f = &FlowState{}
54+
}
55+
c.freeFlowState = f.next
56+
return f
57+
}
58+
59+
func (c *Checker) putFlowState(f *FlowState) {
60+
*f = FlowState{
61+
reduceLabels: f.reduceLabels[:0],
62+
next: c.freeFlowState,
63+
}
64+
c.freeFlowState = f
4865
}
4966

5067
func getFlowNodeOfNode(node *ast.Node) *ast.FlowNode {
@@ -69,20 +86,16 @@ func (c *Checker) getFlowTypeOfReferenceEx(reference *ast.Node, declaredType *Ty
6986
return declaredType
7087
}
7188
}
72-
flowStateCount := len(c.flowStates)
73-
c.flowStates = slices.Grow(c.flowStates, 1)[:flowStateCount+1]
74-
f := &c.flowStates[flowStateCount]
89+
f := c.getFlowState()
7590
f.reference = reference
7691
f.declaredType = declaredType
7792
f.initialType = core.Coalesce(initialType, declaredType)
7893
f.flowContainer = flowContainer
7994
f.sharedFlowStart = len(c.sharedFlows)
80-
f.reduceLabels = f.reduceLabelsBuffer[:0]
8195
c.flowInvocationCount++
8296
evolvedType := c.getTypeAtFlowNode(f, flowNode).t
8397
c.sharedFlows = c.sharedFlows[:f.sharedFlowStart]
84-
c.flowStates[flowStateCount] = FlowState{}
85-
c.flowStates = c.flowStates[:flowStateCount]
98+
c.putFlowState(f)
8699
// When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation,
87100
// we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations
88101
// on empty arrays are possible without implicit any errors and new element types can be inferred without
@@ -2442,15 +2455,15 @@ func (c *Checker) getFlowTypeInStaticBlocks(symbol *ast.Symbol, staticBlocks []*
24422455
}
24432456

24442457
func (c *Checker) isReachableFlowNode(flow *ast.FlowNode) bool {
2445-
result := c.isReachableFlowNodeWorker(flow, false /*noCacheCheck*/)
2458+
f := c.getFlowState()
2459+
result := c.isReachableFlowNodeWorker(f, flow, false /*noCacheCheck*/)
2460+
c.putFlowState(f)
24462461
c.lastFlowNode = flow
24472462
c.lastFlowNodeReachable = result
24482463
return result
24492464
}
24502465

2451-
func (c *Checker) isReachableFlowNodeWorker(flow *ast.FlowNode, noCacheCheck bool) bool {
2452-
var reduceLabelsBuffer [4]*ast.FlowReduceLabelData
2453-
reduceLabels := reduceLabelsBuffer[:0]
2466+
func (c *Checker) isReachableFlowNodeWorker(f *FlowState, flow *ast.FlowNode, noCacheCheck bool) bool {
24542467
for {
24552468
if flow == c.lastFlowNode {
24562469
return c.lastFlowNodeReachable
@@ -2461,7 +2474,7 @@ func (c *Checker) isReachableFlowNodeWorker(flow *ast.FlowNode, noCacheCheck boo
24612474
if reachable, ok := c.flowNodeReachable[flow]; ok {
24622475
return reachable
24632476
}
2464-
reachable := c.isReachableFlowNodeWorker(flow, true /*noCacheCheck*/)
2477+
reachable := c.isReachableFlowNodeWorker(f, flow, true /*noCacheCheck*/)
24652478
c.flowNodeReachable[flow] = reachable
24662479
return reachable
24672480
}
@@ -2487,8 +2500,8 @@ func (c *Checker) isReachableFlowNodeWorker(flow *ast.FlowNode, noCacheCheck boo
24872500
flow = flow.Antecedent
24882501
case flags&ast.FlowFlagsBranchLabel != 0:
24892502
// A branching point is reachable if any branch is reachable.
2490-
for list := getBranchLabelAntecedents(flow, reduceLabels); list != nil; list = list.Next {
2491-
if c.isReachableFlowNodeWorker(list.Flow, false /*noCacheCheck*/) {
2503+
for list := getBranchLabelAntecedents(flow, f.reduceLabels); list != nil; list = list.Next {
2504+
if c.isReachableFlowNodeWorker(f, list.Flow, false /*noCacheCheck*/) {
24922505
return true
24932506
}
24942507
}
@@ -2510,9 +2523,9 @@ func (c *Checker) isReachableFlowNodeWorker(flow *ast.FlowNode, noCacheCheck boo
25102523
case flags&ast.FlowFlagsReduceLabel != 0:
25112524
// Cache is unreliable once we start adjusting labels
25122525
c.lastFlowNode = nil
2513-
reduceLabels = append(reduceLabels, flow.Node.AsFlowReduceLabelData())
2514-
result := c.isReachableFlowNodeWorker(flow.Antecedent, false /*noCacheCheck*/)
2515-
reduceLabels = reduceLabels[:len(reduceLabels)-1]
2526+
f.reduceLabels = append(f.reduceLabels, flow.Node.AsFlowReduceLabelData())
2527+
result := c.isReachableFlowNodeWorker(f, flow.Antecedent, false /*noCacheCheck*/)
2528+
f.reduceLabels = f.reduceLabels[:len(f.reduceLabels)-1]
25162529
return result
25172530
default:
25182531
return flags&ast.FlowFlagsUnreachable == 0
@@ -2536,16 +2549,21 @@ func (c *Checker) isFalseExpression(expr *ast.Node) bool {
25362549
// Return true if the given flow node is preceded by a 'super(...)' call in every possible code path
25372550
// leading to the node.
25382551
func (c *Checker) isPostSuperFlowNode(flow *ast.FlowNode, noCacheCheck bool) bool {
2539-
var reduceLabelsBuffer [4]*ast.FlowReduceLabelData
2540-
reduceLabels := reduceLabelsBuffer[:0]
2552+
f := c.getFlowState()
2553+
result := c.isPostSuperFlowNodeWorker(f, flow, noCacheCheck)
2554+
c.putFlowState(f)
2555+
return result
2556+
}
2557+
2558+
func (c *Checker) isPostSuperFlowNodeWorker(f *FlowState, flow *ast.FlowNode, noCacheCheck bool) bool {
25412559
for {
25422560
flags := flow.Flags
25432561
if flags&ast.FlowFlagsShared != 0 {
25442562
if !noCacheCheck {
25452563
if postSuper, ok := c.flowNodePostSuper[flow]; ok {
25462564
return postSuper
25472565
}
2548-
postSuper := c.isPostSuperFlowNode(flow, true /*noCacheCheck*/)
2566+
postSuper := c.isPostSuperFlowNodeWorker(f, flow, true /*noCacheCheck*/)
25492567
c.flowNodePostSuper[flow] = postSuper
25502568
}
25512569
noCacheCheck = false
@@ -2559,8 +2577,8 @@ func (c *Checker) isPostSuperFlowNode(flow *ast.FlowNode, noCacheCheck bool) boo
25592577
}
25602578
flow = flow.Antecedent
25612579
case flags&ast.FlowFlagsBranchLabel != 0:
2562-
for list := getBranchLabelAntecedents(flow, reduceLabels); list != nil; list = list.Next {
2563-
if !c.isPostSuperFlowNode(list.Flow, false /*noCacheCheck*/) {
2580+
for list := getBranchLabelAntecedents(flow, f.reduceLabels); list != nil; list = list.Next {
2581+
if !c.isPostSuperFlowNodeWorker(f, list.Flow, false /*noCacheCheck*/) {
25642582
return false
25652583
}
25662584
}
@@ -2569,9 +2587,9 @@ func (c *Checker) isPostSuperFlowNode(flow *ast.FlowNode, noCacheCheck bool) boo
25692587
// A loop is post-super if the control flow path that leads to the top is post-super.
25702588
flow = flow.Antecedents.Flow
25712589
case flags&ast.FlowFlagsReduceLabel != 0:
2572-
reduceLabels = append(reduceLabels, flow.Node.AsFlowReduceLabelData())
2573-
result := c.isPostSuperFlowNode(flow.Antecedent, false /*noCacheCheck*/)
2574-
reduceLabels = reduceLabels[:len(reduceLabels)-1]
2590+
f.reduceLabels = append(f.reduceLabels, flow.Node.AsFlowReduceLabelData())
2591+
result := c.isPostSuperFlowNodeWorker(f, flow.Antecedent, false /*noCacheCheck*/)
2592+
f.reduceLabels = f.reduceLabels[:len(f.reduceLabels)-1]
25752593
return result
25762594
default:
25772595
// Unreachable nodes are considered post-super to silence errors

internal/checker/inference.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,40 @@ type InferenceState struct {
2626
visited map[InferenceKey]InferencePriority
2727
sourceStack []*Type
2828
targetStack []*Type
29+
next *InferenceState
30+
}
31+
32+
func (c *Checker) getInferenceState() *InferenceState {
33+
n := c.freeinferenceState
34+
if n == nil {
35+
n = &InferenceState{}
36+
}
37+
c.freeinferenceState = n.next
38+
return n
39+
}
40+
41+
func (c *Checker) putInferenceState(n *InferenceState) {
42+
clear(n.visited)
43+
*n = InferenceState{
44+
inferences: n.inferences[:0],
45+
visited: n.visited,
46+
sourceStack: n.sourceStack[:0],
47+
targetStack: n.targetStack[:0],
48+
next: c.freeinferenceState,
49+
}
50+
c.freeinferenceState = n
2951
}
3052

3153
func (c *Checker) inferTypes(inferences []*InferenceInfo, originalSource *Type, originalTarget *Type, priority InferencePriority, contravariant bool) {
32-
inferenceStateCount := len(c.inferenceStates)
33-
c.inferenceStates = slices.Grow(c.inferenceStates, 1)[:inferenceStateCount+1]
34-
n := &c.inferenceStates[inferenceStateCount]
54+
n := c.getInferenceState()
3555
n.inferences = inferences
3656
n.originalSource = originalSource
3757
n.originalTarget = originalTarget
3858
n.priority = priority
3959
n.inferencePriority = InferencePriorityMaxValue
4060
n.contravariant = contravariant
4161
c.inferFromTypes(n, originalSource, originalTarget)
42-
c.inferenceStates[inferenceStateCount] = InferenceState{}
43-
c.inferenceStates = c.inferenceStates[:inferenceStateCount]
62+
c.putInferenceState(n)
4463
}
4564

4665
func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) {

internal/checker/relater.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -365,12 +365,7 @@ func (c *Checker) checkTypeRelatedToEx(
365365
headMessage *diagnostics.Message,
366366
diagnosticOutput *[]*ast.Diagnostic,
367367
) bool {
368-
r := c.freeRelater
369-
if r == nil {
370-
r = &Relater{c: c}
371-
} else {
372-
c.freeRelater = r.next
373-
}
368+
r := c.getRelater()
374369
r.relation = relation
375370
r.errorNode = errorNode
376371
r.relationCount = (16_000_000 - relation.size()) / 8
@@ -398,16 +393,7 @@ func (c *Checker) checkTypeRelatedToEx(
398393
}
399394
c.reportDiagnostic(createDiagnosticChainFromErrorChain(r.errorChain, r.errorNode, r.relatedInfo), diagnosticOutput)
400395
}
401-
r.maybeKeysSet.Clear()
402-
*r = Relater{
403-
c: c,
404-
maybeKeys: r.maybeKeys[:0],
405-
maybeKeysSet: r.maybeKeysSet,
406-
sourceStack: r.sourceStack[:0],
407-
targetStack: r.targetStack[:0],
408-
next: c.freeRelater,
409-
}
410-
c.freeRelater = r
396+
c.putRelater(r)
411397
return result != TernaryFalse
412398
}
413399

@@ -2528,6 +2514,28 @@ type Relater struct {
25282514
next *Relater
25292515
}
25302516

2517+
func (c *Checker) getRelater() *Relater {
2518+
r := c.freeRelater
2519+
if r == nil {
2520+
r = &Relater{c: c}
2521+
}
2522+
c.freeRelater = r.next
2523+
return r
2524+
}
2525+
2526+
func (c *Checker) putRelater(r *Relater) {
2527+
r.maybeKeysSet.Clear()
2528+
*r = Relater{
2529+
c: c,
2530+
maybeKeys: r.maybeKeys[:0],
2531+
maybeKeysSet: r.maybeKeysSet,
2532+
sourceStack: r.sourceStack[:0],
2533+
targetStack: r.targetStack[:0],
2534+
next: c.freeRelater,
2535+
}
2536+
c.freeRelater = r
2537+
}
2538+
25312539
func (r *Relater) isRelatedToSimple(source *Type, target *Type) Ternary {
25322540
return r.isRelatedToEx(source, target, RecursionFlagsBoth, false /*reportErrors*/, nil /*headMessage*/, IntersectionStateNone)
25332541
}

testdata/baselines/reference/submodule/conformance/neverReturningFunctions1.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ neverReturningFunctions1.ts(129,5): error TS7027: Unreachable code detected.
2020
neverReturningFunctions1.ts(139,9): error TS7027: Unreachable code detected.
2121
neverReturningFunctions1.ts(141,5): error TS7027: Unreachable code detected.
2222
neverReturningFunctions1.ts(148,9): error TS7027: Unreachable code detected.
23+
neverReturningFunctions1.ts(153,5): error TS7027: Unreachable code detected.
2324

2425

25-
==== neverReturningFunctions1.ts (22 errors) ====
26+
==== neverReturningFunctions1.ts (23 errors) ====
2627
function fail(message?: string): never {
2728
throw new Error(message);
2829
}
@@ -220,6 +221,8 @@ neverReturningFunctions1.ts(148,9): error TS7027: Unreachable code detected.
220221
x;
221222
}
222223
x; // Unreachable
224+
~~
225+
!!! error TS7027: Unreachable code detected.
223226
}
224227

225228
function f43() {

testdata/baselines/reference/submodule/conformance/neverReturningFunctions1.errors.txt.diff

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

0 commit comments

Comments
 (0)