Skip to content

Commit 3555153

Browse files
committed
Use dependently return type in CallActivity
1 parent f2feb86 commit 3555153

25 files changed

Lines changed: 467 additions & 131 deletions

File tree

ballerina/context.bal

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,21 @@
1717
import ballerina/jballerina.java;
1818
import ballerina/time;
1919

20+
# Record type for activity parameters.
21+
# Used to pass arguments to activity functions in a type-safe manner.
22+
public type Parameters record {|
23+
anydata...;
24+
|};
25+
2026
# Workflow execution context providing workflow APIs.
2127
# This is a client object that provides access to workflow operations.
2228
#
2329
# This client provides:
24-
# - Activity execution via `callActivity()` remote method
30+
# - Activity execution via `callActivity` remote method
2531
# - Durable sleep operations
2632
# - Workflow state queries (replaying status, workflow ID, workflow type)
2733
#
28-
# Use `check ctx.callActivity(myActivity, arg1, arg2)` to execute activities.
34+
# Use `check ctx->callActivity(myActivity, {"arg1": val1, "arg2": val2})` to execute activities.
2935
# Use Ballerina's `wait` action with event futures for signal handling.
3036
public client class Context {
3137
private handle nativeContext;
@@ -43,17 +49,24 @@ public client class Context {
4349
# that should only be executed once during workflow execution and not during replay.
4450
# The workflow runtime ensures exactly-once execution semantics for activities.
4551
#
52+
# The return type is determined by the `T` typedesc parameter, allowing compile-time
53+
# type checking when the expected return type is specified. The compiler plugin
54+
# validates that the activity function's return type is compatible with `T`.
55+
#
4656
# Example:
4757
# ```ballerina
48-
# string result = check ctx.callActivity(sendEmailActivity, recipientEmail, subject);
58+
# string result = check ctx->callActivity(sendEmailActivity, {"email": recipientEmail, "subject": subject});
4959
# ```
5060
#
5161
# + activityFunction - The activity function to execute (must be annotated with @Activity)
52-
# + args - Variable arguments to pass to the activity function
53-
# + return - The result of the activity execution, or an error if execution fails
54-
remote isolated function callActivity(function activityFunction, anydata... args) returns anydata|error {
55-
return callActivityNative(self.nativeContext, activityFunction, ...args);
56-
}
62+
# + args - Record containing the arguments to pass to the activity function
63+
# + T - The expected return type (inferred from context or explicitly specified)
64+
# + return - The result of the activity execution cast to type T, or an error if execution fails
65+
remote isolated function callActivity(function activityFunction, Parameters args, typedesc<anydata> T = <>)
66+
returns T|error = @java:Method {
67+
'class: "io.ballerina.stdlib.workflow.context.WorkflowContextNative",
68+
name: "callActivity"
69+
} external;
5770

5871
# Durable sleep that survives workflow restarts.
5972
#
@@ -98,15 +111,6 @@ public client class Context {
98111

99112
// Native function declarations
100113

101-
isolated function callActivityNative(
102-
handle contextHandle,
103-
function activityFunction,
104-
anydata... args
105-
) returns anydata|error = @java:Method {
106-
'class: "io.ballerina.stdlib.workflow.context.WorkflowContextNative",
107-
name: "callActivity"
108-
} external;
109-
110114
isolated function sleepNative(
111115
handle contextHandle,
112116
int millis

ballerina/functions.bal

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,6 @@
1616

1717
import ballerina/jballerina.java;
1818

19-
# Internal function to execute an activity.
20-
# Users should use the callActivity remote method on the Context client instead.
21-
#
22-
# Activities are non-deterministic operations (I/O, database calls, external APIs)
23-
# that should only be executed once during workflow execution and not during replay.
24-
# The workflow runtime ensures exactly-once execution semantics for activities.
25-
#
26-
# + activityFunction - The activity function to execute (must be annotated with @Activity)
27-
# + args - Variable arguments to pass to the activity function
28-
# + return - The result of the activity execution, or an error if execution fails
29-
isolated function callActivity(function activityFunction, anydata... args) returns anydata|error = @java:Method {
30-
'class: "io.ballerina.stdlib.workflow.runtime.nativeimpl.WorkflowNative"
31-
} external;
3219

3320
# Starts a new workflow process with the given input.
3421
#

ballerina/tests/test.bal

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ function testActivityFunction2(int value) returns int|error {
5252
// Test process that calls activities using Context client.
5353
@Process
5454
function processWithActivities(Context ctx, string input) returns string|error {
55-
// Use Context client's callActivity remote method
56-
anydata result1 = check ctx->callActivity(testActivityFunction, input);
57-
anydata result2 = check ctx->callActivity(testActivityFunction2, 10);
58-
return <string>result1 + " - " + (<int>result2).toString();
55+
// Use Context client's callActivity remote method with Parameters record
56+
string result1 = check ctx->callActivity(testActivityFunction, {"input": input});
57+
int result2 = check ctx->callActivity(testActivityFunction2, {"value": 10});
58+
return result1 + " - " + result2.toString();
5959
}
6060

6161
// Note: Removed @test:BeforeEach that cleared registry globally.

compiler-plugin-tests/src/test/java/io/ballerina/stdlib/workflow/compiler/WorkflowCompilerPluginTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,33 @@ public void testInvalidDirectActivityCall() {
154154
assertDiagnosticContains(diagnosticResult, WorkflowConstants.WORKFLOW_108);
155155
}
156156

157+
@Test(groups = "invalid")
158+
public void testInvalidCallActivityMissingParam() {
159+
String packagePath = "invalid_call_activity_missing_param";
160+
DiagnosticResult diagnosticResult = getValidationDiagnosticResult(packagePath);
161+
Assert.assertTrue(diagnosticResult.errorCount() > 0,
162+
"Expected validation error for callActivity with missing required parameter");
163+
assertDiagnosticContains(diagnosticResult, WorkflowConstants.WORKFLOW_109);
164+
}
165+
166+
@Test(groups = "invalid")
167+
public void testInvalidCallActivityExtraParam() {
168+
String packagePath = "invalid_call_activity_extra_param";
169+
DiagnosticResult diagnosticResult = getValidationDiagnosticResult(packagePath);
170+
Assert.assertTrue(diagnosticResult.errorCount() > 0,
171+
"Expected validation error for callActivity with extra parameter");
172+
assertDiagnosticContains(diagnosticResult, WorkflowConstants.WORKFLOW_110);
173+
}
174+
175+
@Test(groups = "invalid")
176+
public void testInvalidCallActivityRestParams() {
177+
String packagePath = "invalid_call_activity_rest_params";
178+
DiagnosticResult diagnosticResult = getValidationDiagnosticResult(packagePath);
179+
Assert.assertTrue(diagnosticResult.errorCount() > 0,
180+
"Expected validation error for callActivity with activity having rest parameters");
181+
assertDiagnosticContains(diagnosticResult, WorkflowConstants.WORKFLOW_111);
182+
}
183+
157184
/**
158185
* Get diagnostic result for the given package path.
159186
* Uses runCodeGenAndModifyPlugins() to run the code modifier.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
org = "test_samples"
3+
name = "invalid_call_activity_extra_param"
4+
version = "0.1.0"
5+
distribution = "2201.13.0"
6+
7+
[build-options]
8+
observabilityIncluded = true
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
2+
//
3+
// WSO2 LLC. licenses this file to you under the Apache License,
4+
// Version 2.0 (the "License"); you may not use this file except
5+
// in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing,
11+
// software distributed under the License is distributed on an
12+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13+
// KIND, either express or implied. See the License for the
14+
// specific language governing permissions and limitations
15+
// under the License.
16+
17+
import ballerina/workflow;
18+
19+
type OrderInput record {|
20+
string orderId;
21+
int quantity;
22+
|};
23+
24+
type OrderResult record {|
25+
string status;
26+
|};
27+
28+
// Activity function with single parameter
29+
@workflow:Activity
30+
function processOrder(string orderId) returns boolean|error {
31+
return orderId.length() > 0;
32+
}
33+
34+
// Invalid: Process function calling ctx->callActivity() with extra parameter
35+
// This should produce WORKFLOW_110 error
36+
@workflow:Process
37+
function orderProcess(workflow:Context ctx, OrderInput input) returns OrderResult|error {
38+
// ERROR: Extra parameter 'quantity' - not in activity function signature
39+
boolean isValid = check ctx->callActivity(processOrder, {"orderId": input.orderId, "quantity": input.quantity});
40+
if !isValid {
41+
return error("Invalid order");
42+
}
43+
44+
return {
45+
status: "COMPLETED"
46+
};
47+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
org = "test_samples"
3+
name = "invalid_call_activity_missing_param"
4+
version = "0.1.0"
5+
distribution = "2201.13.0"
6+
7+
[build-options]
8+
observabilityIncluded = true
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
2+
//
3+
// WSO2 LLC. licenses this file to you under the Apache License,
4+
// Version 2.0 (the "License"); you may not use this file except
5+
// in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing,
11+
// software distributed under the License is distributed on an
12+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13+
// KIND, either express or implied. See the License for the
14+
// specific language governing permissions and limitations
15+
// under the License.
16+
17+
import ballerina/workflow;
18+
19+
type OrderInput record {|
20+
string orderId;
21+
int quantity;
22+
|};
23+
24+
type OrderResult record {|
25+
string status;
26+
|};
27+
28+
// Activity function with two required parameters
29+
@workflow:Activity
30+
function processOrder(string orderId, int quantity) returns boolean|error {
31+
return quantity > 0;
32+
}
33+
34+
// Invalid: Process function calling ctx->callActivity() with missing required parameter
35+
// This should produce WORKFLOW_109 error
36+
@workflow:Process
37+
function orderProcess(workflow:Context ctx, OrderInput input) returns OrderResult|error {
38+
// ERROR: Missing required parameter 'quantity'
39+
boolean isValid = check ctx->callActivity(processOrder, {"orderId": input.orderId});
40+
if !isValid {
41+
return error("Invalid order");
42+
}
43+
44+
return {
45+
status: "COMPLETED"
46+
};
47+
}

compiler-plugin-tests/src/test/resources/ballerina_sources/invalid_call_activity_no_annotation/main.bal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function regularFunction(OrderInput input) returns boolean|error {
3535
@workflow:Process
3636
function orderProcess(workflow:Context ctx, OrderInput input) returns OrderResult|error {
3737
// ERROR: regularFunction does not have @Activity annotation
38-
boolean isValid = check ctx->callActivity(regularFunction, input);
38+
boolean isValid = check ctx->callActivity(regularFunction, {"input": input});
3939
if !isValid {
4040
return error("Invalid order");
4141
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
org = "test_samples"
3+
name = "invalid_call_activity_rest_params"
4+
version = "0.1.0"
5+
distribution = "2201.13.0"
6+
7+
[build-options]
8+
observabilityIncluded = true

0 commit comments

Comments
 (0)