Skip to content

fix(microflows): preserve ELSIF arms by lowering to nested IfStmt (#745)#746

Draft
mlehane1 wants to merge 1 commit into
mendixlabs:mainfrom
mlehane1:fix/745-elsif-arms-dropped
Draft

fix(microflows): preserve ELSIF arms by lowering to nested IfStmt (#745)#746
mlehane1 wants to merge 1 commit into
mendixlabs:mainfrom
mlehane1:fix/745-elsif-arms-dropped

Conversation

@mlehane1

@mlehane1 mlehane1 commented Jul 9, 2026

Copy link
Copy Markdown

Fixes #745.

What

buildIfStatement (mdl/visitor/visitor_microflow_statements.go) read only exprs[0]/bodies[0] and the trailing ELSE body, so every middle (ELSIF expression THEN microflowBody)* pair from the grammar was silently dropped from the written model — check and exec reported success and mx check was clean, making the data loss invisible.

Mendix has no native elsif construct, so this lowers each ELSIF arm into a nested IfStmt in the ELSE branch of the arm before it (built innermost-first) — the canonical nested-exclusive-split shape Studio Pro users build by hand. Plain IF/ELSE builds exactly as before (regression-guarded).

Validation

  • Unit tests (new mdl/visitor/visitor_microflow_elsif_test.go): elsif chain preserved with conditions/bodies at each level; elsif-without-else (innermost arm has no ELSE); plain if/else unchanged (no gratuitous nesting). Written failing-first against the old visitor.
  • Bug-test script: mdl-examples/bug-tests/745-elsif-arms-dropped.mdl — exec against a fresh mxcli new 11.11.0 app, then DESCRIBE MICROFLOW BugTestElsif.MF_Grade shows all four arms (one/two/three/other) as a nested if/else chain with sensible auto-layout positions. mx check = 0 errors on the resulting app (Windows, mx 11.11.0).
  • Suites: go test ./mdl/... ./sdk/... green; go vet ./mdl/... clean (grammar regenerated with ANTLR 4.13.1).
  • Agentic testing: found, root-caused, fixed, and end-to-end verified via Claude Code driving mxcli headless (exec → describe round-trip → mx check) against real .mpr apps.
  • Symptom row added to .claude/skills/fix-issue.md per the bug-fix checklist.

Opened as a draft pending maintainer sign-off on #745 per CONTRIBUTING.md.

🤖 Generated with Claude Code

buildIfStatement read only exprs[0]/bodies[0] and the trailing ELSE
body, so every middle (ELSIF expression THEN microflowBody)* pair from
the grammar was silently dropped from the written model — check and
exec reported success and mx check was clean, making the data loss
invisible.

Mendix has no native elsif construct, so lower each ELSIF arm into a
nested IfStmt in the ELSE branch of the arm before it (built innermost
first) — the canonical nested-exclusive-split shape. Plain IF/ELSE
builds exactly as before.

Verified end-to-end: exec of the bug-test script against a fresh
11.11.0 app, DESCRIBE round-trips all arms as nested if/else, mx check
0 errors. go test ./mdl/... ./sdk/... green.

@ako ako left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve — solid, well-scoped fix

Reviewed the diff against the codebase and ran it end-to-end. This correctly fixes the silent ELSIF-arm drop (#745).

Correctness

  • The approach is the only correct one. ast.IfStmt (ast_microflow.go:289) has only Condition/ThenBody/ElseBody/HasElse — no ELSIF field — and Mendix has no native elsif. Lowering each arm into a nested IfStmt in the preceding ELSE is the canonical nested-exclusive-split shape and the only representation this AST supports.
  • Downstream already handles it. A nested IfStmt inside an ElseBody is exactly what a hand-written nested if/else produces — bodyTerminates, layout.measureStatements, and the flow builder all recurse generically. No serializer/executor changes needed, correctly omitted.
  • Edge cases traced: plain-IF, IF/ELSE, IF/ELSIF (no else), and IF/ELSIF/ELSE all lower correctly through the innermost-first loop; the plain path produces no gratuitous nesting; no annotation regression (caller applies s.Annotations to the returned outer stmt as before).

Testing (ran locally)

  • New visitor_microflow_elsif_test.go (chain / no-else / plain-if regression), written failing-first: pass. Full ./mdl/visitor/ and ./mdl/executor/: pass. Build + vet: clean.
  • End-to-end round-trip on a real 11.12 .mpr, both engines: exec + DESCRIBE MICROFLOW returns all four arms (one/two/three/other) as nested if/else on modelsdk and legacy. Before the fix only one/other survived.

Checklist

Test-first ✓, bug-test mdl-examples/bug-tests/745-… (numeric-prefix convention matches) ✓, fix-issue.md symptom row ✓, atomic single-concern diff ✓.

Minor, non-blocking

  1. DESCRIBE renders the chain as nested if/else, not the elsif keyword — a faithful-but-lossy round-trip. Inherent (Mendix has no elsif) and accepted in the issue; worth a one-line note in the PR body so users aren't surprised the elsif source doesn't come back verbatim.
  2. hasElse := … || ifCtx.ELSE() != nil is belt-and-suspenders (an ELSE always yields an extra body node) — harmless, mirrors the old fallback.

Nothing blocking. Merge once un-drafted per the #745 sign-off note.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IF/ELSIF/ELSE silently drops every ELSIF arm on write (both engines)

2 participants