Skip to content

Commit c7de926

Browse files
authored
Merge pull request #1691 from ako/fix/1557-microflow-splice-refusals
fix: microflow splice refusals — find/filter where respelling (#856), alter-block variables (#1399)
2 parents 302f7b3 + f200828 commit c7de926

11 files changed

Lines changed: 723 additions & 36 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"area": "mdl/executor", "date": "2026-10-25", "symptom": "(1) `alter microflow` with `insert after $X begin declare $Late Integer = 0; end;` and then `replace loop … with begin loop … set $Late = $Late + 1; … end;` passed check with a 'not predicted' note and failed at exec: 'the fragment has errors: variable 'Late' is not declared'. (2) A producer moved upstream — `create or modify` restating `$Code = call …` above the declare it followed, or the same as an alter insert+drop in either order — was refused 'the fragment declares $Code, which the microflow already has' (and, once past that, '$Code is still used by: log …; return $Code')", "cause": "Two variable verdicts of an alter block (and of the patch create or modify splices with, which is one) were still taken against the flow as stored: the fragment builder was seeded only with the stored flow's variables (storedVariables + the full build's entity types), so a later fragment could read but not `set` or member-access what an earlier operation declares; and checkFragmentScope refused a fragment declaring any variable a stored activity declares, at the moment it ran, before a later operation dropped that activity — while checkOutputUnused counted readers the inserted producer now feeds", "file": "mdl/executor/cmd_alter_flow.go (applyTo, fragmentBuilder, buildFragmentVars, noteFragment, checkOutputUnused, checkDeclaredUnclashed); mdl/executor/validate_flow_verdicts.go (alterFlowViolations)", "fix": "Each applied fragment's declared variables (with the types its builder resolved) seed later fragments' builders, in check's pre-build as in exec; the clash with a stored producer is judged once every operation is applied and skips one the block takes away (or whose loop it replaces); a stored or fragment reader of a removed output is no 'user' when another operation's fragment declares the variable upstream of it (declaredOnPath)", "insight": "2f42df658 moved checkOutputUnused to the end of the block but left its two siblings per-operation; any check that answers 'does this variable exist' has to be judged against the flow the block writes, or create or modify's own diff (which turns a reorder into insert+drop) trips it. A useful corollary: a create-or-modify body that builds declares each variable once, so a 'declares $X, which the microflow already has' refusal from the splice is always false — the 1361 test pinned one as expected and now asserts the splice. Known order rule kept: a declaration by a LATER operation is not known to an earlier fragment (check reports it unpredicted, exec refuses it)", "refs": ["ako/mxcli#1399", "mendixlabs/mxcli#1517", "mendixlabs/mxcli#1361"], "test": "TestAlterMicroflow_FragmentBuiltWithAnEarlierOperationsDeclaration, TestCreateOrModify_ProducerMovedEarlierKeepsItsReaders, TestAlterMicroflow_ProducerMovedEarlier (mdl/executor/cmd_alter_flow_block_vars_1399_pedapp_test.go); TestCheckFlowVerdicts_Issue1361_VariableClash"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"area": "mdl/executor", "date": "2026-10-25", "symptom": "`create or modify microflow` of a flow holding `$A = find $L where Code = 'x'` (or `filter … where` with a bare member) and one real change next to it was refused under mdl 1 — 'replace $N: the fragment declares $A, which the microflow already has' — and rebuilt whole (MDL-V1-REBUILD) under mdl 0; away from the change the find was needlessly replaced on every run. An unchanged re-run was still 'Unchanged'", "cause": "The builder stores a bare member of a find/filter-by-expression predicate as `$currentObject/<member>` (qualifyIteratorAttributes, #1002) and describe prints the stored form, so the statement as the script spells it never matched its own stored activity in the statement diff; the unchanged re-run escaped only because builtAsStored compares built graphs before the statement diff runs", "file": "mdl/executor/flow_member_spelling.go (memberSpeller.respell, qualifiedPredicate)", "fix": "describedMemberSpellings rewrites a find/filter-by-expression predicate with the builder's own qualifyIteratorAttributes before the statement diff, keeping the predicate as written when the builder would refuse it", "insight": "A spelling the builder normalises is invisible to the identity check (an unchanged re-run passes) and only shows when a REAL change elsewhere sends the flow to the statement diff, so the probe that finds these is: create each flow, re-run it with one inserted statement, and flag any summary other than '1 inserted'. Over 02-microflow-examples (118 flows) and 23 hand-picked respellings this was the only clause left; sort without asc, member qualification, association spelling in expressions, `<>`, aggregates (stored as written) all matched", "refs": ["ako/mxcli#856", "#1002", "#839"], "test": "TestCreateOrModify_ListOperationWhereBareMemberIsItsStoredActivity, TestCreateOrModify_ListOperationWhereChangedPredicateIsAChange (mdl/executor/flow_splice_list_op_where_856_pedapp_test.go)"}

