Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/skills/fix-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_<type>_*.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 |

---
Expand Down
61 changes: 61 additions & 0 deletions mdl-examples/bug-tests/521-empty-java-action-typed-arg.mdl
Original file line number Diff line number Diff line change
@@ -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;
/
39 changes: 35 additions & 4 deletions mdl/executor/cmd_microflows_builder_calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down Expand Up @@ -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 = &microflows.BasicCodeActionParameterValue{
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
Argument: "",
Argument: argument,
}
}
} else {
Expand Down
67 changes: 67 additions & 0 deletions mdl/executor/cmd_microflows_builder_java_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading