Skip to content

Commit 2e80ca4

Browse files
authored
Merge pull request #521 from hjotha/submit/empty-java-action-list-arg
fix: emit "empty" keyword for typed Java action arguments
2 parents 32a4e41 + a867d18 commit 2e80ca4

4 files changed

Lines changed: 164 additions & 4 deletions

File tree

.claude/skills/fix-issue.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ to the symptom table below, so the next similar issue costs fewer reads.
2727
| MDL check gives "unexpected token" on valid-looking syntax | Grammar missing rule or token | `mdl/grammar/MDLParser.g4` + `MDLLexer.g4` | Add rule/token, run `make grammar` |
2828
| CE7054 "parameters updated" / CE7067 "does not support body entity" after `send rest request` | `addSendRestRequestAction` emitted wrong BSON: all params as query params, BodyVariable set for JSON bodies | `mdl/executor/cmd_microflows_builder_calls.go``addSendRestRequestAction` | Look up operation via `fb.restServices`; route path/query params with `buildRestParameterMappings`; suppress BodyVariable for JSON/TEMPLATE/FILE via `shouldSetBodyVariable` |
2929
| `CREATE X` returns "already exists — use create or replace to overwrite" but OR REPLACE is not valid for that type | Error message in executor points to wrong keyword | `mdl/executor/cmd_<type>_*.go` — find the `NewAlreadyExistsMsg` call | Change hint from `or replace` to `or modify`; verify the AST stmt uses `CreateOrModify` not `CreateOrReplace` |
30+
| `mx check` CE0126 "Missing value for parameter X" on `call java action ... ($Param = empty)` for typed (non-entity, non-microflow) parameters | Builder emitted `BasicCodeActionParameterValue.Argument: ""` instead of the literal `"empty"` keyword | `mdl/executor/cmd_microflows_builder_calls.go``addCallJavaActionAction` | Capture all resolved BasicParameterType params into `resolvedBasicParams`; when bound to MDL `empty`, emit `Argument: "empty"` so Studio Pro recognises an explicit empty literal rather than treating the slot as missing |
3031
| `DESCRIBE microflow` puts shared activities inside an `if … then` block — they should appear after `end if;` | Nested guard split inside `traverseFlowUntilMerge` crosses the outer merge boundary | `mdl/executor/cmd_microflows_show_helpers.go` — guard path in `traverseFlowUntilMerge` (~line 854) | Add `if contID != mergeID` guard before the `isMerge` skip-through so the guard continuation never crosses the outer merge |
3132

