Skip to content

Commit 36db480

Browse files
committed
fix: preserve inheritance split case order
Symptom: type split cases with equivalent empty/fall-through bodies could be reordered after describe/exec/describe. Root cause: inheritance split case flows did not carry a stable ordering signal when multiple cases shared the same split-to-merge shape, so the describer could only rely on connection metadata that was identical across those branches. Fix: encode the parsed case order into valid split flow connection pairs and sort inheritance split flows by that encoded order during describe. Tests: added traversal coverage for shuffled stored inheritance case flows that must still describe in the original authoring order; existing inheritance split builder and cast coverage continues to pass.
1 parent 7ce24e0 commit 36db480

3 files changed

Lines changed: 96 additions & 4 deletions

File tree

mdl/executor/cmd_microflows_builder_actions.go

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
458458
id model.ID
459459
caseValue string
460460
fromSplit bool
461+
order int
461462
}
462463
var branchTails []branchTail
463464

@@ -471,7 +472,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
471472
branchIndex++
472473
if len(body) == 0 {
473474
allBranchesReturn = false
474-
branchTails = append(branchTails, branchTail{id: splitID, caseValue: caseValue, fromSplit: true})
475+
branchTails = append(branchTails, branchTail{id: splitID, caseValue: caseValue, fromSplit: true, order: branchNumber})
475476
return
476477
}
477478

