Skip to content

Commit 947a522

Browse files
committed
fix: keep nested inheritance split tails outside cases
Symptom: describing a microflow with an inheritance split inside an if could emit the parent continuation inside the matching split case. Re-executing that MDL made variables declared in the continuation branch-scoped, so Mendix mx check reported invalid or missing return/variable state. Root cause: nested inheritance split emission stopped branches only at the split's own merge. When the inheritance split had no merge because one branch returned and the other fell through to the parent if merge, branch traversal used an empty stop ID and consumed the parent tail. Fix: when emitting an inheritance split, prefer the split's own merge but fall back to the caller's stop ID. This keeps parent continuation statements outside the split cases while preserving standalone inheritance split behavior. Tests: added a synthetic nested if/split-type traversal regression that verifies the parent tail is emitted after both end split and end if.
1 parent 32e9a8b commit 947a522

2 files changed

Lines changed: 95 additions & 4 deletions

File tree

mdl/executor/cmd_microflows_inheritance_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,92 @@ func TestTraverseFlow_InheritanceSplitPreservesExplicitCaseOrder(t *testing.T) {
150150
}
151151
}
152152

153+
func TestTraverseFlow_NestedInheritanceSplitKeepsParentTailOutsideCase(t *testing.T) {
154+
e := newTestExecutor()
155+
entityID := mkID("entity-specialized")
156+
157+
activityMap := map[model.ID]microflows.MicroflowObject{
158+
mkID("start"): &microflows.StartEvent{BaseMicroflowObject: mkObj("start")},
159+
mkID("init"): &microflows.ActionActivity{
160+
BaseActivity: microflows.BaseActivity{BaseMicroflowObject: mkObj("init")},
161+
Action: &microflows.CreateVariableAction{
162+
VariableName: "TokenValue",
163+
InitialValue: "''",
164+
},
165+
},
166+
mkID("outer_split"): &microflows.ExclusiveSplit{
167+
BaseMicroflowObject: mkObj("outer_split"),
168+
SplitCondition: &microflows.ExpressionSplitCondition{Expression: "$UseToken"},
169+
},
170+
mkID("before_type_split"): &microflows.ActionActivity{
171+
BaseActivity: microflows.BaseActivity{BaseMicroflowObject: mkObj("before_type_split")},
172+
Action: &microflows.LogMessageAction{LogLevel: "Info", LogNodeName: "'App'", MessageTemplate: &model.Text{Translations: map[string]string{"en_US": "before type split"}}},
173+
},
174+
mkID("type_split"): &microflows.InheritanceSplit{
175+
BaseMicroflowObject: mkObj("type_split"),
176+
VariableName: "Input",
177+
},
178+
mkID("set_token"): &microflows.ActionActivity{
179+
BaseActivity: microflows.BaseActivity{BaseMicroflowObject: mkObj("set_token")},
180+
Action: &microflows.ChangeVariableAction{VariableName: "TokenValue", Value: "$Input/Value"},
181+
},
182+
mkID("failed_log"): &microflows.ActionActivity{
183+
BaseActivity: microflows.BaseActivity{BaseMicroflowObject: mkObj("failed_log")},
184+
Action: &microflows.LogMessageAction{LogLevel: "Info", LogNodeName: "'App'", MessageTemplate: &model.Text{Translations: map[string]string{"en_US": "no token"}}},
185+
},
186+
mkID("failed_return"): &microflows.EndEvent{
187+
BaseMicroflowObject: mkObj("failed_return"),
188+
ReturnValue: "empty",
189+
},
190+
mkID("outer_merge"): &microflows.ExclusiveMerge{BaseMicroflowObject: mkObj("outer_merge")},
191+
mkID("tail"): &microflows.ActionActivity{
192+
BaseActivity: microflows.BaseActivity{BaseMicroflowObject: mkObj("tail")},
193+
Action: &microflows.LogMessageAction{LogLevel: "Info", LogNodeName: "'App'", MessageTemplate: &model.Text{Translations: map[string]string{"en_US": "tail after split"}}},
194+
},
195+
mkID("end"): &microflows.EndEvent{
196+
BaseMicroflowObject: mkObj("end"),
197+
ReturnValue: "'ok'",
198+
},
199+
}
200+
flowsByOrigin := map[model.ID][]*microflows.SequenceFlow{
201+
mkID("start"): {mkFlow("start", "init")},
202+
mkID("init"): {mkFlow("init", "outer_split")},
203+
mkID("outer_split"): {
204+
mkBranchFlow("outer_split", "before_type_split", &microflows.ExpressionCase{Expression: "true"}),
205+
mkBranchFlow("outer_split", "outer_merge", &microflows.ExpressionCase{Expression: "false"}),
206+
},
207+
mkID("before_type_split"): {mkFlow("before_type_split", "type_split")},
208+
mkID("type_split"): {
209+
mkBranchFlow("type_split", "set_token", &microflows.InheritanceCase{EntityID: entityID}),
210+
mkBranchFlow("type_split", "failed_log", &microflows.InheritanceCase{}),
211+
},
212+
mkID("set_token"): {mkFlow("set_token", "outer_merge")},
213+
mkID("failed_log"): {mkFlow("failed_log", "failed_return")},
214+
mkID("outer_merge"): {mkFlow("outer_merge", "tail")},
215+
mkID("tail"): {mkFlow("tail", "end")},
216+
}
217+
splitMergeMap := map[model.ID]model.ID{mkID("outer_split"): mkID("outer_merge")}
218+
entityNames := map[model.ID]string{entityID: "Sample.SpecializedInput"}
219+
220+
var lines []string
221+
visited := make(map[model.ID]bool)
222+
e.traverseFlow(mkID("start"), activityMap, flowsByOrigin, splitMergeMap, visited, entityNames, nil, &lines, 0, nil, 0, nil)
223+
224+
out := strings.Join(lines, "\n")
225+
tail := strings.Index(out, "tail after split")
226+
endSplit := strings.Index(out, "end split;")
227+
endIf := strings.Index(out, "end if;")
228+
if tail == -1 {
229+
t.Fatalf("expected parent tail after nested inheritance split:\n%s", out)
230+
}
231+
if endSplit == -1 || tail < endSplit {
232+
t.Fatalf("parent tail must not be emitted inside the inheritance case:\n%s", out)
233+
}
234+
if endIf == -1 || tail < endIf {
235+
t.Fatalf("parent tail must remain after the outer IF closes:\n%s", out)
236+
}
237+
}
238+
153239
func TestLastStmtIsReturn_InheritanceSplitAllBranchesReturn(t *testing.T) {
154240
body := []ast.MicroflowStatement{
155241
&ast.InheritanceSplitStmt{

mdl/executor/cmd_microflows_show_helpers.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ func traverseFlowUntilMerge(
651651
startLine := len(*lines) + headerLineCount
652652
nestedMergeID := splitMergeMap[currentID]
653653
emitObjectAnnotations(obj, lines, indentStr, annotationsByTarget, flowsByOrigin, flowsByDest)
654-
emitInheritanceSplitStatement(ctx, currentID, nestedMergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget)
654+
emitInheritanceSplitStatement(ctx, currentID, mergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget)
655655
recordSourceMap(sourceMap, currentID, startLine, len(*lines)+headerLineCount-1)
656656
if nestedMergeID != "" && nestedMergeID != mergeID {
657657
visited[nestedMergeID] = true
@@ -944,7 +944,7 @@ func isMergePairedWithSplit(mergeID model.ID, splitMergeMap map[model.ID]model.I
944944
func emitInheritanceSplitStatement(
945945
ctx *ExecContext,
946946
currentID model.ID,
947-
mergeID model.ID,
947+
stopID model.ID,
948948
activityMap map[model.ID]microflows.MicroflowObject,
949949
flowsByOrigin map[model.ID][]*microflows.SequenceFlow,
950950
flowsByDest map[model.ID][]*microflows.SequenceFlow,
@@ -969,6 +969,11 @@ func emitInheritanceSplitStatement(
969969
indentStr := strings.Repeat(" ", indent)
970970
*lines = append(*lines, indentStr+"split type "+varName)
971971

972+
branchStopID := splitMergeMap[currentID]
973+
if branchStopID == "" {
974+
branchStopID = stopID
975+
}
976+
972977
var elseFlow *microflows.SequenceFlow
973978
for _, flow := range orderedInheritanceSplitFlows(findNormalFlows(flowsByOrigin[currentID])) {
974979
caseName, ok := inheritanceCaseName(flow, entityNames)
@@ -977,11 +982,11 @@ func emitInheritanceSplitStatement(
977982
continue
978983
}
979984
*lines = append(*lines, indentStr+"case "+caseName)
980-
traverseFlowUntilMerge(ctx, flow.DestinationID, mergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, cloneVisited(visited), entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
985+
traverseFlowUntilMerge(ctx, flow.DestinationID, branchStopID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, cloneVisited(visited), entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
981986
}
982987
if elseFlow != nil {
983988
*lines = append(*lines, indentStr+"else")
984-
traverseFlowUntilMerge(ctx, elseFlow.DestinationID, mergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, cloneVisited(visited), entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
989+
traverseFlowUntilMerge(ctx, elseFlow.DestinationID, branchStopID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, cloneVisited(visited), entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
985990
}
986991
*lines = append(*lines, indentStr+"end split;")
987992
}

0 commit comments

Comments
 (0)