From e21a3afd1968bbc7be3c799e7b4713ba0f275863 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Tue, 5 May 2026 10:46:18 +0200 Subject: [PATCH 1/3] 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 8bb5bf20e6ca6a563d2e947b978f567f74bf2281 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Tue, 5 May 2026 11:29:18 +0200 Subject: [PATCH 2/3] ci: retrigger CI after Maven Central flake on antlr4-tools From 7b85f18314a90a372daf8cb16828debf83085767 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Tue, 5 May 2026 12:15:41 +0200 Subject: [PATCH 3/3] 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") + } + }) } }