Skip to content

Commit d783877

Browse files
authored
Merge pull request #371 from hjotha/submit/describer-void-endevent-return
fix: preserve void EndEvent returns in describe
2 parents 0f35801 + 298e1b1 commit d783877

6 files changed

Lines changed: 113 additions & 8 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
-- ============================================================================
2+
-- Bug #368: Empty EndEvent termination paths dropped in void microflow describe
3+
-- ============================================================================
4+
--
5+
-- Symptom (before fix):
6+
-- The describer skipped every empty `EndEvent` unconditionally. For
7+
-- void microflows that meant any explicit terminal `EndEvent` (e.g.
8+
-- the end of an early-exit branch) was lost on describe — even though
9+
-- `return;` is a valid void microflow termination. The roundtrip then
10+
-- fabricated a fall-through to the synthetic end and changed semantics
11+
-- for branches that originally had a deliberate early termination.
12+
--
13+
-- Conversely, in value-returning microflows, an empty EndEvent
14+
-- represented an invalid bare `return;` — emitting it was rejected by
15+
-- the parser on re-exec.
16+
--
17+
-- After fix:
18+
-- `cmd_microflows_show.go` now tracks whether the microflow has a
19+
-- return value:
20+
-- - void microflows describe empty EndEvents as `return;` so explicit
21+
-- termination paths survive describe → exec → describe.
22+
-- - value-returning microflows continue to suppress empty EndEvents
23+
-- so re-exec does not encounter bare `return;` (which is invalid
24+
-- MDL for value-returning shapes).
25+
--
26+
-- Usage:
27+
-- mxcli exec mdl-examples/bug-tests/368-void-endevent-return.mdl -p app.mpr
28+
-- mxcli -p app.mpr -c "describe microflow BugTest368.MF_VoidEarlyExit"
29+
-- The void microflow's describe output must contain `return;` for the
30+
-- early-exit branch. `mx check` must report 0 errors.
31+
-- ============================================================================
32+
33+
create module BugTest368;
34+
35+
-- Void microflow with an early-exit branch. The `if` THEN branch must
36+
-- describe as an explicit `return;` (not be silently dropped).
37+
create microflow BugTest368.MF_VoidEarlyExit (
38+
$Skip: boolean
39+
)
40+
begin
41+
if $Skip then
42+
return;
43+
end if;
44+
45+
log info node 'BugTest368' 'continued path';
46+
end;
47+
/
48+
49+
-- Value-returning microflow with the same shape — describer must NOT
50+
-- emit a bare `return;` for the implicit end EndEvent because that would
51+
-- be invalid in a value-returning microflow.
52+
create microflow BugTest368.MF_BoolEarlyExit (
53+
$Skip: boolean
54+
)
55+
returns boolean as $Done
56+
begin
57+
declare $Done boolean = false;
58+
59+
if $Skip then
60+
return false;
61+
end if;
62+
63+
set $Done = true;
64+
return $Done;
65+
end;
66+
/

mdl/executor/cmd_microflows_format_action.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ func formatActivity(
3333
}
3434
return fmt.Sprintf("return %s;", returnVal)
3535
}
36-
return "" // Skip end events without return value
36+
if ctx != nil && ctx.DescribingMicroflowHasReturnValue {
37+
return ""
38+
}
39+
return "return;"
3740

3841
case *microflows.ActionActivity:
3942
return formatAction(ctx, activity.Action, entityNames, microflowNames)