3233
---
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
-- ============================================================================
2+
-- Bug #521: `empty` literal on a typed Java action parameter triggers CE0126
3+
-- ============================================================================
4+
--
5+
-- Symptom (before fix):
6+
-- `mx check` reports CE0126 "Missing value for parameter X" on a
7+
-- `call java action Module.Foo(Param = empty)` when Param is a typed
8+
-- (non-entity, non-microflow) parameter — most commonly a list parameter
9+
-- or a String parameter. The builder emitted a BasicCodeActionParameterValue
10+
-- with `Argument: ""` instead of the literal `"empty"` keyword Studio Pro
11+
-- uses to mark an explicit empty literal. Studio Pro treats the empty
12+
-- string as a missing slot and fires CE0126 on validation.
13+
--
14+
-- After fix:
15+
-- When the backend resolves the Java action and the parameter is a
16+
-- BasicParameterType (StringType, ListType, ParameterizedEntityType, etc.),
17+
-- the builder emits `Argument: "empty"` for an MDL `empty` literal so
18+
-- Studio Pro reads the slot as an explicitly-empty value, not a missing
19+
-- one. Entity-typed parameters keep using EntityCodeActionParameterValue
20+
-- and microflow-typed parameters keep using MicroflowParameterValue, so
21+
-- this fix is scoped to BasicParameterType alone.
22+
--
23+
-- Validation:
24+
-- `mxcli check 521-empty-java-action-typed-arg.mdl` parses the script.
25+
-- `mx check` against the resulting MPR reports 0 errors for both the
26+
-- list-typed and string-typed `empty` arguments. Roundtrip preserves
27+
-- the `Argument: "empty"` BSON value byte-for-byte.
28+
--
29+
-- Usage:
30+
-- mxcli exec mdl-examples/bug-tests/521-empty-java-action-typed-arg.mdl -p app.mpr
31+
-- mxcli -p app.mpr -c "describe microflow BugTest521.MF_CallWithEmpty"
32+
-- ============================================================================
33+
34+
create module BugTest521;
35+
36+
create non-persistent entity BugTest521.Item (
37+
Code : string
38+
);
39+
/
40+
41+
-- Java action with a list parameter and a string parameter — both typed
42+
-- (BasicParameterType) so they exercise the resolved-basic-arg path.
43+
create java action BugTest521.JA_ProcessBatch(Items: list of BugTest521.Item not null, Tag: string not null) returns boolean
44+
as $$
45+
return true;
46+
$$;
47+
/
48+
49+
-- Calling with `empty` for both typed slots must produce
50+
-- BasicCodeActionParameterValue.Argument = "empty" so Studio Pro
51+
-- recognises the explicit empty literal and does not fire CE0126.
52+
create microflow BugTest521.MF_CallWithEmpty ()
53+
returns boolean
54+
begin
55+
$Result = call java action BugTest521.JA_ProcessBatch(
56+
Items = empty,
57+
Tag = empty)
58+
on error rollback;
59+
return $Result;
60+
end;
61+
/

mdl/executor/cmd_microflows_builder_calls.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,15 +241,32 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model.
241241
}
242242
}
243243

244-
// Build a map of parameter name -> param type for the Java action
244+
// Build a map of parameter name -> param type for the Java action.
245+
// resolvedBasicParams tracks parameters whose type was successfully
246+
// resolved via the Java action definition AND is not an entity-type or
247+
// microflow-type parameter (i.e. anything that lands in
248+
// BasicCodeActionParameterValue: String, Integer, Boolean, ListType,
249+
// ParameterizedEntityType, etc.). When the MDL author binds such a
250+
// parameter to `empty`, Studio Pro authors `Argument: "empty"` (the MDL
251+
// literal string) rather than `Argument: ""`, the unbound marker. The
252+
// distinction matters: `mx check` reports CE0126 "Missing value for
253+
// parameter X" when a typed parameter receives the unbound `""` shape.
254+
//
255+
// Without a backend lookup (jaDef == nil) we fall back to the prior
256+
// `""` behaviour to preserve the documented "intentionally unbound"
257+
// semantics of PROPOSAL_microflow_empty_java_action_argument.md.
245258
entityTypeParams := make(map[string]bool)
246259
microflowTypeParams := make(map[string]bool)
260+
resolvedBasicParams := make(map[string]bool)
247261
if jaDef != nil {
248262
for _, p := range jaDef.Parameters {
249-
if _, ok := p.ParameterType.(*javaactions.EntityTypeParameterType); ok {
263+
switch p.ParameterType.(type) {
264+
case *javaactions.EntityTypeParameterType:
250265
entityTypeParams[p.Name] = true
251-
} else if _, ok := p.ParameterType.(*javaactions.MicroflowType); ok {
266+
case *javaactions.MicroflowType:
252267
microflowTypeParams[p.Name] = true
268+
default:
269+
resolvedBasicParams[p.Name] = true
253270
}
254271
}
255272
}
@@ -285,9 +302,23 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model.
285302
Microflow: "",
286303
}
287304
} else {
305+
// When the Java action definition is available and the
306+
// parameter is a typed BasicParameterType (anything that
307+
// isn't entity-type or microflow-type — String, Integer,
308+
// Boolean, ListType, ParameterizedEntityType, etc.), Studio
309+
// Pro authors `Argument: "empty"` for the MDL `empty`
310+
// literal. Without that information (jaDef == nil) keep the
311+
// blank-string "intentionally unbound" marker that
312+
// PROPOSAL_microflow_empty_java_action_argument.md
313+
// established for code-action callers without backend
314+
// resolution.
315+
argument := ""
316+
if resolvedBasicParams[arg.Name] {
317+
argument = "empty"
318+
}
288319
value = &microflows.BasicCodeActionParameterValue{
289320
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
290-
Argument: "",
321+
Argument: argument,
291322
}
292323
}
293324
} else {

mdl/executor/cmd_microflows_builder_java_action_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,73 @@ func TestBuildJavaAction_EmptyArgumentPreservesEmptyBasicValue(t *testing.T) {
6161
}
6262
}
6363

