Skip to content

Commit ba18481

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 4c57343 commit ba18481

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
@@ -806,7 +806,7 @@ func traverseFlowUntilMerge(
806806
startLine := len(*lines) + headerLineCount
807807
nestedMergeID := splitMergeMap[currentID]
808808
emitObjectAnnotations(obj, lines, indentStr, annotationsByTarget, flowsByOrigin, flowsByDest, activityMap)
809-
emitInheritanceSplitStatement(ctx, currentID, nestedMergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget)
809+
emitInheritanceSplitStatement(ctx, currentID, mergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, visited, entityNames, microflowNames, lines, indent, sourceMap, headerLineCount, annotationsByTarget)
810810
recordSourceMap(sourceMap, currentID, startLine, len(*lines)+headerLineCount-1)
811811
if nestedMergeID != "" && nestedMergeID != mergeID {
812812
visited[nestedMergeID] = true
@@ -1276,7 +1276,7 @@ func emitEnumSplitStatement(
12761276
func emitInheritanceSplitStatement(
12771277
ctx *ExecContext,
12781278
currentID model.ID,
1279-
mergeID model.ID,
1279+
stopID model.ID,
12801280
activityMap map[model.ID]microflows.MicroflowObject,
12811281
flowsByOrigin map[model.ID][]*microflows.SequenceFlow,
12821282
flowsByDest map[model.ID][]*microflows.SequenceFlow,
@@ -1301,6 +1301,11 @@ func emitInheritanceSplitStatement(
13011301
indentStr := strings.Repeat(" ", indent)
13021302
*lines = append(*lines, indentStr+"split type "+varName)
13031303

1304+
branchStopID := splitMergeMap[currentID]
1305+
if branchStopID == "" {
1306+
branchStopID = stopID
1307+
}
1308+
13041309
var elseFlow *microflows.SequenceFlow
13051310
for _, flow := range orderedInheritanceSplitFlows(findNormalFlows(flowsByOrigin[currentID])) {
13061311
caseName, ok := inheritanceCaseName(flow, entityNames)
@@ -1309,11 +1314,11 @@ func emitInheritanceSplitStatement(
13091314
continue
13101315
}
13111316
*lines = append(*lines, indentStr+"case "+caseName)
1312-
traverseFlowUntilMerge(ctx, flow.DestinationID, mergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, cloneVisited(visited), entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
1317+
traverseFlowUntilMerge(ctx, flow.DestinationID, branchStopID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, cloneVisited(visited), entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
13131318
}
13141319
if elseFlow != nil {
13151320
*lines = append(*lines, indentStr+"else")
1316-
traverseFlowUntilMerge(ctx, elseFlow.DestinationID, mergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, cloneVisited(visited), entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
1321+
traverseFlowUntilMerge(ctx, elseFlow.DestinationID, branchStopID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, cloneVisited(visited), entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
13171322
}
13181323
*lines = append(*lines, indentStr+"end split;")
13191324
}

0 commit comments

Comments
 (0)