diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index dc3f0ed1..c30c594e 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 | | `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 | --- 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; +/ diff --git a/mdl/executor/cmd_microflows_builder_calls.go b/mdl/executor/cmd_microflows_builder_calls.go index ec45f4ac..ede59402 100644 --- a/mdl/executor/cmd_microflows_builder_calls.go +++ b/mdl/executor/cmd_microflows_builder_calls.go @@ -241,15 +241,32 @@ 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. + // 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 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) + resolvedBasicParams := 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 + default: + resolvedBasicParams[p.Name] = true } } } @@ -285,9 +302,23 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model. Microflow: "", } } else { + // 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 resolvedBasicParams[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..1206f68d 100644 --- a/mdl/executor/cmd_microflows_builder_java_action_test.go +++ b/mdl/executor/cmd_microflows_builder_java_action_test.go @@ -61,6 +61,73 @@ func TestBuildJavaAction_EmptyArgumentPreservesEmptyBasicValue(t *testing.T) { } } +// 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 + }, + }, + } + 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("resolved empty argument = %q, want %q", value.Argument, "empty") + } + }) + } +} + func TestBuildJavaAction_EmptyMicroflowArgumentUsesMicroflowParameterValue(t *testing.T) { fb := &flowBuilder{ posX: 100,