64+
// TestBuildJavaAction_EmptyResolvedBasicArgumentEmitsEmptyKeyword pins
65+
// the BSON shape Studio Pro authors when a typed (non-entity-type,
66+
// non-microflow-type) Java action parameter is bound to MDL `empty`:
67+
// the BasicCodeActionParameterValue.Argument holds the literal string
68+
// "empty". Emitting the blank `""` for such a parameter triggers
69+
// `mx check` CE0126 "Missing value for parameter X" because the model
70+
// treats the parameter as missing rather than explicitly empty. The
71+
// behaviour applies regardless of the inner type (String, ListType,
72+
// ParameterizedEntityType, …) — the discriminator is whether the
73+
// backend resolved the parameter at all.
74+
func TestBuildJavaAction_EmptyResolvedBasicArgumentEmitsEmptyKeyword(t *testing.T) {
75+
cases := []struct {
76+
name string
77+
paramType javaactions.CodeActionParameterType
78+
}{
79+
{"list", &javaactions.ListType{Entity: "SampleModule.Tag"}},
80+
{"string", &javaactions.StringType{}},
81+
}
82+
for _, tc := range cases {
83+
t.Run(tc.name, func(t *testing.T) {
84+
fb := &flowBuilder{
85+
posX: 100,
86+
posY: 100,
87+
spacing: HorizontalSpacing,
88+
backend: &mock.MockBackend{
89+
ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) {
90+
if qualifiedName != "SampleModule.AddBatch" {
91+
t.Fatalf("java action lookup = %q", qualifiedName)
92+
}
93+
return &javaactions.JavaAction{
94+
Parameters: []*javaactions.JavaActionParameter{
95+
{Name: "Param", ParameterType: tc.paramType},
96+
},
97+
}, nil
98+
},
99+
},
100+
}
101+
stmt := &ast.CallJavaActionStmt{
102+
ActionName: ast.QualifiedName{Module: "SampleModule", Name: "AddBatch"},
103+
Arguments: []ast.CallArgument{
104+
{Name: "Param", Value: &ast.LiteralExpr{Kind: ast.LiteralEmpty}},
105+
},
106+
}
107+
108+
id := fb.addCallJavaActionAction(stmt)
109+
var activity *microflows.ActionActivity
110+
for _, obj := range fb.objects {
111+
if obj.GetID() == id {
112+
activity, _ = obj.(*microflows.ActionActivity)
113+
break
114+
}
115+
}
116+
if activity == nil {
117+
t.Fatal("expected Java action activity")
118+
}
119+
action := activity.Action.(*microflows.JavaActionCallAction)
120+
value, ok := action.ParameterMappings[0].Value.(*microflows.BasicCodeActionParameterValue)
121+
if !ok {
122+
t.Fatalf("mapping value = %T, want *BasicCodeActionParameterValue", action.ParameterMappings[0].Value)
123+
}
124+
if value.Argument != "empty" {
125+
t.Fatalf("resolved empty argument = %q, want %q", value.Argument, "empty")
126+
}
127+
})
128+
}
129+
}
130+
64131
func TestBuildJavaAction_EmptyMicroflowArgumentUsesMicroflowParameterValue(t *testing.T) {
65132
fb := &flowBuilder{
66133
posX: 100,

0 commit comments

Comments
 (0)