fix: preserve annotations attached inside loop bodies#331
fix: preserve annotations attached inside loop bodies#331ako merged 7 commits intomendixlabs:mainfrom
Conversation
AI Code ReviewWhat Looks Good
RecommendationApprove. The PR correctly fixes the annotation preservation issue in loop bodies with minimal, well-targeted changes and appropriate test coverage. All relevant checklist items are satisfied for this bug fix. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
|
Updated the branch to also cover annotation flows stored directly inside loop object collections. Validation after the update:
|
01d88e0 to
15605e0
Compare
AI Code ReviewCritical IssuesNone found. Moderate IssuesNone found. Minor IssuesNone found. What Looks Good
Recommendationapprove - The PR correctly addresses the issue with minimal, well-tested changes that follow existing patterns. No blocking concerns identified. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
ako
left a comment
There was a problem hiding this comment.
Review: fix: preserve annotations attached inside loop bodies
Summary: Clean two-part fix. The builder side is a symmetric two-liner matching the existing sequence-flow promotion pattern; the describer side correctly handles the split between annotation objects (stored in loop ObjectCollections) and annotation flows (now promoted to the parent level).
Builder fix (two lines)
fb.annotationFlows = append(fb.annotationFlows, loopBuilder.annotationFlows...)Applied symmetrically in both addLoopStatement and addWhileStatement, matching the existing fb.flows = append(fb.flows, loopBuilder.flows...) promotion. The final MicroflowObjectCollection already packages fb.annotationFlows into AnnotationFlows, so no further wiring is needed. ✓
Describer fix
The object-vs-flow split is key: annotation Objects live inside the loop's ObjectCollection; annotation Flows are now promoted to the parent level by the builder. The describer fix handles both sides:
collectAnnotationCaptions: Recursively descends intoLoopedActivity.ObjectCollectionto collect caption IDs from nested annotations. Thecontinueafter the Annotation case is redundant (a Go object can only be one type) but harmless.emitLoopBody: Merges parentannotationsByTargetwith the loop-local annotation map. This handles the case where annotation flows are stored in the loop's own ObjectCollection (older MPR files that predate the builder fix).
Together these cover both the new MPR layout (flows promoted, captions found recursively) and existing MPR files (flows in loop ObjectCollection, found by the loop-local buildAnnotationsByTarget). ✓
mergeAnnotationsByTarget — returns overlay by reference when base is empty
if len(base) == 0 {
return overlay
}This returns the same map as overlay, not a copy. In emitLoopBody the overlay is a freshly built map from buildAnnotationsByTarget, so mutation of the result wouldn't affect anything else. Safe for the current caller. Worth a comment if this function gets additional callers in future.
Test coverage
TestLoopBodyIfAnnotationPromotedToParentFlows: Verifies the builder path — an annotation on a nested IF inside a LOOP ends up in the parent object collection's annotation map. Correctly traces through the promoted flow + recursive caption collection. ✓
TestTraverseFlow_LoopBodyUsesNestedAnnotationFlows: Pre-builds annotationsByTarget from the loop's ObjectCollection and passes it directly to traverseFlow, verifying that @annotation appears in the rendered output. This exercises annotation rendering but not the emitLoopBody merge path itself (the merge logic is tested indirectly by the MDL bug-test script rather than a Go unit test).
A Go-level test that constructs a full LoopedActivity with nested annotations and exercises emitLoopBody end-to-end would close this gap, but the existing coverage is sufficient for the described bug.
Bug test script
Documents the symptom (annotations on IF inside LOOP disappear after roundtrip) and the dependency on PR #327 for full fixpoint roundtrip. The scope note is helpful context. ✓
Verdict
Approve. The fix is correct, minimal, and symmetric between the builder and describer sides.
Documents that the empty-base/empty-overlay short-circuits return the other map by reference. Current callers do not mutate the result, so aliasing is intentionally safe; the comment warns future callers that intend to mutate to copy first. Addresses ako's review on PR mendixlabs#331. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Documents that the empty-base/empty-overlay short-circuits return the other map by reference. Current callers do not mutate the result, so aliasing is intentionally safe; the comment warns future callers that intend to mutate to copy first. Addresses ako's review on PR mendixlabs#331. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
12b8890 to
bfc8285
Compare
AI Code ReviewCritical IssuesNone found. Moderate IssuesNone found. Minor IssuesNone found. What Looks Good
RecommendationApprove. The PR fully addresses bug #330 with appropriate test coverage, follows the project's architecture (backend abstraction compliance is maintained as changes are confined to executor), and meets all checklist criteria for bug fixes. No changes are needed. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
|
Review follow-up status on the current head:
Local validation after the update was green: make build
make test
make lint-go |
Symptom: all open PRs against main failed the shared build-and-test job in make test-integration, even when their local build/test/lint validation passed. The failures reproduced on origin/main, so they were baseline CI instability rather than PR-specific regressions. Root cause: TestWatcherDebounce could allow stale timer callbacks to send an extra message under slow scheduling, nanoflow integration fixtures used MDL syntax that no longer matched the grammar, and the doctype mx-check harness did not classify known page/nanoflow showcase consistency errors as expected limitations. Fix: guard watcher debounce callbacks with a generation counter, tighten the watcher burst test, update nanoflow fixtures to current MDL syntax, and extend the known consistency-error allowlist for showcase-only limitations. Tests: make build Tests: go test ./cmd/mxcli/tui -run TestWatcherDebounce -count=20 -v Tests: ./bin/mxcli check mdl-examples/doctype-tests/02b-nanoflow-examples.mdl Tests: go test -tags integration -count=1 -timeout 30m ./mdl/executor -run 'TestRoundtripNanoflow_(Loop|EnumParameter|Annotations)|TestMxCheck_DoctypeScripts/02b-nanoflow-examples.mdl|TestMxCheck_DoctypeScripts/03-page-examples.mdl' -v Tests: make test Tests: make lint-go Tests: make test-integration
Symptom: annotations attached to statements inside loop or while bodies could disappear after describe/exec because nested annotation flows were not visible from the traversal context. Root cause: loop body builders copied only sequence flows back to the parent builder, describe collected annotation captions only from top-level objects, and loop-local annotation flows were not merged into the annotation map used while traversing loop bodies. Fix: promote nested loop annotation flows to the parent graph, collect annotation captions recursively from nested loop object collections, and merge loop-local annotations into loop body traversal. Tests: add regression coverage for an annotated nested decision built inside a loop body and for annotation flows stored directly in a loop object collection.
Adds an MDL script under mdl-examples/bug-tests/ exercising an annotated IF nested inside a LOOP. After exec, the describe output must contain `@annotation 'note on nested if'`, confirming the builder propagates nested annotation flows to the parent graph and the describer collects captions recursively. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Documents that the empty-base/empty-overlay short-circuits return the other map by reference. Current callers do not mutate the result, so aliasing is intentionally safe; the comment warns future callers that intend to mutate to copy first. Addresses ako's review on PR mendixlabs#331. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4df5314 to
b290d34
Compare
AI Code ReviewCritical Issues
Moderate Issues
Minor Issues
What Looks Good
RecommendationApprove. The PR correctly fixes the annotation preservation bug in loop bodies through focused changes in the executor and show helpers, supported by excellent test coverage (both MDL and unit tests). The minor concerns about test robustness and doctest reduction do not outweigh the correctness and clarity of the fix. The changes adhere to the project's architectural boundaries (no improper SDK imports in executor, proper backend abstraction via existing interfaces) and maintain full-stack consistency for the microflow execution/description pipeline. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
|
CI stabilization note: merged #412 into this branch so it uses the same integration-CI baseline as the rest of the active PR set. |
Symptom: CI failed in TestMxCheck_DoctypeScripts/empty_java_action_argument.mdl because a Java action declared as RETURNS Void was written as an entity return type named .void, and Studio Pro reported CE1613. Root cause: the generic data-type visitor treats bare qualified names as entity/enumeration references. Java action return types reused that generic path, so the explicit Void spelling became a qualified name instead of ast.TypeVoid. Fix: add a Java-action return-type wrapper that maps unqualified Void to ast.TypeVoid while leaving generic data-type parsing unchanged for parameters and attributes. Tests: added visitor coverage for explicit RETURNS Void; verified mxcli check for the doctype fixture and the targeted integration subtest that failed in GitHub Actions.
Part of #332.
Summary
Closes #330
Validation
go test ./mdl/executor -run 'TestLoopBodyIfAnnotationPromotedToParentFlows|TestTraverseFlow_LoopBodyUsesNestedAnnotationFlows'make buildmake lint-gomake test