fix: reject duplicate microflow output variables#337
Conversation
AI Code ReviewWhat Looks Good
RecommendationApprove the PR. The changes are correct, well-tested, and properly documented. No blocking issues found. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
AI Code ReviewCritical Issues
Moderate Issues
Minor Issues
What Looks Good
Recommendation Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
|
Follow-up pushed in This narrows duplicate-output aliasing to variables that are live on the continuation path. Terminal branch-local declarations no longer force Validation rerun on the branch:
|
AI Code ReviewLet me analyze this PR against the review checklist. First, let's understand what this PR does:
Now let me review against the checklist: Overlap & duplication
MDL syntax design (for new or modified MDL syntax)
Full-stack consistency (for MDL features)
Test coverage
Security & robustness
Scope & atomicity
Code quality
Bugs & correctness
Let me check a few specific implementation details:
The test file cmd_microflows_builder_variable_alias_test.go looks good:
The bug test and doctest files look appropriate for testing the end-to-end behavior. One thing I notice: the PR updates MDL_QUICK_REFERENCE.md to document the behavior. This is good for users to understand what DESCRIBE might produce. Let me check if there are any issues with the implementation: In uniqueImplicitOutputVariable(): if previous, ok := fb.outputVarPositions[varName]; ok &&
previous.X == position.X && previous.Y == position.Y &&
fb.isVariableDeclared(varName) {The condition checks However, there's a potential issue: what if the first occurrence hasn't been processed yet? But since we're processing statements sequentially, when we see the second occurrence, the first should have already been processed. Actually, looking more carefully: the method is called when processing a CreateObjectStmt or RetrieveStmt. For the first occurrence, fb.outputVarPositions[varName] won't exist yet, so it will skip the aliasing logic and just store the position. For the second occurrence at the same position, it will find the previous position, see that it matches, and check if the variable is declared (which it should be from the first occurrence). This seems correct. Another thing: in the alias generation loop: for i := 2; ; i++ {
candidate := fmt.Sprintf("%s_%d", varName, i)
if !fb.isVariableDeclared(candidate) {
fb.variableAliases[varName] = candidate
return candidate
}
}This will keep incrementing until it finds an undeclared name. This should work, but theoretically could loop forever if all possible aliases are taken. In practice, with reasonable variable names, this shouldn't be an issue. The implementation in cmd_microflows_builder_calls.go for call microflow outputs: useReturnVariable := s.OutputVariable != ""
if s.OutputVariable != "" && fb.callOutputDeclarations != nil {
useReturnVariable = fb.callOutputDeclarations[s]
}
action := µflows.MicroflowCallAction{
// ...
UseReturnVariable: useReturnVariable,
}
// ...
if s.OutputVariable != "" && useReturnVariable {
fb.registerResultVariableType(s.OutputVariable, fb.lookupMicroflowReturnType(mfQN))
}This looks correct. It determines whether to use the return variable based on planning, and only registers the result type if we're actually going to use the return variable. The variable state snapshot/restore in control flow looks important for correctness. Without this, variable aliases could leak between branches inappropriately. Let me check if there are any missing pieces:
I think that covers the main places where variable names are used in microflow activities. One potential issue: in cmd_microflows_builder.go, the resolveAssociationPaths function handles VariableExpr and AttributePathExpr, but what about other expression types that might contain variable references? Looking at the switch statement in resolveAssociationPaths: switch e := expr.(type) {
case *ast.VariableExpr:
return &ast.VariableExpr{Name: fb.resolveVariableName(e.Name)}
case *ast.AttributePathExpr:
// ... uses fb.resolveVariableName(e.Variable)
// ... other casesIt doesn't handle other expression types that might contain variables, like:
This could be a problem! If there are variable references in expressions that aren't handled by resolveAssociationPaths, they won't get the aliasing applied. Let me check if there are other places where expressions are processed: In cmd_microflows_builder_actions.go:
Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
ako
left a comment
There was a problem hiding this comment.
PR #337 — fix: preserve duplicate implicit output aliases
Overview: Two independent fixes for alias collisions in the microflow builder:
- Implicit output collisions (
create/retrieveproducing the same$Varat the same canvas position): assignsFoo_2,Foo_3suffixes viauniqueImplicitOutputVariable(). - Duplicate call-microflow result names (same output variable across two branches, not referenced): sets
UseReturnVariable=falsevia pre-passplanMicroflowCallOutputDeclarations().
The variable-state snapshot/restore in addIfStatement is the right approach for scoping aliases to branches. Tests are solid. Good to see a bug-test file alongside the doctype fixture.
Moderate
statementVarRefs has incomplete AST coverage — AggregateListStmt, ListOperationStmt, SendRestRequestStmt, CallExternalActionStmt, ExportToMappingStmt, and CreateListStmt are not handled. referencedVars is used to decide whether a duplicated call-microflow output is safe to suppress (UseReturnVariable=false). A variable referenced only via one of these missing types will be mis-classified as unreferenced, causing UseReturnVariable=false when the downstream code still reads the variable.
Concrete failing pattern:
$Items = call Module.GetItems(); -- branch A
$Items = call Module.GetItems(); -- branch B, same name
$Count = COUNT($Items); -- AggregateListStmt — not seen by statementVarRefs
Both call outputs get UseReturnVariable=false → $Items never declared → COUNT($Items) references undefined variable.
Fix: add the missing cases to statementVarRefs in cmd_microflows_builder_graph.go:
case *ast.AggregateListStmt:
refs = append(refs, s.InputVariable)
case *ast.ListOperationStmt:
refs = append(refs, s.InputVariable, s.SecondVariable)
case *ast.CallExternalActionStmt:
for _, arg := range s.Arguments {
refs = append(refs, exprVarRefs(arg.Value)...)
}
case *ast.CreateListStmt:
// no refs consumed (output-only)
case *ast.SendRestRequestStmt:
refs = append(refs, exprVarRefs(s.Body)...)Minor
doctype-tests/variable_alias_collision.test.mdl missing prerequisites — the file creates SampleAliases.ACT_DuplicateOutputPosition with return type SampleAliases.Item but never creates the module or entity. Running this against a project without that setup will fail. Compare 02-microflow-examples.mdl which opens with create module MfTest; and entity definitions. Add:
create module SampleAliases;
create entity SampleAliases.Item ( Name : string(100) );
/
before the microflow block (matching the bug-test file which already has this).
countMicroflowCallOutputs + inner walk in planMicroflowCallOutputDeclarations duplicate ~40 lines of identical switch boilerplate. Each time a new statement type gains an ErrorHandling field or output variable, it must be added in two places. A shared walkStatements(body []ast.MicroflowStatement, fn func(ast.MicroflowStatement)) helper extracting the recursion would halve the maintenance surface. Not a blocker for this PR, but worth a follow-up.
Proposal status is still "Draft" — PROPOSAL_microflow_variable_alias_collision.md line 3. Once this PR merges, it should read Status: Implemented.
MDL_QUICK_REFERENCE.md description is slightly misleading — "Describe may alias same-position duplicate outputs for round-trip preservation." The alias is assigned by the builder, which runs on both exec and describe paths. Suggested wording: "Builder assigns numeric aliases when duplicate implicit outputs collide at the same canvas position".
Recurring Findings table update
Suggest adding row 11:
| 11 | AST walker switch-case duplicated across multiple functions — new statement types must be added in every copy | Code quality | Extract a shared walkStatements(body, fn) helper; pass a per-function closure for the per-node logic |
AI Code ReviewCritical IssuesNone found. Moderate IssuesNone found. Minor Issues
What Looks Good
RecommendationApprove the pull request. The implementation correctly addresses the duplicate implicit output variable collision issue with a deterministic, round-trip-preserving solution. The changes are minimal, focused, and thoroughly tested with appropriate documentation. The solution maintains backward compatibility while fixing the described round-trip failure mode. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
Moves statementVarRefs / statementsVarRefs / referencedVariableSet / expressionStringVarRefs out of cmd_microflows_builder_graph.go into cmd_microflows_builder_varutil.go. Sibling builder passes (PR mendixlabs#366's pending error-handler queue, future planners) need the same walker without redeclaring it in their own file. Pure refactor — no behavior change. Also marks the variable-alias-collision proposal as Implemented now that the feature is shipping. Addresses ako's review on PR mendixlabs#337. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Moves statementVarRefs / statementsVarRefs / referencedVariableSet / expressionStringVarRefs out of cmd_microflows_builder_graph.go into cmd_microflows_builder_varutil.go. Sibling builder passes (PR mendixlabs#366's pending error-handler queue, future planners) need the same walker without redeclaring it in their own file. Pure refactor — no behavior change. Also marks the variable-alias-collision proposal as Implemented now that the feature is shipping. Addresses ako's review on PR mendixlabs#337. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9559bb1 to
7d0564f
Compare
AI Code ReviewWhat Looks Good
RecommendationApprove - The PR is well-scoped, thoroughly tested, properly documented, and implements a sound solution for preserving duplicate implicit output aliases during microflow round-trip operations. It maintains backward compatibility while fixing the described issue without introducing new syntax or breaking existing functionality. 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 |
|
Review follow-up status:
Local validation after the update: make build
make test |
Design concern: silent aliasing treats an invalid model as validThe premise of this PR is that legacy projects can contain duplicate output variable names that "Studio Pro can keep in the model." But Studio Pro does not allow two activities to share the same output variable name — it flags this as a model error. So a project exhibiting this pattern is already invalid, and the correct response is to surface that, not to paper over it with silent renaming. Two specific problems: 1. DESCRIBE of an invalid model should say so, not emit If describe encounters two activities at the same canvas position with the same output name, the project is broken. Emitting The right output is a comment flagging the problem: 2. Authored MDL with duplicate output names should be rejected, not accepted The example script: This is invalid MDL and should produce a validation error: Silently accepting it and renaming the second to Suggested direction
The aliasing machinery ( |
AI Code ReviewCritical IssuesNone found. Moderate IssuesNone found. Minor Issues
What Looks Good
RecommendationApprove. The PR correctly implements deterministic aliasing for duplicate implicit outputs while preserving round-trip validity. Changes are focused, well-tested, and maintain backward compatibility Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
|
Review follow-up status:
Validation on the updated branch: go test ./mdl/executor -run 'DuplicateOutput|StatementVarRefs' -count=1
make build
make test |
AI Code ReviewCritical Issues
Moderate Issues
Minor Issues
What Looks Good
Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
|
Follow-up observation from the local roundtrip audit: the Two cases (A, B) fan in to a common MDL does not have a native way to represent "shared subtree reachable from multiple cases" (no No action requested on this PR. |
40f77e6 to
b40e368
Compare
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: PR review pointed out that duplicate output variables are invalid Studio Pro models, but the branch silently rewrote later outputs to generated names such as $Item_2. Root cause: the builder carried variable aliasing state and planned some duplicate call outputs as non-returning variables, which made invalid authored MDL appear valid and made describe output transform the source model. Fix: remove the silent aliasing machinery, validate duplicate output variables as CE0111-style errors, keep describe output on the original variable names, and emit a warning comment when an existing MPR already contains duplicate output variables. Tests: added synthetic validation and describe-warning coverage, kept the shared variable-reference walker coverage, and ran make build && make test.
b40e368 to
13b259d
Compare
AI Code ReviewCritical Issues
Moderate Issues
Minor Issues
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.
AI Code ReviewLet me review this pull request according to the checklist provided. Summary of the PRThe PR is titled "fix: reject duplicate microflow output variables" and its body states:
The changes include:
Let me now review this against the checklist: Overlap & duplicationFirst, I need to check if there are existing proposals covering the same functionality. The PR mentions "Part of #332" but there's no proposals directory listed in the context, so I'll assume there are no conflicting proposals. Looking at the diff, I see changes to:
The core changes are in the microflow executor, specifically around validating duplicate output variables. Let me check if this duplicates existing functionality:
Looking at the doctype test files listed in the context, I don't see any that specifically test duplicate output variable handling, so this appears to be new functionality rather than duplicating existing tests. The nanoflow examples file (02b-nanoflow-examples.mdl) was significantly simplified, removing many examples. This seems to be cleanup unrelated to the main feature, but it's part of this PR. MDL syntax designThe PR doesn't appear to add or modify any MDL syntax (grammar rules, keywords, statement types). It's changing how existing syntax is validated and handled. Looking at the changes:
So the MDL syntax design checklist doesn't really apply here since no MDL syntax is being added or modified. Full-stack consistencySince this isn't adding new MDL syntax (just changing validation/error handling of existing syntax), the full-stack consistency requirements don't apply in the same way. However, let's check if the changes are properly layered:
This seems properly layered - validation in the builder/validator, output formatting in the show/describe functionality. Test coverageLet's check the test coverage:
The new test file specifically tests the duplicate output variable validation, which is good. It covers:
The changes to roundtrip_doctype_test.go show they're aware that some existing tests might now produce CE errors due to this change, and they've updated the known errors list accordingly. Security & robustnessLooking at the changes:
The validation now properly errors on duplicate variables instead of silently aliasing them, which is more robust. Scope & atomicityThe PR description says it's about "rejecting duplicate microflow output variables instead of silently generating aliases." Looking at the changes:
The TUI watcher changes, nanoflow examples simplification, and Java action visitor changes seem unrelated to the main feature. This violates the "each PR should be scoped to a single feature or concern" principle. However, let me check if these might be related:
But they're bundled with the main feature change, which makes the PR less focused. Code qualityLet's look for any issues:
The code quality of the main feature changes looks good. Bugs & correctnessLet's think about correctness: The main change is replacing silent aliasing with explicit validation errors for duplicate output variables. From the PR description:
This seems correct:
The implementation:
This looks correct. Now let me check if there are any issues:
Let me double-check if the Java action changes are actually related to the main feature. The main feature is about duplicate output variables in microflows/nanoflows. The Java action changes are about return type handling. These seem unrelated. Similarly, the TUI watcher changes are unrelated to MDL microflow validation. The nanoflow examples simplification is also not directly related to the duplicate output variable validation. This suggests the PR contains multiple unrelated changes, which violates the scoping principle. However, let me check if there's any connection I'm missing:
It appears these are unrelated changes bundled together. RecommendationThe core functionality (rejecting duplicate microflow output variables) is well-implemented and tested. However, the PR contains several unrelated changes:
According to the checklist:
The PR description only mentions the duplicate output variable functionality, but includes other unrelated changes. I would recommend:
Specifically:
Let me verify what exactly constitutes the core feature changes: From the diff, the core feature appears to be in:
The other changes seem unrelated. Actually, looking more carefully at the roundtrip test updates:
These updates to the roundtrip tests seem to be addressing issues that were uncovered by the nanoflow examples simplification or are unrelated to the main feature. Given that the nanoflow examples file was significantly changed, it's likely that the roundtrip test updates are related to those changes, not the duplicate output variable validation. Let me re-examine what exactly is needed for the duplicate output variable feature:
The roundtrip test updates are likely needed because the validation changes would cause previously passing tests to now fail with validation errors, so they need to be updated to reflect the new expected behavior (either fixing the test scripts or updating the known errors list). Looking at the roundtrip_doctype_test.go changes:
And in roundtrip_nanoflow_test.go:
These seem to be fixing issues in the test scripts themselves, not just updating known errors. This suggests the test scripts had bugs that were uncovered when running the tests, possibly unrelated to the main feature. Actually, looking at the nanoflow examples file that was simplified (02b-nanoflow-examples.mdl), it's likely that the roundtrip_nanoflow_test.go was using examples from that file, and when the examples were simplified, the test scripts needed to be updated. This reinforces that the nanoflow examples simplification and related test updates are unrelated to the main feature. Let me try to isolate just the core feature changes: Core feature (duplicate output variable validation):
Looking at roundtrip_nanoflow_test.go changes:
These appear to be fixing bugs in the test scripts themselves, likely unrelated to the duplicate output variable feature. So the core feature changes are:
Everything else appears to be unrelated changes bundled into this PR. Given the checklist item about scope and atomicity:
And the fact that the PR description only mentions the duplicate output variable functionality, I should request that the unrelated changes be split out. However, as a reviewer, I need to make a call on whether to approve or request changes. The core functionality looks correct and well-tested. The unrelated changes, while not ideal to bundle, don't appear to be harmful (they seem to be improvements or fixes). But following the contribution guidelines strictly, I should request changes to split the unrelated work into separate PRs. Let me check if there's any guidance in CLAUDE.md about bundled changes: From the "Scope & atomicity" section:
This clearly states that unrelated items should be split into separate PRs. The PR contains:
I should request changes to split these into separate PRs. However, let me consider if any of these could be argued as related:
Given the strict interpretation of the guidelines, I should request changes. But let me think about the practical impact:
However, as a code reviewer following the project's contribution guidelines, I need to enforce the scoping principle. Therefore, I will request changes to split the unrelated work into separate PRs. Actually, let me double-check the nanoflow examples simplification. Is it possible that this was done because the examples were demonstrating bad patterns that the new validation would catch? Looking at the before/after of 02b-nanoflow-examples.mdl: Before: Had examples like N002_ValidateProduct, N003_CountProducts, etc. The removed examples included things like:
Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
Summary
Rejects duplicate microflow output variables instead of silently generating aliases.
If an authored MDL script declares the same output variable twice in the same scope, validation now reports a duplicate-variable error. If
describeencounters an existing MPR that already contains duplicate output variables, it preserves the original output variable names and emits a warning comment instead of transforming the model to generated names such as$Item_2.Part of #332.
Changes
-- WARNING: duplicate output variable ... model is invalidin describe output for invalid existing MPRs.Validation
go test ./mdl/executor -run 'DuplicateOutput|StatementVarRefs' -count=1make buildmake test