Skip to content
Open
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
56 changes: 56 additions & 0 deletions mdl-examples/bug-tests/352-java-action-return-type-inference.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
-- ============================================================================
-- Bug #352 (part): Java action result variable type inference
-- ============================================================================
--
-- Symptom (before fix):
-- The microflow builder looked up Java action definitions to format
-- entity-typed parameters, but it did NOT register the Java action's
-- return type on the output variable. Subsequent statements that
-- touched the result (`change`, `commit`, `$Result/Attribute`,
-- association paths) had no type info, so:
-- - the writer fell back to untyped expressions
-- - `mx check` could surface CE0117 on dependent activities
-- - describe re-emitted the call without the right variable
-- declaration shape
--
-- After fix:
-- `addCallJavaActionAction` consults the Java action's `ReturnType`
-- and registers the output variable as the appropriate object/list
-- type (`Module.Entity`, `List of Module.Entity`, `System.FileDocument`,
-- etc.) so downstream activities resolve correctly.
--
-- Usage:
-- mxcli exec mdl-examples/bug-tests/352-java-action-return-type-inference.mdl -p app.mpr
-- mxcli -p app.mpr -c "describe microflow BugTest352b.MF_UseJavaResult"
-- `mx check` against the resulting MPR must report 0 errors.
-- ============================================================================

create module BugTest352b;

create persistent entity BugTest352b.LogEntry (
Message : string(500)
);
/

-- Java action returning an entity object — the result variable type must
-- be inferred so downstream `change` / `commit` resolve.
create java action BugTest352b.CreateLogEntry (
Msg: string not null
) returns BugTest352b.LogEntry
as $$
return new com.mendix.systemwideinterfaces.core.IMendixObject();
$$;
/

-- Caller microflow that depends on the inferred return type. The change
-- statement on `$Entry/Message` would fail validation if the Java action
-- result variable type is not registered.
create microflow BugTest352b.MF_UseJavaResult (
$Msg: string
)
returns BugTest352b.LogEntry as $Entry
begin
$Entry = call java action BugTest352b.CreateLogEntry (Msg = $Msg);
change $Entry (Message = $Msg + ' (processed)');
end;
/
49 changes: 49 additions & 0 deletions mdl/executor/cmd_microflows_builder_calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model.
ResultVariableName: s.OutputVariable,
UseReturnVariable: s.OutputVariable != "",
}
if s.OutputVariable != "" && jaDef != nil && fb.varTypes != nil {
if varType := javaActionReturnVarType(jaDef.ReturnType); varType != "" {
fb.varTypes[s.OutputVariable] = varType
} else if inferred := fb.inferGenericJavaActionReturnType(jaDef, s); inferred != "" {
fb.varTypes[s.OutputVariable] = inferred
}
}

activityX := fb.posX
activity := &microflows.ActionActivity{
Expand Down Expand Up @@ -268,6 +275,48 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model.
return activity.ID
}

func javaActionReturnVarType(returnType javaactions.CodeActionReturnType) string {
switch t := returnType.(type) {
case *javaactions.EntityType:
return t.Entity
case *javaactions.ListType:
if t.Entity != "" {
return "List of " + t.Entity
}
case *javaactions.FileDocumentType:
return "System.FileDocument"
}
return ""
}

func (fb *flowBuilder) inferGenericJavaActionReturnType(jaDef *javaactions.JavaAction, s *ast.CallJavaActionStmt) string {
if jaDef == nil || fb.varTypes == nil || s == nil {
return ""
}
switch t := jaDef.ReturnType.(type) {
case *javaactions.ListType:
if t.Entity != "" {
return ""
}
case javaactions.ListType:
if t.Entity != "" {
return ""
}
default:
return ""
}
for _, arg := range s.Arguments {
valueExpr := strings.TrimPrefix(strings.Trim(fb.exprToString(arg.Value), "'"), "$")
if valueExpr == "" {
continue
}
if typ := fb.varTypes[valueExpr]; strings.HasPrefix(typ, "List of ") {
return typ
}
}
return ""
}

// addCallExternalActionAction creates a CALL EXTERNAL ACTION statement.
func (fb *flowBuilder) addCallExternalActionAction(s *ast.CallExternalActionStmt) model.ID {
serviceQN := s.ServiceName.Module + "." + s.ServiceName.Name
Expand Down
95 changes: 95 additions & 0 deletions mdl/executor/cmd_microflows_builder_java_return_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: Apache-2.0

package executor

import (
"testing"

"github.com/mendixlabs/mxcli/mdl/ast"
"github.com/mendixlabs/mxcli/mdl/backend/mock"
"github.com/mendixlabs/mxcli/sdk/javaactions"
)

func TestAddJavaAction_FileDocumentReturnRegistersSystemFileDocument(t *testing.T) {
backend := &mock.MockBackend{
ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) {
if qualifiedName != "Spreadsheet.ExportRows" {
return nil, nil
}
return &javaactions.JavaAction{
ReturnType: &javaactions.FileDocumentType{},
}, nil
},
}

fb := &flowBuilder{
backend: backend,
varTypes: map[string]string{},
declaredVars: map[string]string{},
}

fb.addCallJavaActionAction(&ast.CallJavaActionStmt{
OutputVariable: "GeneratedDocument",
ActionName: ast.QualifiedName{Module: "Spreadsheet", Name: "ExportRows"},
})

if got := fb.varTypes["GeneratedDocument"]; got != "System.FileDocument" {
t.Fatalf("GeneratedDocument type = %q, want System.FileDocument", got)
}
}

func TestAddJavaAction_ConcreteListReturnRegistersListType(t *testing.T) {
backend := &mock.MockBackend{
ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) {
return &javaactions.JavaAction{
ReturnType: &javaactions.ListType{Entity: "Orders.Order"},
}, nil
},
}

fb := &flowBuilder{
backend: backend,
varTypes: map[string]string{},
declaredVars: map[string]string{},
}

fb.addCallJavaActionAction(&ast.CallJavaActionStmt{
OutputVariable: "FilteredOrders",
ActionName: ast.QualifiedName{Module: "Lists", Name: "FilterOrders"},
})

if got := fb.varTypes["FilteredOrders"]; got != "List of Orders.Order" {
t.Fatalf("FilteredOrders type = %q, want list type", got)
}
}

func TestAddJavaAction_GenericListReturnInheritsInputListType(t *testing.T) {
backend := &mock.MockBackend{
ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) {
return &javaactions.JavaAction{
ReturnType: &javaactions.ListType{},
}, nil
},
}

fb := &flowBuilder{
varTypes: map[string]string{
"InputOrders": "List of Orders.Order",
},
declaredVars: map[string]string{},
backend: backend,
measurer: &layoutMeasurer{},
}

fb.addCallJavaActionAction(&ast.CallJavaActionStmt{
OutputVariable: "FilteredOrders",
ActionName: ast.QualifiedName{Module: "Lists", Name: "FilterGeneric"},
Arguments: []ast.CallArgument{
{Name: "InputList", Value: &ast.VariableExpr{Name: "InputOrders"}},
},
})

if got := fb.varTypes["FilteredOrders"]; got != "List of Orders.Order" {
t.Fatalf("generic java list result type = %q, want input list type", got)
}
}
Loading