Skip to content

Commit 80c0ddf

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 9a9e341 commit 80c0ddf

3 files changed

Lines changed: 97 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
@@ -332,6 +332,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
332332
id model.ID
333333
caseValue string
334334
fromSplit bool
335+
order int
335336
}
336337
var branchTails []branchTail
337338

@@ -345,7 +346,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
345346
branchIndex++
346347
if len(body) == 0 {
347348
allBranchesReturn = false
348-
branchTails = append(branchTails, branchTail{id: splitID, caseValue: caseValue, fromSplit: true})
349+
branchTails = append(branchTails, branchTail{id: splitID, caseValue: caseValue, fromSplit: true, order: branchNumber})
349350
return
350351
}
351352

@@ -377,6 +378,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
377378
if caseValue == "" {
378379
flow = newHorizontalFlow(splitID, actID)
379380
}
381+
applyInheritanceSplitCaseOrder(flow, branchNumber)
380382
fb.flows = append(fb.flows, flow)
381383
} else {
382384
if pendingCase != "" {
@@ -428,11 +430,14 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
428430
fb.objects = append(fb.objects, merge)
429431
for _, tail := range branchTails {
430432
if tail.fromSplit {
433+
var flow *microflows.SequenceFlow
431434
if tail.caseValue == "" {
432-
fb.flows = append(fb.flows, newHorizontalFlow(splitID, merge.ID))
435+
flow = newHorizontalFlow(splitID, merge.ID)
433436
} else {
434-
fb.flows = append(fb.flows, newDownwardFlowWithInheritanceCase(splitID, merge.ID, tail.caseValue))
437+
flow = newDownwardFlowWithInheritanceCase(splitID, merge.ID, tail.caseValue)
435438
}
439+
applyInheritanceSplitCaseOrder(flow, tail.order)
440+
fb.flows = append(fb.flows, flow)
436441
} else {
437442
if tail.caseValue != "" {
438443
fb.flows = append(fb.flows, newHorizontalFlowWithCase(tail.id, merge.ID, tail.caseValue))
@@ -455,6 +460,39 @@ func appendInheritanceBodies(s *ast.InheritanceSplitStmt) []ast.MicroflowStateme
455460
return stmts
456461
}
457462

463+
type inheritanceSplitCaseOrderAnchor struct {
464+
origin int
465+
destination int
466+
}
467+
468+
var inheritanceSplitCaseOrderAnchors = []inheritanceSplitCaseOrderAnchor{
469+
{AnchorTop, AnchorLeft},
470+
{AnchorRight, AnchorLeft},
471+
{AnchorBottom, AnchorLeft},
472+
{AnchorLeft, AnchorLeft},
473+
{AnchorTop, AnchorTop},
474+
{AnchorRight, AnchorTop},
475+
{AnchorBottom, AnchorTop},
476+
{AnchorLeft, AnchorTop},
477+
{AnchorTop, AnchorRight},
478+
{AnchorRight, AnchorRight},
479+
{AnchorBottom, AnchorRight},
480+
{AnchorLeft, AnchorRight},
481+
{AnchorTop, AnchorBottom},
482+
{AnchorRight, AnchorBottom},
483+
{AnchorBottom, AnchorBottom},
484+
{AnchorLeft, AnchorBottom},
485+
}
486+
487+
func applyInheritanceSplitCaseOrder(flow *microflows.SequenceFlow, order int) {
488+
if flow == nil || order < 0 || order >= len(inheritanceSplitCaseOrderAnchors) {
489+
return
490+
}
491+
pair := inheritanceSplitCaseOrderAnchors[order]
492+
flow.OriginConnectionIndex = pair.origin
493+
flow.DestinationConnectionIndex = pair.destination
494+
}
495+
458496
func qualifiedNameString(qn ast.QualifiedName) string {
459497
if qn.Module == "" {
460498
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: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package executor
66
import (
77
"context"
88
"fmt"
9+
"sort"
910
"strings"
1011

1112
"github.com/mendixlabs/mxcli/model"
@@ -969,7 +970,7 @@ func emitInheritanceSplitStatement(
969970
*lines = append(*lines, indentStr+"split type "+varName)
970971

971972
var elseFlow *microflows.SequenceFlow
972-
for _, flow := range findNormalFlows(flowsByOrigin[currentID]) {
973+
for _, flow := range orderedInheritanceSplitFlows(findNormalFlows(flowsByOrigin[currentID])) {
973974
caseName, ok := inheritanceCaseName(flow, entityNames)
974975
if !ok {
975976
elseFlow = flow
@@ -1008,6 +1009,26 @@ func inheritanceCaseName(flow *microflows.SequenceFlow, entityNames map[model.ID
10081009
return "", false
10091010
}
10101011

1012+
func orderedInheritanceSplitFlows(flows []*microflows.SequenceFlow) []*microflows.SequenceFlow {
1013+
ordered := append([]*microflows.SequenceFlow(nil), flows...)
1014+
sort.SliceStable(ordered, func(i, j int) bool {
1015+
return inheritanceSplitCaseOrder(ordered[i]) < inheritanceSplitCaseOrder(ordered[j])
1016+
})
1017+
return ordered
1018+
}
1019+
1020+
func inheritanceSplitCaseOrder(flow *microflows.SequenceFlow) int {
1021+
if flow == nil {
1022+
return 1 << 20
1023+
}
1024+
for i, pair := range inheritanceSplitCaseOrderAnchors {
1025+
if flow.OriginConnectionIndex == pair.origin && flow.DestinationConnectionIndex == pair.destination {
1026+
return i
1027+
}
1028+
}
1029+
return (1 << 10) + flow.OriginConnectionIndex*4 + flow.DestinationConnectionIndex
1030+
}
1031+
10111032
func cloneVisited(visited map[model.ID]bool) map[model.ID]bool {
10121033
cloned := make(map[model.ID]bool, len(visited))
10131034
for id, seen := range visited {

0 commit comments

Comments
 (0)