Skip to content

Commit 7ce24e0

Browse files
committed
fix: preserve inheritance split nested continuation cases
Symptom: an inheritance split branch containing a nested empty-then decision could lose the boolean case value on the continuation flow when the branch joined a shared split merge. Root cause: addStructuredInheritanceSplit consumed flowBuilder.nextConnectionPoint from the nested decision but did not carry flowBuilder.nextFlowCase through branch tail wiring. Fix: preserve the pending case value on inheritance branch tails and reapply it when connecting to the next statement, parent continuation, or shared merge. Tests: add a builder regression for nested empty-then inheritance branches that must keep CaseValue=true on continuation flows.
1 parent 620e294 commit 7ce24e0

2 files changed

Lines changed: 82 additions & 3 deletions

File tree

mdl/executor/cmd_microflows_builder_actions.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
480480
fb.endsWithReturn = false
481481

482482
var lastID model.ID
483+
pendingCase := ""
483484
for _, stmt := range body {
484485
actID := fb.addStatement(stmt)
485486
if actID == "" {
@@ -504,11 +505,18 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
504505
}
505506
fb.flows = append(fb.flows, flow)
506507
} else {
507-
fb.flows = append(fb.flows, newHorizontalFlow(lastID, actID))
508+
if pendingCase != "" {
509+
fb.flows = append(fb.flows, newHorizontalFlowWithCase(lastID, actID, pendingCase))
510+
pendingCase = ""
511+
} else {
512+
fb.flows = append(fb.flows, newHorizontalFlow(lastID, actID))
513+
}
508514
}
509515
if fb.nextConnectionPoint != "" {
510516
lastID = fb.nextConnectionPoint
511517
fb.nextConnectionPoint = ""
518+
pendingCase = fb.nextFlowCase
519+
fb.nextFlowCase = ""
512520
} else {
513521
lastID = actID
514522
}
@@ -517,7 +525,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
517525
if !lastStmtIsReturn(body) {
518526
allBranchesReturn = false
519527
if lastID != "" {
520-
branchTails = append(branchTails, branchTail{id: lastID})
528+
branchTails = append(branchTails, branchTail{id: lastID, caseValue: pendingCase})
521529
}
522530
}
523531
}
@@ -534,6 +542,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
534542
fb.endsWithReturn = true
535543
} else if len(branchTails) == 1 && !branchTails[0].fromSplit {
536544
fb.nextConnectionPoint = branchTails[0].id
545+
fb.nextFlowCase = branchTails[0].caseValue
537546
} else if len(branchTails) > 0 {
538547
merge := &microflows.ExclusiveMerge{
539548
BaseMicroflowObject: microflows.BaseMicroflowObject{
@@ -551,7 +560,11 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
551560
fb.flows = append(fb.flows, newDownwardFlowWithInheritanceCase(splitID, merge.ID, tail.caseValue))
552561
}
553562
} else {
554-
fb.flows = append(fb.flows, newHorizontalFlow(tail.id, merge.ID))
563+
if tail.caseValue != "" {
564+
fb.flows = append(fb.flows, newHorizontalFlowWithCase(tail.id, merge.ID, tail.caseValue))
565+
} else {
566+
fb.flows = append(fb.flows, newHorizontalFlow(tail.id, merge.ID))
567+
}
555568
}
556569
}
557570
fb.nextConnectionPoint = merge.ID

mdl/executor/cmd_microflows_inheritance_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,72 @@ func TestLastStmtIsReturn_InheritanceSplitAllBranchesReturn(t *testing.T) {
130130
}
131131
}
132132

133+
func TestBuilder_InheritanceSplitNestedEmptyThenBranchKeepsContinuationCase(t *testing.T) {
134+
fb := &flowBuilder{
135+
spacing: HorizontalSpacing,
136+
declaredVars: map[string]string{"HasMember": "Boolean", "HasApp": "Boolean"},
137+
varTypes: map[string]string{"Selection": "Sample.Selection"},
138+
measurer: &layoutMeasurer{},
139+
}
140+
141+
oc := fb.buildFlowGraph([]ast.MicroflowStatement{
142+
&ast.InheritanceSplitStmt{
143+
Variable: "Selection",
144+
Cases: []ast.InheritanceSplitCase{
145+
{
146+
Entity: ast.QualifiedName{Module: "Sample", Name: "MemberSelection"},
147+
Body: []ast.MicroflowStatement{
148+
&ast.IfStmt{
149+
Condition: &ast.VariableExpr{Name: "HasMember"},
150+
ElseBody: []ast.MicroflowStatement{&ast.ReturnStmt{}},
151+
},
152+
},
153+
},
154+
{
155+
Entity: ast.QualifiedName{Module: "Sample", Name: "AppSelection"},
156+
Body: []ast.MicroflowStatement{
157+
&ast.IfStmt{
158+
Condition: &ast.VariableExpr{Name: "HasApp"},
159+
ElseBody: []ast.MicroflowStatement{&ast.ReturnStmt{}},
160+
},
161+
},
162+
},
163+
},
164+
ElseBody: []ast.MicroflowStatement{&ast.ReturnStmt{}},
165+
},
166+
&ast.LogStmt{Level: ast.LogInfo, Message: &ast.LiteralExpr{Kind: ast.LiteralString, Value: "shared tail"}},
167+
}, nil)
168+
169+
objects := map[model.ID]microflows.MicroflowObject{}
170+
var nestedSplitID model.ID
171+
for _, obj := range oc.Objects {
172+
objects[obj.GetID()] = obj
173+
split, ok := obj.(*microflows.ExclusiveSplit)
174+
if !ok {
175+
continue
176+
}
177+
if condition, ok := split.SplitCondition.(*microflows.ExpressionSplitCondition); ok && condition.Expression == "$HasMember" {
178+
nestedSplitID = split.ID
179+
}
180+
}
181+
if nestedSplitID == "" {
182+
t.Fatal("expected nested decision split")
183+
}
184+
for _, flow := range oc.Flows {
185+
if flow.OriginID != nestedSplitID {
186+
continue
187+
}
188+
caseValue, ok := flow.CaseValue.(microflows.EnumerationCase)
189+
if !ok || caseValue.Value != "true" {
190+
continue
191+
}
192+
if _, ok := objects[flow.DestinationID].(*microflows.ExclusiveMerge); ok {
193+
return
194+
}
195+
}
196+
t.Fatal("nested empty-then inheritance branch must carry CaseValue=true to the inheritance merge")
197+
}
198+
133199
func assertLineContains(t *testing.T, lines []string, want string) {
134200
t.Helper()
135201
for _, line := range lines {

0 commit comments

Comments
 (0)