@@ -503,6 +504,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
503504
if caseValue == "" {
504505
flow = newHorizontalFlow(splitID, actID)
505506
}
507+
applyInheritanceSplitCaseOrder(flow, branchNumber)
506508
fb.flows = append(fb.flows, flow)
507509
} else {
508510
if pendingCase != "" {
@@ -554,11 +556,14 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
554556
fb.objects = append(fb.objects, merge)
555557
for _, tail := range branchTails {
556558
if tail.fromSplit {
559+
var flow *microflows.SequenceFlow
557560
if tail.caseValue == "" {
558-
fb.flows = append(fb.flows, newHorizontalFlow(splitID, merge.ID))
561+
flow = newHorizontalFlow(splitID, merge.ID)
559562
} else {
560-
fb.flows = append(fb.flows, newDownwardFlowWithInheritanceCase(splitID, merge.ID, tail.caseValue))
563+
flow = newDownwardFlowWithInheritanceCase(splitID, merge.ID, tail.caseValue)
561564
}
565+
applyInheritanceSplitCaseOrder(flow, tail.order)
566+
fb.flows = append(fb.flows, flow)
562567
} else {
563568
if tail.caseValue != "" {
564569
fb.flows = append(fb.flows, newHorizontalFlowWithCase(tail.id, merge.ID, tail.caseValue))
@@ -664,6 +669,39 @@ func appendInheritanceBodies(s *ast.InheritanceSplitStmt) []ast.MicroflowStateme
664669
return stmts
665670
}
666671

672+
type inheritanceSplitCaseOrderAnchor struct {
673+
origin int
674+
destination int
675+
}
676+
677+
var inheritanceSplitCaseOrderAnchors = []inheritanceSplitCaseOrderAnchor{
678+
{AnchorTop, AnchorLeft},
679+
{AnchorRight, AnchorLeft},
680+
{AnchorBottom, AnchorLeft},
681+
{AnchorLeft, AnchorLeft},
682+
{AnchorTop, AnchorTop},
683+
{AnchorRight, AnchorTop},
684+
{AnchorBottom, AnchorTop},
685+
{AnchorLeft, AnchorTop},
686+
{AnchorTop, AnchorRight},
687+
{AnchorRight, AnchorRight},
688+
{AnchorBottom, AnchorRight},
689+
{AnchorLeft, AnchorRight},
690+
{AnchorTop, AnchorBottom},
691+
{AnchorRight, AnchorBottom},
692+
{AnchorBottom, AnchorBottom},
693+
{AnchorLeft, AnchorBottom},
694+
}
695+
696+
func applyInheritanceSplitCaseOrder(flow *microflows.SequenceFlow, order int) {
697+
if flow == nil || order < 0 || order >= len(inheritanceSplitCaseOrderAnchors) {
698+
return
699+
}
700+
pair := inheritanceSplitCaseOrderAnchors[order]
701+
flow.OriginConnectionIndex = pair.origin
702+
flow.DestinationConnectionIndex = pair.destination
703+
}
704+
667705
func qualifiedNameString(qn ast.QualifiedName) string {
668706
if qn.Module == "" {
669707
return qn.Name

mdl/executor/cmd_microflows_inheritance_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package executor
44

55
import (
6+
"strings"
67
"testing"
78

89
"github.com/mendixlabs/mxcli/mdl/ast"
@@ -116,6 +117,39 @@ func TestTraverseFlow_InheritanceSplit(t *testing.T) {
116117
assertLineContains(t, lines, "end split;")
117118
}
118119

120+
func TestTraverseFlow_InheritanceSplitPreservesExplicitCaseOrder(t *testing.T) {
121+
e := newTestExecutor()
122+
activityMap := map[model.ID]microflows.MicroflowObject{
123+
mkID("split"): &microflows.InheritanceSplit{
124+
BaseMicroflowObject: mkObj("split"),
125+
VariableName: "Input",
126+
},
127+
mkID("merge"): &microflows.ExclusiveMerge{BaseMicroflowObject: mkObj("merge")},
128+
}
129+
accountFlow := mkBranchFlow("split", "merge", &microflows.InheritanceCase{EntityQualifiedName: "Sample.Account"})
130+
userFlow := mkBranchFlow("split", "merge", &microflows.InheritanceCase{EntityQualifiedName: "Sample.User"})
131+
applyInheritanceSplitCaseOrder(accountFlow, 0)
132+
applyInheritanceSplitCaseOrder(userFlow, 1)
133+
flowsByOrigin := map[model.ID][]*microflows.SequenceFlow{
134+
mkID("split"): {userFlow, accountFlow},
135+
}
136+
splitMergeMap := map[model.ID]model.ID{mkID("split"): mkID("merge")}
137+
138+
var lines []string
139+
visited := make(map[model.ID]bool)
140+
e.traverseFlow(mkID("split"), activityMap, flowsByOrigin, splitMergeMap, visited, nil, nil, &lines, 1, nil, 0, nil)
141+
142+
out := strings.Join(lines, "\n")
143+
accountIdx := strings.Index(out, "case Sample.Account")
144+
userIdx := strings.Index(out, "case Sample.User")
145+
if accountIdx == -1 || userIdx == -1 {
146+
t.Fatalf("missing expected cases:\n%s", out)
147+
}
148+
if accountIdx > userIdx {
149+
t.Fatalf("case order was not preserved:\n%s", out)
150+
}
151+
}
152+
119153
func TestLastStmtIsReturn_InheritanceSplitAllBranchesReturn(t *testing.T) {
120154
body := []ast.MicroflowStatement{
121155
&ast.InheritanceSplitStmt{

mdl/executor/cmd_microflows_show_helpers.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1302,7 +1302,7 @@ func emitInheritanceSplitStatement(
13021302
*lines = append(*lines, indentStr+"split type "+varName)
13031303

13041304
var elseFlow *microflows.SequenceFlow
1305-
for _, flow := range findNormalFlows(flowsByOrigin[currentID]) {
1305+
for _, flow := range orderedInheritanceSplitFlows(findNormalFlows(flowsByOrigin[currentID])) {
13061306
caseName, ok := inheritanceCaseName(flow, entityNames)
13071307
if !ok {
13081308
elseFlow = flow
@@ -1387,6 +1387,14 @@ func orderedEnumSplitFlows(flows []*microflows.SequenceFlow) []*microflows.Seque
13871387
return ordered
13881388
}
13891389

1390+
func orderedInheritanceSplitFlows(flows []*microflows.SequenceFlow) []*microflows.SequenceFlow {
1391+
ordered := append([]*microflows.SequenceFlow(nil), flows...)
1392+
sort.SliceStable(ordered, func(i, j int) bool {
1393+
return inheritanceSplitCaseOrder(ordered[i]) < inheritanceSplitCaseOrder(ordered[j])
1394+
})
1395+
return ordered
1396+
}
1397+
13901398
func splitCaseOrder(flow *microflows.SequenceFlow) int {
13911399
if flow == nil {
13921400
return 1 << 20
@@ -1399,6 +1407,18 @@ func splitCaseOrder(flow *microflows.SequenceFlow) int {
13991407
return (1 << 10) + flow.OriginConnectionIndex*4 + flow.DestinationConnectionIndex
14001408
}
14011409

1410+
func inheritanceSplitCaseOrder(flow *microflows.SequenceFlow) int {
1411+
if flow == nil {
1412+
return 1 << 20
1413+
}
1414+
for i, pair := range inheritanceSplitCaseOrderAnchors {
1415+
if flow.OriginConnectionIndex == pair.origin && flow.DestinationConnectionIndex == pair.destination {
1416+
return i
1417+
}
1418+
}
1419+
return (1 << 10) + flow.OriginConnectionIndex*4 + flow.DestinationConnectionIndex
1420+
}
1421+
14021422
func formatEnumSplitCaseValue(value string) string {
14031423
if value == "" || value == "(empty)" {
14041424
return "(empty)"

0 commit comments

Comments
 (0)