MDL list operations as one statement per Studio Pro activity; set mandatory under mdl 1 (#733) - #737
Merged
Merged
Conversation
…t mandatory under mdl 1 Each Studio Pro List operation / Aggregate list activity is one statement whose operand is a variable, so nesting cannot be written: $Open = filter $Orders by Status = M.Status.Open; $Big = filter $Orders where $currentObject/Total > 1000; $N = count $Open; $Csv = reduce $Orders from '' as String using $currentResult + $currentObject/Name; The statement forms parse under every version. The call forms keep parsing: a respelling everywhere except find/contains, registered as MDL-DEPR003 (list operations) and MDL-DEPR004 (aggregates), both building the same AST. find(...)/contains(...) clash with the string functions, so they, a list call after `set`, and a nested call are version-gated (MDL-V1-LIST): kept and warned under mdl 0, refused under mdl 1, where `set $x = find(...)` is always the string function. A reassignment without `set` is refused under mdl 1 and warned under mdl 0 (MDL-V1-SET). `where` is always Find/Filter by expression; `by` and the call form keep the by-member reading when the condition is `Member = value` (ast.IsMemberEquality, shared by visitor and flow builder). Part of #733. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
describe writes the language of the newest frozen version, or of the script it runs in when that is newer. While mdl 1 is a preview a plain describe keeps the call form; inside an `mdl 1;` script it prints the statement form, which executes back to the same microflow (PedApp test). Part of #733. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…forms Part of #733. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lementIDs, Writer.UpdateRawUnitPatch) TransplantIDs pairs elements by type and position, which re-pairs the surviving flows of a patched microflow onto their neighbours' $IDs after a drop. A write that started from the stored bytes skips it; elision and the storage-GUID guard still apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e, replace, drop) Splices a fragment into the raw stored unit: appends only the fragment's objects and flows, rewires the flow around the target by its pointers and the rewired end, moves nodes past the insertion point to make room, and never rewrites an $ID. Save refuses a unit in which anything still points at a removed element or two elements share an $ID. Refuses what it cannot do safely: after a decision, before a join, inside a loop body, drop/replace of a decision or of an activity with an error handler, and a placement that would overlap an object. The modelsdk backend writes through UpdateRawUnitPatch (canon.Reconcile). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#736) alter microflow|nanoflow M.F { insert after|before <target> { … } replace <target> with { … } drop <target>; }. Targets are the #713 content addresses, resolved against the stored flow before any operation runs. Fragments are built with the create-microflow builder, seeded with the flow's variables, and cut out of their start and end events. A fragment variable that clashes with one the flow has, or one it reads that is not declared upstream of the insertion point, is an error; so is dropping an activity whose output is still read. Acceptance on PedApp VAL_Feedback: only the new log, the two flows around it and the shifted positions differ; an empty alter writes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… handle
A handle has to parse as an alter target, and a target ends at the { of a
fragment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runs the shared splice on the stored flow and sends the difference as ped_update_document path operations in one update, after checking the live document still matches the .mpr (PED addresses entries by index). Drop and replace are refused: PED does not roll back a removal when an update fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Loop body flows are stored in the unit's Flows list, not in the loop, so removing a Studio Pro loop with two or more body activities left them pointing at removed objects and Save refused the unit (TestApp ACT_ConflictedWorkflowHelper_ApplyJumpTo). mx check 11.14 on the dropped and replaced copies gives the baseline error list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each operation was checked against the flow as stored only. Measured with mx check 11.14 on TestApp: two fragments declaring the same variable gave CE0111, and a fragment reading a variable a drop in the same statement removed gave CE0109, after "Altered microflow". The context now tracks what earlier operations declared, read and removed. Also count a fragment loop's iterator as the fragment's own, so loop fragments are no longer refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 27, 2026
Closed
This was referenced Sep 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #733 (plan item 2.4 of PROPOSAL_mdl_beta_syntax_freeze.md; ADR-0010 R2/R12, ADR-0011).
What changes
List operations and aggregates are statements, one per Studio Pro activity. The operand is always a variable, so nesting cannot be written:
$x = head($L)… (not find/contains)$x = find(…)/$x = contains(…)set $x = find(…)/contains(…)MfSetStmt)set(count(filter(…)),set $x = head($L))$x = <expr>withoutset(variable target)describeprints the statement forms only under mdl 1: it writes the newest frozen version (langver.Frozen, still V0) or, when it runs inside a script with a newer header, that version. So a plaindescribekeeps today's call forms while mdl 1 is a preview, andmdl 1; describe microflow X;prints the new forms.MDL-LISTOP02 is unreachable under mdl 1 (nesting is refused by the parser) and stays for mdl 0 scripts only; documented on the rule.
Vocabulary check against Studio Pro (scope item 1)
Checked against docs.mendix.com (
refguide/list-operation,refguide/aggregate-list, raw source from mendix/docs) and the metamodel inmodelsdk/gen/microflows. Operation names match the §4 table exactly (Union, Intersect, Subtract, Contains, Equals, Sort, Filter, Filter by expression, Find, Find by expression, Head, Tail, Range; Sum, Average, Count, Minimum, Maximum, All, Any, Reduce). The dialogs show field labels, not linking words, soby/where/of/with/from/in/andare not contradicted by any label. Deviations and notes:offset … limit …so it reads the same asretrieve … limit … offset …(one word for one concept, R8);amountis not accepted. Flagging it in case the "inputs are the ones the dialog asks for" rule should win.by <Attr>/of <expr>follow the proposal.reduce $L from <initial> as <type> using <expr>, as in the proposal. It needed one new keyword,USING(added to the keyword rule, so it is still usable as an identifier).contains $Object in $List.subtract $B from $A= A − B,ListVariable1 = A.by Member = valueaccepts an association too.Design choices the ADRs did not settle
byvswhereand the call form.ListOperationStmt.ByExpressionis set bywhere. The call form sets it by the shape the flow builder already used (Member = value→ by member, anything else → by expression), sofilter($L, c)is an exact respelling offilter $L by …or… where …(same AST).where Status = 'x'is now always Filter by expression;ast.IsMemberEqualityis the one predicate the visitor and the builder share.bymust beMember = value; anything else is an error pointing atwhere. An unresolvable member afterbystill falls back to the expression operation (unchanged behaviour of the call form).scriptLanguageVersionreads the header off theProgramContext), because they are free functions without theBuilder. Warnings and errors go throughBuilder.gateinExitSetStatement/ExitListOperationStatement/ExitAggregateListStatement.Rewritegains aStructuralform, because a call form to statement form rewrite is not a keyword swap.TestRegistryExamplesRecordTheirCodestill proves it by AST equality.fmt --upgradedoes not exist yet, so nothing applies it.$o/Attr = vwithoutsetis not gated: it is thechangealias (Phase 3), not a reassignment.Test plan (what I ran)
make build,make lint: pass.go test ./mdl/... ./cmd/mxcli/...: pass (the orchestrator runs the whole repo).go test -tags integration ./mdl/roundtrip/: pass; allowlist untouched (no entry fixed or added).mdl/visitor/visitor_list_activity_test.go: every §4 row under both versions; call form = statement form AST for each operation plus the deprecation code; find/contains gated;set $x = find(…)isMfSetStmtunder mdl 1 (control: list op + note under mdl 0); nested call refused under mdl 1;setmandatory (with no-warning controls);byshape.mdl/executor/cmd_microflows_format_list_activity_test.go: describe of every operation and aggregate under mdl 1, with a control that mdl 0 / nil context keeps the call form; the output parses back with no warnings.mdl/executor/cmd_microflows_builder_list_activity_test.go:where Status = 'Open'builds FilterOperation, with a control thatbybuilds FilterByAttribute.mdl/roundtrip/list_activity_test.go(integration, PedApp): every §4 row executes, describes back verbatim undermdl 1;, and describe → exec → describe is stable. Control: a plain describe still prints the call form.s.ByExpressionfromlistAttributeOperation→TestFilterWhereIsAlwaysByExpressionfails (built FilterByAttribute);describeLanguageignore the script version → 20TestDescribeListActivityUnderMdl1cases fail;buildSetStatementNode→TestSetFindIsTheStringFunctionUnderMdl1fails;Exit…gating methods → the gating, alias and registry-example tests fail (TestSetIsMandatoryUnderMdl1,TestNestedListOperationRefusedUnderMdl1,TestFindContainsFunctionFormIsVersionGated,TestListFunctionFormIsAnAlias,TestFilterByNeedsMemberEquality,TestRegistryExamplesRecordTheirCode/MDL-DEPR003|004).mxcli docker check: 1002 before and after; the fixture has no theme, so CE6083 appears everywhere. Control: a microflow with an undefined list variable raises the count to 1003.describeunder mdl 1 → exec under mdl 1 vsdescribe→ exec under mdl 0 on a separate copy. The resulting describe output is identical for 41 of 41, andmxcli docker checkgives the same single pre-existing error on both copies.MyFirstModule.MDL733_ListActivities3with every row except two. The MCP backend does not supportrangeyet ("list operation ListRangeOperation is not yet supported by the MCP backend"), and PED's schema refusesReduceon create ("Expected one of [Sum, …, Any]"). Both are backend gaps, not grammar problems. Three scratch microflowsMDL733_ListActivities{,2,3}are left unsaved in the open Studio Pro; there is no delete or save tool without Concord.Follow-ups (not in this PR)
fmt --upgradeimplementation of the MDL-DEPR003/004 structural rewrite, plus adding the header.MDL_QUICK_REFERENCE.mdand CLAUDE.md idiom 3 still show the call forms.$a/X * $b/Yis stored as$a/X * $b / Yin any expression, with the call form or the new one (filed as a separate task).🤖 Generated with Claude Code