Repository navigation
alter microflow … replace loop: a find … by Member = value inside the new loop is stored as a find by an invalid expression (CE0117), because the loop body's builder does not inherit qualifiedMembersOnly #1673
Description
Summary
When alter microflow … { replace loop … with begin … end; } writes a loop whose body holds
$X = find $List by Member = value;, and the fragment does not know the entity of $List, the find is stored as
Microflows$FindByExpression with the bare text Member = value. check --references and exec report nothing, and
Studio Pro's mx check then fails with CE0117. In this case $List comes from a retrieve over an association that
stands before the loop.
The same find written by create microflow is stored as Microflows$Find on the attribute. The same find inserted
outside a loop with alter … insert after is refused with a clear message, which is the guard added by
ako#891. Inside a loop of the fragment, that guard does not apply.
Steps to reproduce
Setup:
mdl 1;
create module MyModule;
create persistent entity MyModule.Order (Code: String(50));
create persistent entity MyModule.OrderLine (Code: String(50), Status: String(20));
create association MyModule.OrderLine_Order from MyModule.OrderLine to MyModule.Order;
create non-persistent entity MyModule.Lookup (Code: String(50));
create microflow MyModule.SUB_MatchLines ($Order: MyModule.Order, $Lookups: List of MyModule.Lookup) returns Integer as $Found
begin
declare $Found Integer = 0;
retrieve $Lines from $Order/MyModule.OrderLine_Order;
loop $Lookup in $Lookups
begin
$Line = find $Lines by Code = $Lookup/Code;
if $Line != empty then
set $Found = $Found + 1;
end if;
end loop;
return $Found;
end;The alter replaces the loop with its own text:
mdl 1;
alter microflow MyModule.SUB_MatchLines {
replace loop $Lookup in $Lookups with begin
loop $Lookup in $Lookups
begin
$Line = find $Lines by Code = $Lookup/Code;
if $Line != empty then
set $Found = $Found + 1;
end if;
end loop;
end;
};Then:
mxcli -p app.mpr exec setup.mdl
mxcli -p app.mpr bson dump --type microflow --object MyModule.SUB_MatchLines
mxcli -p app.mpr check alter.mdl --references
mxcli -p app.mpr exec alter.mdl
mxcli -p app.mpr bson dump --type microflow --object MyModule.SUB_MatchLines
mx check -w app.mpr
Expected
Either the find is stored as before, Microflows$Find on MyModule.OrderLine.Code, or the alter is refused the way
an insert with the same find is refused, so that nothing invalid is written.
Actual
After the setup, the list operation of the find is:
"$Type": "Microflows$Find",
"Attribute": "MyModule.OrderLine.Code",
"Expression": "$Lookup/Code",
check alter.mdl --references prints "Check passed!", and exec alter.mdl prints "Altered microflow
MyModule.SUB_MatchLines" with exit code 0. Afterwards the list operation is:
"$Type": "Microflows$FindByExpression",
"Expression": "Code = $Lookup/Code",
and describe microflow prints $Line = find $Lines where Code = $Lookup/Code;. mx check -w reports one error:
[error] [CE0117] "Error(s) in expression." at List operation activity 'Find by expression'
The same app after the setup alone reports no error, and the warnings are the same, so the error comes from the
alter.
Controls (same app and module, measured in the same session)
-
$Linesas a parameter ($Lines: List of MyModule.OrderLine), same loop, same alter: stored as
Microflows$FindonMyModule.OrderLine.Code. -
$Linesfrom a database retrieve (retrieve $Lines from MyModule.OrderLine;), same loop, same alter: stored as
Microflows$FindonMyModule.OrderLine.Code. -
The same find after the association retrieve, inserted on the main line instead of inside a loop:
mdl 1; create microflow MyModule.SUB_FindOnMainLine ($Order: MyModule.Order, $Code: String) returns Boolean as $IsFound begin retrieve $Lines from $Order/MyModule.OrderLine_Order; declare $IsFound Boolean = false; return $IsFound; end; alter microflow MyModule.SUB_FindOnMainLine { insert after $Lines begin $Line = find $Lines by Code = $Code; end; };
is refused, exit code 1, and nothing is written:
Created microflow: MyModule.SUB_FindOnMainLine Error: alter microflow MyModule.SUB_FindOnMainLine: insert after $Lines: the fragment has errors: - cannot qualify member "Code": the entity its variable holds is not known here, and a bare attribute name would make the project unloadable ("not a valid AttributeIdentifier"); name it in full as Module.Entity.Code Nothing was written: statement 1 was rolled back, and the project is as it was before exec
Workaround
Name the member in full inside the fragment, $Line = find $Lines by MyModule.OrderLine.Code = $Lookup/Code;. The
alter then stores Microflows$Find on MyModule.OrderLine.Code, describe prints the short form
find $Lines by Code = $Lookup/Code again, and mx check reports no error.
Cause (read from the v0.28.0 source, not verified with a patched build)
ako#891 added qualifiedMembersOnly to flowBuilder (mdl/executor/cmd_microflows_builder.go), set for a
fragment spliced into a stored flow, so that a member the builder cannot qualify is refused instead of written bare.
addLoopStatement in mdl/executor/cmd_microflows_builder_control.go builds the loop body with a new builder that
shares varTypes, declaredVars, measurer, backend and others, but not that flag:
loopBuilder := &flowBuilder{
textLang: fb.textLang,
...
varTypes: fb.varTypes, // Share variable scope
declaredVars: fb.declaredVars, // Share declared vars (fixes nil map panic)
...
}So inside the loop the find falls back to the bare member, which is the find by an invalid expression that #891
describes. The builder of an error handler's body in addErrorHandlerFlow (cmd_microflows_builder_flows.go) does
not copy the flag either; a find inside a handler in a fragment was not measured.
A second, smaller point: the fragment does not know the entity of a list retrieved over an association, while it
knows it for a list parameter and for a database retrieve. That is what makes the member unresolvable here; the
insert above shows it as a refusal.
Suggested fix
Copy qualifiedMembersOnly into every nested builder (loop and while bodies, error handlers, and any other builder a
fragment creates), so that the guard of #891 holds at every depth. Separately, type the output of a retrieve over an
association in the variables a fragment starts from, so that this find can be written as Microflows$Find without
the full name.
Environment
- mxcli v0.28.0 (2026-10-23T14:29:27Z), Windows 11
- Mendix 11.12.4 (Studio Pro's bundled mxbuild and
mx check), MPR v2 - measured 2026-10-28; main after Sync ako/mxcli: loop-body splices and #1517 decisions, workflow texts and timers, Java action round trips, input widget drops #1660 (loop-body splices) was not measured
Related
- fix(flow-modify): a spliced member is written qualified — bare names made the project unloadable (#885) ako/mxcli#891 (merged, fixes CRITICAL: a create-or-modify splice writes bare attribute names in a change activity — the project no longer loads ako/mxcli#885): the guard against bare members in spliced fragments, which this case
bypasses. - Attribute-based find/filter list operations can roundtrip as invalid expressions #343 (closed): the same stored shape, a find by an invalid expression, from the full build before it learned
attribute finds. - create or modify / alter microflow splice refusals (loop body, used return, earlier alter, variable scope in one alter block) #1517 (open) and Sync ako/mxcli: loop-body splices and #1517 decisions, workflow texts and timers, Java action round trips, input widget drops #1660 (merged after v0.28.0): splices inside a loop body and
alter … replace body of.
Activity