Skip to content

Commit 351294e

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 36db480 commit 351294e

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
@@ -459,6 +459,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
459459
caseValue string
460460
fromSplit bool
461461
order int
462+
anchor *ast.FlowAnchors
462463
}
463464
var branchTails []branchTail
464465

@@ -481,8 +482,10 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
481482
fb.endsWithReturn = false
482483

483484
var lastID model.ID
485+
var prevAnchor *ast.FlowAnchors
484486
pendingCase := ""
485487
for _, stmt := range body {
488+
thisAnchor := stmtOwnAnchor(stmt)
486489
actID := fb.addStatement(stmt)
487490
if actID == "" {
488491
continue
@@ -501,19 +504,21 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
501504
} else {
502505
flow = newDownwardFlowWithInheritanceCase(splitID, actID, caseValue)
503506
}
504-
if caseValue == "" {
505-
flow = newHorizontalFlow(splitID, actID)
506-
}
507-
applyInheritanceSplitCaseOrder(flow, branchNumber)
507+
applyUserAnchors(flow, nil, thisAnchor)
508508
fb.flows = append(fb.flows, flow)
509509
} else {
510510
if pendingCase != "" {
511-
fb.flows = append(fb.flows, newHorizontalFlowWithCase(lastID, actID, pendingCase))
511+
flow := newHorizontalFlowWithCase(lastID, actID, pendingCase)
512+
applyUserAnchors(flow, prevAnchor, thisAnchor)
513+
fb.flows = append(fb.flows, flow)
512514
pendingCase = ""
513515
} else {
514-
fb.flows = append(fb.flows, newHorizontalFlow(lastID, actID))
516+
flow := newHorizontalFlow(lastID, actID)
517+
applyUserAnchors(flow, prevAnchor, thisAnchor)
518+
fb.flows = append(fb.flows, flow)
515519
}
516520
}
521+
prevAnchor = thisAnchor
517522
if fb.nextConnectionPoint != "" {
518523
lastID = fb.nextConnectionPoint
519524
fb.nextConnectionPoint = ""
@@ -527,7 +532,7 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
527532
if !lastStmtIsReturn(body) {
528533
allBranchesReturn = false
529534
if lastID != "" {
530-
branchTails = append(branchTails, branchTail{id: lastID, caseValue: pendingCase})
535+
branchTails = append(branchTails, branchTail{id: lastID, caseValue: pendingCase, anchor: prevAnchor})
531536
}
532537
}
533538
}
@@ -557,18 +562,22 @@ func (fb *flowBuilder) addStructuredInheritanceSplit(s *ast.InheritanceSplitStmt
557562
for _, tail := range branchTails {
558563
if tail.fromSplit {
559564
var flow *microflows.SequenceFlow
560-
if tail.caseValue == "" {
561-
flow = newHorizontalFlow(splitID, merge.ID)
565+
if tail.order == 0 {
566+
flow = newHorizontalFlowWithInheritanceCase(splitID, merge.ID, tail.caseValue)
562567
} else {
563568
flow = newDownwardFlowWithInheritanceCase(splitID, merge.ID, tail.caseValue)
564569
}
565570
applyInheritanceSplitCaseOrder(flow, tail.order)
566571
fb.flows = append(fb.flows, flow)
567572
} else {
568573
if tail.caseValue != "" {
569-
fb.flows = append(fb.flows, newHorizontalFlowWithCase(tail.id, merge.ID, tail.caseValue))
574+
flow := newHorizontalFlowWithCase(tail.id, merge.ID, tail.caseValue)
575+
applyUserAnchors(flow, tail.anchor, nil)
576+
fb.flows = append(fb.flows, flow)
570577
} else {
571-
fb.flows = append(fb.flows, newHorizontalFlow(tail.id, merge.ID))
578+
flow := newHorizontalFlow(tail.id, merge.ID)
579+
applyUserAnchors(flow, tail.anchor, nil)
580+
fb.flows = append(fb.flows, flow)
572581
}
573582
}
574583
}

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)