From c0a110596d3be95604b6dc57c6d5b2dcf583b95c Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Tue, 5 May 2026 10:46:18 +0200 Subject: [PATCH 1/4] fix: emit "empty" keyword for list-typed Java action arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a Java action parameter is list-typed and the MDL author binds it to `empty`, Studio Pro authors the BSON BasicCodeActionParameterValue with Argument: "empty" — the literal MDL keyword. The current builder collapses every empty primitive binding to Argument: "" (the unbound marker), regardless of parameter type. For list-typed parameters that substitution triggers `mx check` CE0126 "Missing value for parameter X" because the model treats the parameter as missing rather than explicitly empty. Detect list-typed parameters via the Java action definition (*javaactions.ListType) when looking up the action; for those parameters bound to `empty` emit Argument: "empty". Primitive parameters keep the existing blank-string behaviour. The describer already maps both to the MDL `empty` keyword so round-trip stays symmetric. Co-Authored-By: Claude Opus 4.7 (1M context) --- mdl/executor/cmd_microflows_builder_calls.go | 24 ++++++-- ...cmd_microflows_builder_java_action_test.go | 57 +++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/mdl/executor/cmd_microflows_builder_calls.go b/mdl/executor/cmd_microflows_builder_calls.go index c5a7f666..74fbaf3f 100644 --- a/mdl/executor/cmd_microflows_builder_calls.go +++ b/mdl/executor/cmd_microflows_builder_calls.go @@ -241,15 +241,24 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model. } } - // Build a map of parameter name -> param type for the Java action + // Build a map of parameter name -> param type for the Java action. + // listTypeParams tracks list-typed parameters: when bound to `empty` in + // MDL, Studio Pro authors them as `Argument: "empty"` (the MDL literal + // string) rather than `Argument: ""`, the unbound primitive marker. The + // distinction matters: `mx check` reports CE0126 "Missing value for + // parameter X" when a list-typed parameter receives `Argument: ""`. entityTypeParams := make(map[string]bool) microflowTypeParams := make(map[string]bool) + listTypeParams := make(map[string]bool) if jaDef != nil { for _, p := range jaDef.Parameters { - if _, ok := p.ParameterType.(*javaactions.EntityTypeParameterType); ok { + switch p.ParameterType.(type) { + case *javaactions.EntityTypeParameterType: entityTypeParams[p.Name] = true - } else if _, ok := p.ParameterType.(*javaactions.MicroflowType); ok { + case *javaactions.MicroflowType: microflowTypeParams[p.Name] = true + case *javaactions.ListType: + listTypeParams[p.Name] = true } } } @@ -285,9 +294,16 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model. Microflow: "", } } else { + // List-typed parameters bound to `empty` keep the literal + // "empty" expression in BSON; primitive parameters use the + // blank Argument as their unbound marker. + argument := "" + if listTypeParams[arg.Name] { + argument = "empty" + } value = µflows.BasicCodeActionParameterValue{ BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, - Argument: "", + Argument: argument, } } } else { diff --git a/mdl/executor/cmd_microflows_builder_java_action_test.go b/mdl/executor/cmd_microflows_builder_java_action_test.go index aabcddbd..589d1438 100644 --- a/mdl/executor/cmd_microflows_builder_java_action_test.go +++ b/mdl/executor/cmd_microflows_builder_java_action_test.go @@ -61,6 +61,63 @@ func TestBuildJavaAction_EmptyArgumentPreservesEmptyBasicValue(t *testing.T) { } } +// TestBuildJavaAction_EmptyListArgumentEmitsEmptyKeyword pins the BSON +// shape Studio Pro authors when a list-typed Java action parameter is +// bound to MDL `empty`: the BasicCodeActionParameterValue.Argument +// holds the literal string "empty" — distinguishable from primitive +// parameters whose unbound binding is the blank string. Emitting the +// blank string for a list parameter triggers `mx check` CE0126 +// "Missing value for parameter X" because the model treats the +// parameter as missing rather than explicitly empty. +func TestBuildJavaAction_EmptyListArgumentEmitsEmptyKeyword(t *testing.T) { + fb := &flowBuilder{ + posX: 100, + posY: 100, + spacing: HorizontalSpacing, + backend: &mock.MockBackend{ + ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) { + if qualifiedName != "SampleModule.AddBatch" { + t.Fatalf("java action lookup = %q", qualifiedName) + } + return &javaactions.JavaAction{ + Parameters: []*javaactions.JavaActionParameter{ + { + Name: "Tags", + ParameterType: &javaactions.ListType{Entity: "SampleModule.Tag"}, + }, + }, + }, nil + }, + }, + } + stmt := &ast.CallJavaActionStmt{ + ActionName: ast.QualifiedName{Module: "SampleModule", Name: "AddBatch"}, + Arguments: []ast.CallArgument{ + {Name: "Tags", Value: &ast.LiteralExpr{Kind: ast.LiteralEmpty}}, + }, + } + + id := fb.addCallJavaActionAction(stmt) + var activity *microflows.ActionActivity + for _, obj := range fb.objects { + if obj.GetID() == id { + activity, _ = obj.(*microflows.ActionActivity) + break + } + } + if activity == nil { + t.Fatal("expected Java action activity") + } + action := activity.Action.(*microflows.JavaActionCallAction) + value, ok := action.ParameterMappings[0].Value.(*microflows.BasicCodeActionParameterValue) + if !ok { + t.Fatalf("mapping value = %T, want *BasicCodeActionParameterValue", action.ParameterMappings[0].Value) + } + if value.Argument != "empty" { + t.Fatalf("list-typed empty argument = %q, want %q", value.Argument, "empty") + } +} + func TestBuildJavaAction_EmptyMicroflowArgumentUsesMicroflowParameterValue(t *testing.T) { fb := &flowBuilder{ posX: 100, From 3465f1d5daf80e591d7ba916c82917601f4aa1f4 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Tue, 5 May 2026 11:29:18 +0200 Subject: [PATCH 2/4] ci: retrigger CI after Maven Central flake on antlr4-tools From e6298e1618f5c985a2d34ba4ad6e84496225dac8 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Tue, 5 May 2026 12:15:41 +0200 Subject: [PATCH 3/4] fix: broaden empty Java action arg fix to cover all resolved BasicParameterType params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original fix targeted only ListType parameters, but Studio Pro authors `Argument: "empty"` for any typed BasicParameterType parameter (String, Integer, Boolean, ParameterizedEntityType, ListType, …) when the user explicitly binds it to MDL `empty`. Audit observations surfaced two further cases beyond list-typed Tags: a microflow parameter typed as `ParameterizedEntityType` and a Java action parameter typed as `StringType`, both authored as `Argument: "empty"` in real Studio Pro projects and both regressing to `Argument: ""` under our previous fix — triggering the same `mx check` CE0126 "Missing value for parameter X" diagnostic. Replace the listTypeParams set with resolvedBasicParams: when the backend resolves a parameter whose type is anything OTHER than entity- or microflow-type, the parameter goes into the BasicCodeActionParameterValue branch and the MDL `empty` literal must be preserved as `Argument: "empty"`. When jaDef is unavailable (jaDef == nil) we keep the prior `""` behaviour to preserve the documented "intentionally unbound" semantics of PROPOSAL_microflow_empty_java_action_argument.md. The unit test gains a String case alongside the List case — both must emit `Argument: "empty"` once the parameter type is resolved. Co-Authored-By: Claude Opus 4.7 (1M context) --- mdl/executor/cmd_microflows_builder_calls.go | 37 ++++-- ...cmd_microflows_builder_java_action_test.go | 114 ++++++++++-------- 2 files changed, 88 insertions(+), 63 deletions(-) diff --git a/mdl/executor/cmd_microflows_builder_calls.go b/mdl/executor/cmd_microflows_builder_calls.go index 74fbaf3f..cdc2c061 100644 --- a/mdl/executor/cmd_microflows_builder_calls.go +++ b/mdl/executor/cmd_microflows_builder_calls.go @@ -242,14 +242,22 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model. } // Build a map of parameter name -> param type for the Java action. - // listTypeParams tracks list-typed parameters: when bound to `empty` in - // MDL, Studio Pro authors them as `Argument: "empty"` (the MDL literal - // string) rather than `Argument: ""`, the unbound primitive marker. The + // resolvedBasicParams tracks parameters whose type was successfully + // resolved via the Java action definition AND is not an entity-type or + // microflow-type parameter (i.e. anything that lands in + // BasicCodeActionParameterValue: String, Integer, Boolean, ListType, + // ParameterizedEntityType, etc.). When the MDL author binds such a + // parameter to `empty`, Studio Pro authors `Argument: "empty"` (the MDL + // literal string) rather than `Argument: ""`, the unbound marker. The // distinction matters: `mx check` reports CE0126 "Missing value for - // parameter X" when a list-typed parameter receives `Argument: ""`. + // parameter X" when a typed parameter receives the unbound `""` shape. + // + // Without a backend lookup (jaDef == nil) we fall back to the prior + // `""` behaviour to preserve the documented "intentionally unbound" + // semantics of PROPOSAL_microflow_empty_java_action_argument.md. entityTypeParams := make(map[string]bool) microflowTypeParams := make(map[string]bool) - listTypeParams := make(map[string]bool) + resolvedBasicParams := make(map[string]bool) if jaDef != nil { for _, p := range jaDef.Parameters { switch p.ParameterType.(type) { @@ -257,8 +265,8 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model. entityTypeParams[p.Name] = true case *javaactions.MicroflowType: microflowTypeParams[p.Name] = true - case *javaactions.ListType: - listTypeParams[p.Name] = true + default: + resolvedBasicParams[p.Name] = true } } } @@ -294,11 +302,18 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model. Microflow: "", } } else { - // List-typed parameters bound to `empty` keep the literal - // "empty" expression in BSON; primitive parameters use the - // blank Argument as their unbound marker. + // When the Java action definition is available and the + // parameter is a typed BasicParameterType (anything that + // isn't entity-type or microflow-type — String, Integer, + // Boolean, ListType, ParameterizedEntityType, etc.), Studio + // Pro authors `Argument: "empty"` for the MDL `empty` + // literal. Without that information (jaDef == nil) keep the + // blank-string "intentionally unbound" marker that + // PROPOSAL_microflow_empty_java_action_argument.md + // established for code-action callers without backend + // resolution. argument := "" - if listTypeParams[arg.Name] { + if resolvedBasicParams[arg.Name] { argument = "empty" } value = µflows.BasicCodeActionParameterValue{ diff --git a/mdl/executor/cmd_microflows_builder_java_action_test.go b/mdl/executor/cmd_microflows_builder_java_action_test.go index 589d1438..1206f68d 100644 --- a/mdl/executor/cmd_microflows_builder_java_action_test.go +++ b/mdl/executor/cmd_microflows_builder_java_action_test.go @@ -61,60 +61,70 @@ func TestBuildJavaAction_EmptyArgumentPreservesEmptyBasicValue(t *testing.T) { } } -// TestBuildJavaAction_EmptyListArgumentEmitsEmptyKeyword pins the BSON -// shape Studio Pro authors when a list-typed Java action parameter is -// bound to MDL `empty`: the BasicCodeActionParameterValue.Argument -// holds the literal string "empty" — distinguishable from primitive -// parameters whose unbound binding is the blank string. Emitting the -// blank string for a list parameter triggers `mx check` CE0126 -// "Missing value for parameter X" because the model treats the -// parameter as missing rather than explicitly empty. -func TestBuildJavaAction_EmptyListArgumentEmitsEmptyKeyword(t *testing.T) { - fb := &flowBuilder{ - posX: 100, - posY: 100, - spacing: HorizontalSpacing, - backend: &mock.MockBackend{ - ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) { - if qualifiedName != "SampleModule.AddBatch" { - t.Fatalf("java action lookup = %q", qualifiedName) - } - return &javaactions.JavaAction{ - Parameters: []*javaactions.JavaActionParameter{ - { - Name: "Tags", - ParameterType: &javaactions.ListType{Entity: "SampleModule.Tag"}, - }, +// TestBuildJavaAction_EmptyResolvedBasicArgumentEmitsEmptyKeyword pins +// the BSON shape Studio Pro authors when a typed (non-entity-type, +// non-microflow-type) Java action parameter is bound to MDL `empty`: +// the BasicCodeActionParameterValue.Argument holds the literal string +// "empty". Emitting the blank `""` for such a parameter triggers +// `mx check` CE0126 "Missing value for parameter X" because the model +// treats the parameter as missing rather than explicitly empty. The +// behaviour applies regardless of the inner type (String, ListType, +// ParameterizedEntityType, …) — the discriminator is whether the +// backend resolved the parameter at all. +func TestBuildJavaAction_EmptyResolvedBasicArgumentEmitsEmptyKeyword(t *testing.T) { + cases := []struct { + name string + paramType javaactions.CodeActionParameterType + }{ + {"list", &javaactions.ListType{Entity: "SampleModule.Tag"}}, + {"string", &javaactions.StringType{}}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + fb := &flowBuilder{ + posX: 100, + posY: 100, + spacing: HorizontalSpacing, + backend: &mock.MockBackend{ + ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) { + if qualifiedName != "SampleModule.AddBatch" { + t.Fatalf("java action lookup = %q", qualifiedName) + } + return &javaactions.JavaAction{ + Parameters: []*javaactions.JavaActionParameter{ + {Name: "Param", ParameterType: tc.paramType}, + }, + }, nil }, - }, nil - }, - }, - } - stmt := &ast.CallJavaActionStmt{ - ActionName: ast.QualifiedName{Module: "SampleModule", Name: "AddBatch"}, - Arguments: []ast.CallArgument{ - {Name: "Tags", Value: &ast.LiteralExpr{Kind: ast.LiteralEmpty}}, - }, - } + }, + } + stmt := &ast.CallJavaActionStmt{ + ActionName: ast.QualifiedName{Module: "SampleModule", Name: "AddBatch"}, + Arguments: []ast.CallArgument{ + {Name: "Param", Value: &ast.LiteralExpr{Kind: ast.LiteralEmpty}}, + }, + } - id := fb.addCallJavaActionAction(stmt) - var activity *microflows.ActionActivity - for _, obj := range fb.objects { - if obj.GetID() == id { - activity, _ = obj.(*microflows.ActionActivity) - break - } - } - if activity == nil { - t.Fatal("expected Java action activity") - } - action := activity.Action.(*microflows.JavaActionCallAction) - value, ok := action.ParameterMappings[0].Value.(*microflows.BasicCodeActionParameterValue) - if !ok { - t.Fatalf("mapping value = %T, want *BasicCodeActionParameterValue", action.ParameterMappings[0].Value) - } - if value.Argument != "empty" { - t.Fatalf("list-typed empty argument = %q, want %q", value.Argument, "empty") + id := fb.addCallJavaActionAction(stmt) + var activity *microflows.ActionActivity + for _, obj := range fb.objects { + if obj.GetID() == id { + activity, _ = obj.(*microflows.ActionActivity) + break + } + } + if activity == nil { + t.Fatal("expected Java action activity") + } + action := activity.Action.(*microflows.JavaActionCallAction) + value, ok := action.ParameterMappings[0].Value.(*microflows.BasicCodeActionParameterValue) + if !ok { + t.Fatalf("mapping value = %T, want *BasicCodeActionParameterValue", action.ParameterMappings[0].Value) + } + if value.Argument != "empty" { + t.Fatalf("resolved empty argument = %q, want %q", value.Argument, "empty") + } + }) } } From faee3363de3727bdf5060ff6211b5a8ffcf84378 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Fri, 8 May 2026 10:16:50 +0200 Subject: [PATCH 4/4] fixup: add bug-test 521 and CE0126 symptom row to fix-issue skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR #521 round-3 review (ako 2026-05-07). - **`mdl-examples/bug-tests/521-empty-java-action-typed-arg.mdl`** — new bug-test calling a Java action with a list parameter and a string parameter, both bound to MDL `empty`. Before the fix this trips CE0126 "Missing value for parameter X"; after the fix, both slots serialise as `Argument: "empty"` and `mx check` reports 0 errors. - **`.claude/skills/fix-issue.md`** — new symptom row mapping CE0126 on `call java action ... ($Param = empty)` to `addCallJavaActionAction` and the `resolvedBasicParams` fix pattern. Future contributors hitting the same symptom can land on the right file from the table without re-deriving the diagnosis. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/skills/fix-issue.md | 1 + .../521-empty-java-action-typed-arg.mdl | 61 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 mdl-examples/bug-tests/521-empty-java-action-typed-arg.mdl diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index a9fc0b59..ada9b5fc 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -27,6 +27,7 @@ to the symptom table below, so the next similar issue costs fewer reads. | 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` | | 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` | | `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__*.go` — find the `NewAlreadyExistsMsg` call | Change hint from `or replace` to `or modify`; verify the AST stmt uses `CreateOrModify` not `CreateOrReplace` | +| `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 | --- diff --git a/mdl-examples/bug-tests/521-empty-java-action-typed-arg.mdl b/mdl-examples/bug-tests/521-empty-java-action-typed-arg.mdl new file mode 100644 index 00000000..1308fce5 --- /dev/null +++ b/mdl-examples/bug-tests/521-empty-java-action-typed-arg.mdl @@ -0,0 +1,61 @@ +-- ============================================================================ +-- Bug #521: `empty` literal on a typed Java action parameter triggers CE0126 +-- ============================================================================ +-- +-- Symptom (before fix): +-- `mx check` reports CE0126 "Missing value for parameter X" on a +-- `call java action Module.Foo(Param = empty)` when Param is a typed +-- (non-entity, non-microflow) parameter — most commonly a list parameter +-- or a String parameter. The builder emitted a BasicCodeActionParameterValue +-- with `Argument: ""` instead of the literal `"empty"` keyword Studio Pro +-- uses to mark an explicit empty literal. Studio Pro treats the empty +-- string as a missing slot and fires CE0126 on validation. +-- +-- After fix: +-- When the backend resolves the Java action and the parameter is a +-- BasicParameterType (StringType, ListType, ParameterizedEntityType, etc.), +-- the builder emits `Argument: "empty"` for an MDL `empty` literal so +-- Studio Pro reads the slot as an explicitly-empty value, not a missing +-- one. Entity-typed parameters keep using EntityCodeActionParameterValue +-- and microflow-typed parameters keep using MicroflowParameterValue, so +-- this fix is scoped to BasicParameterType alone. +-- +-- Validation: +-- `mxcli check 521-empty-java-action-typed-arg.mdl` parses the script. +-- `mx check` against the resulting MPR reports 0 errors for both the +-- list-typed and string-typed `empty` arguments. Roundtrip preserves +-- the `Argument: "empty"` BSON value byte-for-byte. +-- +-- Usage: +-- mxcli exec mdl-examples/bug-tests/521-empty-java-action-typed-arg.mdl -p app.mpr +-- mxcli -p app.mpr -c "describe microflow BugTest521.MF_CallWithEmpty" +-- ============================================================================ + +create module BugTest521; + +create non-persistent entity BugTest521.Item ( + Code : string +); +/ + +-- Java action with a list parameter and a string parameter — both typed +-- (BasicParameterType) so they exercise the resolved-basic-arg path. +create java action BugTest521.JA_ProcessBatch(Items: list of BugTest521.Item not null, Tag: string not null) returns boolean +as $$ +return true; +$$; +/ + +-- Calling with `empty` for both typed slots must produce +-- BasicCodeActionParameterValue.Argument = "empty" so Studio Pro +-- recognises the explicit empty literal and does not fire CE0126. +create microflow BugTest521.MF_CallWithEmpty () +returns boolean +begin + $Result = call java action BugTest521.JA_ProcessBatch( + Items = empty, + Tag = empty) + on error rollback; + return $Result; +end; +/