mdl/executor/cmd_microflows_show.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,12 @@ func describeMicroflow(ctx *ExecContext, name ast.QualifiedName) error {
273273
// BEGIN block
274274
lines = append(lines, "begin")
275275

276+
prevDescribingReturnValue := ctx.DescribingMicroflowHasReturnValue
277+
ctx.DescribingMicroflowHasReturnValue = microflowHasReturnValue(targetMf)
278+
defer func() {
279+
ctx.DescribingMicroflowHasReturnValue = prevDescribingReturnValue
280+
}()
281+
276282
// Generate activities
277283
if targetMf.ObjectCollection != nil && len(targetMf.ObjectCollection.Objects) > 0 {
278284
activityLines := formatMicroflowActivities(ctx, targetMf, entityNames, microflowNames)
@@ -487,6 +493,12 @@ func renderMicroflowMDL(
487493
microflowNames map[model.ID]string,
488494
sourceMap map[string]elkSourceRange,
489495
) string {
496+
prevDescribingReturnValue := ctx.DescribingMicroflowHasReturnValue
497+
ctx.DescribingMicroflowHasReturnValue = microflowHasReturnValue(mf)
498+
defer func() {
499+
ctx.DescribingMicroflowHasReturnValue = prevDescribingReturnValue
500+
}()
501+
490502
var lines []string
491503

492504
if mf.Documentation != "" {
@@ -573,6 +585,14 @@ func renderMicroflowMDL(
573585
return strings.Join(lines, "\n")
574586
}
575587

588+
func microflowHasReturnValue(mf *microflows.Microflow) bool {
589+
if mf == nil || mf.ReturnType == nil {
590+
return false
591+
}
592+
_, isVoid := mf.ReturnType.(*microflows.VoidType)
593+
return !isVoid
594+
}
595+
576596
// formatMicroflowDataType formats a microflow data type for MDL output.
577597
func formatMicroflowDataType(ctx *ExecContext, dt microflows.DataType, entityNames map[model.ID]string) string {
578598
if dt == nil {

mdl/executor/cmd_microflows_show_helpers_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,16 @@ func TestFormatActivity_EndEvent_NoReturn(t *testing.T) {
322322
e := newTestExecutor()
323323
obj := &microflows.EndEvent{BaseMicroflowObject: mkObj("1")}
324324
got := e.formatActivity(obj, nil, nil)
325+
if got != "return;" {
326+
t.Errorf("expected bare return for EndEvent without return, got %q", got)
327+
}
328+
}
329+
330+
func TestFormatActivity_EndEvent_NoReturnValueInReturningMicroflow(t *testing.T) {
331+
obj := &microflows.EndEvent{BaseMicroflowObject: mkObj("1")}
332+
got := formatActivity(&ExecContext{DescribingMicroflowHasReturnValue: true}, obj, nil, nil)
325333
if got != "" {
326-
t.Errorf("expected empty for EndEvent without return, got %q", got)
334+
t.Errorf("expected empty EndEvent to be skipped in value-returning microflow, got %q", got)
327335
}
328336
}
329337

mdl/executor/cmd_microflows_traverse_test.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,17 @@ func TestTraverseFlow_LinearSequence(t *testing.T) {
4343
visited := make(map[model.ID]bool)
4444
e.traverseFlow(mkID("start"), activityMap, flowsByOrigin, nil, visited, nil, nil, &lines, 1, nil, 0, nil)
4545

46-
// StartEvent produces no output, EndEvent with no return produces no output.
47-
// Each activity now has a @position line before it.
48-
if len(lines) != 4 {
49-
t.Fatalf("expected 4 lines, got %d: %v", len(lines), lines)
46+
// StartEvent produces no output. Void EndEvent emits an explicit return.
47+
// Each emitted activity has a @position line before it.
48+
if len(lines) != 6 {
49+
t.Fatalf("expected 6 lines, got %d: %v", len(lines), lines)
5050
}
5151
assertContains(t, lines[0], "@position(0, 0)")
5252
assertContains(t, lines[1], "$Obj = create Mod.Entity;")
5353
assertContains(t, lines[2], "@position(0, 0)")
5454
assertContains(t, lines[3], "commit $Obj;")
55+
assertContains(t, lines[4], "@position(0, 0)")
56+
assertContains(t, lines[5], "return;")
5557
}
5658

5759
// =============================================================================
@@ -206,11 +208,12 @@ func TestCollectErrorHandlerStatements_Simple(t *testing.T) {
206208
}
207209

208210
stmts := e.collectErrorHandlerStatements(mkID("err_log"), activityMap, flowsByOrigin, nil, nil)
209-
if len(stmts) != 1 {
210-
t.Fatalf("expected 1 statement, got %d: %v", len(stmts), stmts)
211+
if len(stmts) != 2 {
212+
t.Fatalf("expected 2 statements, got %d: %v", len(stmts), stmts)
211213
}
212214
assertContains(t, stmts[0], "log error")
213215
assertContains(t, stmts[0], "Something failed")
216+
assertContains(t, stmts[1], "return;")
214217
}
215218

216219
func TestCollectErrorHandlerStatements_StopsAtMerge(t *testing.T) {

mdl/executor/exec_context.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ type ExecContext struct {
8282
// Executor. Used by REFRESH CATALOG BACKGROUND so the goroutine can
8383
// deliver the result after syncBack has already run.
8484
SyncCatalog func(*catalog.Catalog)
85+
86+
// DescribingMicroflowHasReturnValue is set while rendering a microflow body.
87+
// It lets activity formatting distinguish a terminal void EndEvent from an
88+
// empty EndEvent in a value-returning microflow, where bare `return;` is invalid.
89+
DescribingMicroflowHasReturnValue bool
8590
}
8691

8792
// Connected returns true if a project is connected via the Backend.

0 commit comments

Comments
 (0)