Skip to content

Commit 32e9a8b

Browse files
committed
fix: preserve inheritance split branch anchors
Symptom: inheritance split roundtrips could lose branch flow anchors or collapse an explicit empty ELSE branch into an untyped flow. Root cause: inheritance branch building did not thread statement anchor metadata through split-to-body and body-to-merge flows, and empty ELSE tails used a plain sequence flow instead of an explicit inheritance case. Fix: propagate authored branch anchors across inheritance branch body flows and keep empty ELSE branches represented by an explicit empty InheritanceCase. Tests: added builder coverage for anchored inheritance branch bodies and tightened the existing case-flow assertion to select the intended typed branch.
1 parent 80c0ddf commit 32e9a8b

2 files changed

Lines changed: 104 additions & 12 deletions

File tree

mdl/executor/cmd_microflows_builder_actions.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
333333
caseValue string
334334
fromSplit bool
335335
order int
336+
anchor *ast.FlowAnchors
336337
}
337338
var branchTails []branchTail
338339

@@ -355,8 +356,10 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
355356
fb.endsWithReturn = false
356357

357358
var lastID model.ID
359+
var prevAnchor *ast.FlowAnchors
358360
pendingCase := ""
359361
for _, stmt := range body {
362+
thisAnchor := stmtOwnAnchor(stmt)
360363
actID := fb.addStatement(stmt)
361364
if actID == "" {
362365
continue
@@ -375,19 +378,21 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
375378
} else {
376379
flow = newDownwardFlowWithInheritanceCase(splitID, actID, caseValue)
377380
}
378-
if caseValue == "" {
379-
flow = newHorizontalFlow(splitID, actID)
380-
}
381-
applyInheritanceSplitCaseOrder(flow, branchNumber)
381+
applyUserAnchors(flow, nil, thisAnchor)
382382
fb.flows = append(fb.flows, flow)
383383
} else {
384384
if pendingCase != "" {
385-
fb.flows = append(fb.flows, newHorizontalFlowWithCase(lastID, actID, pendingCase))
385+
flow := newHorizontalFlowWithCase(lastID, actID, pendingCase)
386+
applyUserAnchors(flow, prevAnchor, thisAnchor)
387+
fb.flows = append(fb.flows, flow)
386388
pendingCase = ""
387389
} else {
388-
fb.flows = append(fb.flows, newHorizontalFlow(lastID, actID))
390+
flow := newHorizontalFlow(lastID, actID)
391+
applyUserAnchors(flow, prevAnchor, thisAnchor)
392+
fb.flows = append(fb.flows, flow)
389393
}
390394
}
395+
prevAnchor = thisAnchor
391396
if fb.nextConnectionPoint != "" {
392397
lastID = fb.nextConnectionPoint
393398
fb.nextConnectionPoint = ""
@@ -401,7 +406,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
401406
if !lastStmtIsReturn(body) {
402407
allBranchesReturn = false
403408
if lastID != "" {
404-
branchTails = append(branchTails, branchTail{id: lastID, caseValue: pendingCase})
409+
branchTails = append(branchTails, branchTail{id: lastID, caseValue: pendingCase, anchor: prevAnchor})
405410
}
406411
}
407412
}
@@ -431,18 +436,22 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
431436
for _, tail := range branchTails {
432437
if tail.fromSplit {
433438
var flow *microflows.SequenceFlow
434-
if tail.caseValue == "" {
435-
flow = newHorizontalFlow(splitID, merge.ID)
439+
if tail.order == 0 {
440+
flow = newHorizontalFlowWithInheritanceCase(splitID, merge.ID, tail.caseValue)
436441
} else {
437442
flow = newDownwardFlowWithInheritanceCase(splitID, merge.ID, tail.caseValue)
438443
}
439444
applyInheritanceSplitCaseOrder(flow, tail.order)
440445
fb.flows = append(fb.flows, flow)
441446
} else {
442447
if tail.caseValue != "" {
443-
fb.flows = append(fb.flows, newHorizontalFlowWithCase(tail.id, merge.ID, tail.caseValue))
448+
flow := newHorizontalFlowWithCase(tail.id, merge.ID, tail.caseValue)
449+
applyUserAnchors(flow, tail.anchor, nil)
450+
fb.flows = append(fb.flows, flow)
444451
} else {
445-
fb.flows = append(fb.flows, newHorizontalFlow(tail.id, merge.ID))
452+
flow := newHorizontalFlow(tail.id, merge.ID)
453+
applyUserAnchors(flow, tail.anchor, nil)
454+
fb.flows = append(fb.flows, flow)
446455
}
447456
}
448457
}

mdl/executor/cmd_microflows_inheritance_test.go

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestBuilder_InheritanceSplitAndCastAction(t *testing.T) {
5757
}
5858
for _, flow := range oc.Flows {
5959
if split != nil && flow.OriginID == split.ID {
60-
if _, ok := flow.CaseValue.(*microflows.InheritanceCase); ok {
60+
if caseValue, ok := flow.CaseValue.(*microflows.InheritanceCase); ok && caseValue.EntityQualifiedName == "Sample.SpecializedInput" {
6161
caseFlow = flow
6262
}
6363
}
@@ -230,6 +230,89 @@ func TestBuilder_InheritanceSplitNestedEmptyThenBranchKeepsContinuationCase(t *t
230230
t.Fatal("nested empty-then inheritance branch must carry CaseValue=true to the inheritance merge")
231231
}
232232

233+
func TestBuilder_InheritanceSplitBranchAnchorsApplyToBodyFlows(t *testing.T) {
234+
fb := &flowBuilder{spacing: HorizontalSpacing, measurer: &layoutMeasurer{}}
235+
message := &ast.ShowMessageStmt{
236+
Message: &ast.LiteralExpr{Kind: ast.LiteralString, Value: "No matching account"},
237+
Type: "Information",
238+
Annotations: &ast.ActivityAnnotations{
239+
Anchor: &ast.FlowAnchors{From: ast.AnchorSideBottom, To: ast.AnchorSideTop},
240+
},
241+
}
242+
bodyReturn := &ast.ReturnStmt{
243+
Annotations: &ast.ActivityAnnotations{
244+
Anchor: &ast.FlowAnchors{From: ast.AnchorSideUnset, To: ast.AnchorSideTop},
245+
},
246+
}
247+
248+
oc := fb.buildFlowGraph([]ast.MicroflowStatement{
249+
&ast.InheritanceSplitStmt{
250+
Variable: "Input",
251+
Cases: []ast.InheritanceSplitCase{
252+
{
253+
Entity: ast.QualifiedName{Module: "Sample", Name: "Primary"},
254+
Body: []ast.MicroflowStatement{&ast.ReturnStmt{}},
255+
},
256+
{
257+
Entity: ast.QualifiedName{Module: "Sample", Name: "Secondary"},
258+
Body: []ast.MicroflowStatement{message, bodyReturn},
259+
},
260+
},
261+
ElseBody: []ast.MicroflowStatement{&ast.ReturnStmt{}},
262+
},
263+
}, nil)
264+
265+
var splitID, messageID model.ID
266+
for _, obj := range oc.Objects {
267+
switch obj := obj.(type) {
268+
case *microflows.InheritanceSplit:
269+
splitID = obj.ID
270+
case *microflows.ActionActivity:
271+
if _, ok := obj.Action.(*microflows.ShowMessageAction); ok {
272+
messageID = obj.ID
273+
}
274+
}
275+
}
276+
if splitID == "" || messageID == "" {
277+
t.Fatalf("expected split and show-message activity, got split=%q message=%q", splitID, messageID)
278+
}
279+
280+
var splitToMessage, messageToReturn *microflows.SequenceFlow
281+
var elseCase *microflows.InheritanceCase
282+
for _, flow := range oc.Flows {
283+
if flow.OriginID == splitID && flow.DestinationID == messageID {
284+
splitToMessage = flow
285+
}
286+
if flow.OriginID == messageID {
287+
messageToReturn = flow
288+
}
289+
if flow.OriginID == splitID {
290+
if c, ok := flow.CaseValue.(*microflows.InheritanceCase); ok && c.EntityQualifiedName == "" {
291+
elseCase = c
292+
}
293+
}
294+
}
295+
if splitToMessage == nil {
296+
t.Fatal("expected inheritance split flow to annotated branch body")
297+
}
298+
if splitToMessage.OriginConnectionIndex != AnchorBottom || splitToMessage.DestinationConnectionIndex != AnchorTop {
299+
t.Fatalf("split branch anchors = (%d,%d), want (%d,%d)",
300+
splitToMessage.OriginConnectionIndex, splitToMessage.DestinationConnectionIndex,
301+
AnchorBottom, AnchorTop)
302+
}
303+
if messageToReturn == nil {
304+
t.Fatal("expected message to return flow")
305+
}
306+
if messageToReturn.OriginConnectionIndex != AnchorBottom || messageToReturn.DestinationConnectionIndex != AnchorTop {
307+
t.Fatalf("body flow anchors = (%d,%d), want (%d,%d)",
308+
messageToReturn.OriginConnectionIndex, messageToReturn.DestinationConnectionIndex,
309+
AnchorBottom, AnchorTop)
310+
}
311+
if elseCase == nil {
312+
t.Fatal("expected ELSE branch to keep an explicit empty inheritance case")
313+
}
314+
}
315+
233316
func assertLineContains(t *testing.T, lines []string, want string) {
234317
t.Helper()
235318
for _, line := range lines {

0 commit comments

Comments
 (0)