‎.claude/skills/mendix/write-microflows/reference/pitfalls.md‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,10 +362,16 @@ the scope checks judge the flow the **whole block** writes:
362362

363363
- a fragment may read a variable an earlier operation of the block declares, when that
364364
declaration lands upstream of it (an `insert after`/`replace` of a node before it, or
365-
an `insert before` the node it replaces or inserts after);
365+
an `insert before` the node it replaces or inserts after) — and may `set` it or name
366+
its members, since the fragment is built knowing what the earlier operations declare
367+
(ako/mxcli#1399). Declare first, use after: a declaration made by a *later*
368+
operation of the block is not known to an earlier one's fragment;
366369
- an activity whose output is taken away is still "used" only by readers the block
367-
leaves in place — so a rename (the producer, every reader and the `return`) goes in
368-
**one** block:
370+
leaves in place, and not by a reader that another operation's fragment declares the
371+
variable for upstream of it — so a producer can be moved up the flow (insert it
372+
where it goes, drop it where it was; `create or modify` does this for a reordered
373+
body), and a rename (the producer, every reader and the `return`) goes in **one**
374+
block:
369375

370376
```mdl
371377
mdl 1;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
-- ============================================================================
2+
-- ako/mxcli#1399 (mendixlabs/mxcli#1517) — variables of one alter block,
3+
-- and a producer moved upstream of where it was
4+
-- ============================================================================
5+
--
6+
-- The operations of one `alter microflow` block — and the patch a
7+
-- `create or modify` splices with, which is one — write ONE flow, but two of
8+
-- their variable verdicts were still taken against the flow as stored:
9+
--
10+
-- Repro B a fragment was built knowing only the stored flow's variables,
11+
-- so `set $Late = …` in a replaced loop failed at exec with
12+
-- "variable 'Late' is not declared" although the insert before it
13+
-- in the same block declares $Late upstream — while check passed
14+
-- with a "not predicted" note.
15+
--
16+
-- Used return a producer moved earlier (`$Code = call …` above the
17+
-- declare it followed) was refused "the fragment declares $Code,
18+
-- which the microflow already has" by the stored $Code the same
19+
-- patch drops, and in the other order "$Code is still used by:
20+
-- log …; return $Code" by readers the moved producer now feeds.
21+
--
22+
-- To reproduce: exec this file against any project. Expected:
23+
-- Altered microflow BugTest1399.LoopFlow
24+
-- Modified microflow: BugTest1399.MoveFlow (spliced: 1 inserted, 1 dropped)
25+
-- Before the fix exec stopped at the alter ("the fragment has errors: variable
26+
-- 'Late' is not declared") and rolled the whole script back; without the
27+
-- alter it stopped at the second MoveFlow statement. mx check: 0 errors.
28+
-- The file can be run again: LoopFlow is dropped and made afresh, and
29+
-- MoveFlow moves its producer back and forth.
30+
31+
mdl 1;
32+
33+
create module BugTest1399;
34+
35+
create or modify persistent entity BugTest1399.Item (
36+
Name: String(100)
37+
);
38+
39+
create or modify microflow BugTest1399.Helper () returns String
40+
begin
41+
return 'x';
42+
end;
43+
44+
-- Repro B: the replaced loop sets the variable the insert declares.
45+
drop microflow if exists BugTest1399.LoopFlow;
46+
47+
create microflow BugTest1399.LoopFlow ($Items: List of BugTest1399.Item)
48+
begin
49+
declare $X Integer = 0;
50+
loop $It in $Items
51+
begin
52+
log info node 'BugTest1399' $It/Name;
53+
end loop;
54+
log info node 'BugTest1399' 'done';
55+
end;
56+
57+
alter microflow BugTest1399.LoopFlow {
58+
insert after declare $X Integer = 0 begin declare $Late Integer = 0; end;
59+
replace loop $It in $Items with begin
60+
loop $It in $Items
61+
begin
62+
set $Late = $Late + 1;
63+
end loop;
64+
end;
65+
};
66+
67+
-- Used return: the producer of the returned $Code moves above the declare.
68+
create or modify microflow BugTest1399.MoveFlow () returns String
69+
begin
70+
declare $N Integer = 1;
71+
$Code = call microflow BugTest1399.Helper();
72+
log info node 'BugTest1399' $Code;
73+
return $Code;
74+
end;
75+
76+
create or modify microflow BugTest1399.MoveFlow () returns String
77+
begin
78+
$Code = call microflow BugTest1399.Helper();
79+
declare $N Integer = 1;
80+
log info node 'BugTest1399' $Code;
81+
return $Code;
82+
end;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
-- ============================================================================
2+
-- ako/mxcli#856 — a find/filter `where` naming a member bare never matched
3+
-- its own stored activity, so a one-line change next to it was refused
4+
-- ============================================================================
5+
--
6+
-- `find $L where Code = 'x'` is stored as `$currentObject/Code = 'x'` (Mendix
7+
-- evaluates the predicate once per item, and a bare name is CE0117), and
8+
-- describe prints the stored form. create or modify matches the statements it
9+
-- is given against that description, so the statement as written read as
10+
-- changed on every run:
11+
--
12+
-- - a re-run that changed nothing was still Unchanged (the built flow is
13+
-- the stored one);
14+
-- - a re-run with a real change elsewhere replaced the find too, and next
15+
-- to the change the two became one replace whose fragment declares $A
16+
-- while the stored find still does. Under `mdl 1;`:
17+
--
18+
-- this change cannot be spliced into the stored flow: alter microflow
19+
-- BugTest856.F: replace $N: the fragment declares $A, which the
20+
-- microflow already has; choose another name. Nothing was written
21+
--
22+
-- and under mdl 0 the whole flow was rebuilt (MDL-V1-REBUILD warning).
23+
--
24+
-- The predicate is now compared as the builder stores it.
25+
--
26+
-- To reproduce: exec this file against a project. Expected:
27+
-- Created microflow: BugTest856.F
28+
-- Modified microflow: BugTest856.F (spliced: 1 replaced)
29+
-- Before the fix the second statement was refused and exec rolled the whole
30+
-- script back. Running the file again sets the declare back and forth, and
31+
-- each statement again replaces the declare alone (spliced: 1 replaced).
32+
33+
mdl 1;
34+
35+
create module BugTest856;
36+
37+
create or modify persistent entity BugTest856.Row (
38+
Code: String(100),
39+
Qty: Integer
40+
);
41+
42+
create or modify microflow BugTest856.F ($L: List of BugTest856.Row)
43+
begin
44+
declare $N Integer = 0;
45+
$A = find $L where Code = 'x';
46+
$B = filter $L where Code != 'y' and Qty > 0;
47+
log info node 'BugTest856' toString($N);
48+
end;
49+
50+
-- The same flow with one change, next to the find: only the declare is
51+
-- replaced; the find and the filter keep their $IDs.
52+
create or modify microflow BugTest856.F ($L: List of BugTest856.Row)
53+
begin
54+
declare $N Integer = 1;
55+
$A = find $L where Code = 'x';
56+
$B = filter $L where Code != 'y' and Qty > 0;
57+
log info node 'BugTest856' toString($N);
58+
end;

0 commit comments

Comments
 (0)