Skip to content

Commit 8af0836

Browse files
authored
Merge pull request #469 from hjotha/submit/merged-pr-ako-polish
fix: address merged-PR ako follow-up polish items
2 parents 4c8404d + cb83b16 commit 8af0836

6 files changed

Lines changed: 128 additions & 27 deletions

mdl/executor/cmd_microflows_builder_actions.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,13 @@ func (fb *flowBuilder) addRollbackAction(s *ast.RollbackStmt) model.ID {
227227

228228
// addChangeObjectAction creates a CHANGE statement.
229229
func (fb *flowBuilder) addChangeObjectAction(s *ast.ChangeObjectStmt) model.ID {
230-
// Empty non-committing changes need RefreshInClient to satisfy Studio Pro
231-
// consistency checks; explicit `refresh` keeps the same flag for all changes.
230+
// CE0032 rejects change actions with no items that do not commit the
231+
// object. The published error text only mentions items/commit, but
232+
// `mx check` also accepts RefreshInClient=true as a third valid escape.
233+
// The builder auto-promotes empty changes to refresh-only so describe →
234+
// exec of such actions stays valid without requiring authored MDL to say
235+
// `refresh` explicitly; when the author wrote `refresh`, we keep the
236+
// same flag for non-empty changes too.
232237
action := &microflows.ChangeObjectAction{
233238
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
234239
ErrorHandlingType: fb.ehType(nil),

mdl/executor/cmd_microflows_builder_calls.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -357,28 +357,28 @@ func javaActionReturnVarType(returnType javaactions.CodeActionReturnType) string
357357
return ""
358358
}
359359

360+
// inferGenericJavaActionReturnType infers the element type of a generic
361+
// `ListType{Entity: ""}` Java action return by inspecting the caller's
362+
// variable-typed arguments. When the action parameters don't determine an
363+
// element type, the function returns "" and the caller records the declaration
364+
// as Unknown.
360365
func (fb *flowBuilder) inferGenericJavaActionReturnType(jaDef *javaactions.JavaAction, s *ast.CallJavaActionStmt) string {
361366
if jaDef == nil || fb.varTypes == nil || s == nil {
362367
return ""
363368
}
364-
switch t := jaDef.ReturnType.(type) {
365-
case *javaactions.ListType:
366-
if t.Entity != "" {
367-
return ""
368-
}
369-
case javaactions.ListType:
370-
if t.Entity != "" {
371-
return ""
372-
}
373-
default:
369+
// ListType is always stored as a pointer by the parser; there is no value
370+
// form in the SDK. Only the generic (Entity == "") case reaches the
371+
// variable-type lookup below.
372+
t, ok := jaDef.ReturnType.(*javaactions.ListType)
373+
if !ok || t.Entity != "" {
374374
return ""
375375
}
376376
for _, arg := range s.Arguments {
377-
valueExpr := strings.TrimPrefix(strings.Trim(fb.exprToString(arg.Value), "'"), "$")
378-
if valueExpr == "" {
377+
varExpr, ok := arg.Value.(*ast.VariableExpr)
378+
if !ok {
379379
continue
380380
}
381-
if typ := fb.varTypes[valueExpr]; strings.HasPrefix(typ, "List of ") {
381+
if typ := fb.varTypes[varExpr.Name]; strings.HasPrefix(typ, "List of ") {
382382
return typ
383383
}
384384
}

mdl/executor/cmd_microflows_builder_flows.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -783,12 +783,15 @@ func isTerminalStmt(stmt ast.MicroflowStatement) bool {
783783
return false
784784
}
785785
}
786-
// Both paths return true intentionally. isTerminalStmt diverges from
787-
// bodyReturns here: bodyReturns requires an ELSE to guarantee all paths
788-
// return (a valid Mendix requirement), but isTerminalStmt only needs to
789-
// know whether the flow builder must thread a continuation edge past this
790-
// statement. When every explicit case terminates the split has no outgoing
791-
// merge edge regardless of whether an ELSE exists, so we are always terminal.
786+
// Every reachable branch terminates, so the split has no continuation
787+
// to thread into the parent flow. This intentionally diverges from
788+
// `bodyReturns` in validate_microflow.go: that predicate treats an
789+
// enum split without an `else` as non-terminal because authored MDL
790+
// is expected to supply a default branch covering missing values.
791+
// Here we also accept described-from-MPR graphs where Studio Pro
792+
// produced an exhaustive set of value cases without a default flow —
793+
// in both no-else and with-else forms the split terminates once we
794+
// reach this point.
792795
return true
793796
default:
794797
return false

mdl/executor/cmd_microflows_builder_java_return_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,37 @@ func TestAddJavaAction_ConcreteListReturnRegistersListType(t *testing.T) {
6363
}
6464
}
6565

66+
// TestAddJavaAction_EntityReturnRegistersEntityType pins ako's review follow-up
67+
// for PR #357: the most common Java action return shape — a bare
68+
// `*javaactions.EntityType{Entity: "Mod.Ent"}` — must register the output
69+
// variable as the entity's qualified name so downstream attribute access
70+
// resolves against the right domain-model entry.
71+
func TestAddJavaAction_EntityReturnRegistersEntityType(t *testing.T) {
72+
backend := &mock.MockBackend{
73+
ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) {
74+
return &javaactions.JavaAction{
75+
ReturnType: &javaactions.EntityType{Entity: "Orders.Order"},
76+
}, nil
77+
},
78+
}
79+
80+
fb := &flowBuilder{
81+
backend: backend,
82+
varTypes: map[string]string{},
83+
declaredVars: map[string]string{},
84+
measurer: &layoutMeasurer{},
85+
}
86+
87+
fb.addCallJavaActionAction(&ast.CallJavaActionStmt{
88+
OutputVariable: "CreatedOrder",
89+
ActionName: ast.QualifiedName{Module: "Orders", Name: "CreateFromPayload"},
90+
})
91+
92+
if got := fb.varTypes["CreatedOrder"]; got != "Orders.Order" {
93+
t.Fatalf("CreatedOrder type = %q, want Orders.Order", got)
94+
}
95+
}
96+
6697
func TestAddJavaAction_GenericListReturnInheritsInputListType(t *testing.T) {
6798
backend := &mock.MockBackend{
6899
ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package executor
4+
5+
import (
6+
"testing"
7+
8+
"github.com/mendixlabs/mxcli/sdk/microflows"
9+
)
10+
11+
// TestHasExplicitFalseBranchAnchor pins ako's review follow-up for PR #364:
12+
// the heuristic that decides whether a false-branch flow carries an explicit
13+
// anchor annotation should fire only on the AnchorTop→AnchorBottom pair and
14+
// must stay dormant for nil flows, builder defaults, and any other side
15+
// combination.
16+
func TestHasExplicitFalseBranchAnchor(t *testing.T) {
17+
tests := []struct {
18+
name string
19+
flow *microflows.SequenceFlow
20+
want bool
21+
}{
22+
{name: "nil flow", flow: nil, want: false},
23+
{
24+
name: "builder default right→left",
25+
flow: &microflows.SequenceFlow{OriginConnectionIndex: AnchorRight, DestinationConnectionIndex: AnchorLeft},
26+
want: false,
27+
},
28+
{
29+
name: "split default bottom→top",
30+
flow: &microflows.SequenceFlow{OriginConnectionIndex: AnchorBottom, DestinationConnectionIndex: AnchorTop},
31+
want: false,
32+
},
33+
{
34+
name: "authored top→bottom",
35+
flow: &microflows.SequenceFlow{OriginConnectionIndex: AnchorTop, DestinationConnectionIndex: AnchorBottom},
36+
want: true,
37+
},
38+
{
39+
name: "only origin customised",
40+
flow: &microflows.SequenceFlow{OriginConnectionIndex: AnchorTop, DestinationConnectionIndex: AnchorTop},
41+
want: false,
42+
},
43+
{
44+
name: "only destination customised",
45+
flow: &microflows.SequenceFlow{OriginConnectionIndex: AnchorBottom, DestinationConnectionIndex: AnchorBottom},
46+
want: false,
47+
},
48+
}
49+
50+
for _, tc := range tests {
51+
t.Run(tc.name, func(t *testing.T) {
52+
got := hasExplicitFalseBranchAnchor(tc.flow)
53+
if got != tc.want {
54+
t.Fatalf("hasExplicitFalseBranchAnchor(%s) = %v, want %v", tc.name, got, tc.want)
55+
}
56+
})
57+
}
58+
}

mdl/executor/cmd_microflows_show_helpers.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,12 +1473,16 @@ func branchFlowStartsAtTerminal(flow *microflows.SequenceFlow, activityMap map[m
14731473
}
14741474
}
14751475

1476-
// hasExplicitFalseBranchAnchor reports whether a flow is the false-branch anchor
1477-
// of a boolean ExclusiveSplit (origin=top, destination=bottom). This heuristic
1478-
// distinguishes boolean false-branch flows from enum-split flows inside isGuard,
1479-
// because both share a nil CaseValue but differ in their anchor positions.
1480-
// Limitation: a custom @anchor that happens to use (top, bottom) would be
1481-
// misclassified; this is accepted because it is an unlikely user choice.
1476+
// hasExplicitFalseBranchAnchor reports whether a false-branch sequence flow
1477+
// carries anchor metadata that the user explicitly authored. Top→Bottom is
1478+
// the non-default pair produced by `@anchor(false: (from: top, to: bottom))`;
1479+
// any other combination is either a builder default or a different author
1480+
// intention that should not trigger the guard-pattern describer.
1481+
//
1482+
// Used by the `isGuard` paths in traverseFlow / traverseFlowUntilMerge to
1483+
// distinguish a real guard continuation from a branch whose layout should
1484+
// stay visible as an explicit `else` in the described MDL. See
1485+
// TestHasExplicitFalseBranchAnchor for the exhaustive cases.
14821486
func hasExplicitFalseBranchAnchor(flow *microflows.SequenceFlow) bool {
14831487
if flow == nil {
14841488
return false

0 commit comments

Comments
 (0)