From d7b7dcb61a4f67e42c2ad61373bd41ba98670bcb Mon Sep 17 00:00:00 2001 From: wangbill Date: Wed, 15 Jul 2026 12:06:37 -0700 Subject: [PATCH] Fix generator emitting invalid C# for nullable activity input The source generator appended `= default` to the [ActivityTrigger] input parameter when an activity's input type was nullable (e.g. object?). Because that parameter is followed by the required instanceId and executionContext parameters, this produced an optional parameter ahead of required ones, which is invalid C# (CS1737) and failed to compile. The generated *CallAsync helpers legitimately keep `= default` because their input is followed by an optional options parameter, so only AddActivityFunctionDeclaration is changed. Extended the Activities_ClassBasedSyntax regression test with an object? case that reproduces the issue. Fixes #730 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Generators/DurableTaskSourceGenerator.cs | 4 ---- test/Generators.Tests/AzureFunctionsTests.cs | 17 +++++++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Generators/DurableTaskSourceGenerator.cs b/src/Generators/DurableTaskSourceGenerator.cs index 25566bc2..89d868a2 100644 --- a/src/Generators/DurableTaskSourceGenerator.cs +++ b/src/Generators/DurableTaskSourceGenerator.cs @@ -1101,10 +1101,6 @@ static void AddActivityFunctionDeclaration(StringBuilder sourceBuilder, DurableT string inputType = activity.GetInputTypeForNamespace(targetNamespace); string outputType = activity.GetOutputTypeForNamespace(targetNamespace); string inputParameter = inputType + " input"; - if (inputType.EndsWith("?", StringComparison.Ordinal)) - { - inputParameter += " = default"; - } string simplifiedActivityTypeName = SimplifyTypeName(activity.TypeName, targetNamespace); // GeneratedActivityContext is a generated class that we use for each generated activity trigger definition. diff --git a/test/Generators.Tests/AzureFunctionsTests.cs b/test/Generators.Tests/AzureFunctionsTests.cs index 3165af59..49d2f9bb 100644 --- a/test/Generators.Tests/AzureFunctionsTests.cs +++ b/test/Generators.Tests/AzureFunctionsTests.cs @@ -203,12 +203,11 @@ await TestHelpers.RunTestAsync( [InlineData("int", "string")] [InlineData("string", "int")] [InlineData("Guid", "TimeSpan")] + [InlineData("object?", "string")] public async Task Activities_ClassBasedSyntax(string inputType, string outputType) { - // Nullable reference types need to be used for input expressions - string defaultInputType = TestHelpers.GetDefaultInputType(inputType); - string code = $@" +#nullable enable using System; using System.Threading.Tasks; using Microsoft.DurableTask; @@ -219,11 +218,13 @@ public class MyActivity : TaskActivity<{inputType}, {outputType}> public override Task<{outputType}> RunAsync(TaskActivityContext context, {inputType} input) => Task.FromResult<{outputType}>(default!); }}"; - // Build the expected InputParameter format (matches generator logic) - string expectedInputParameter = inputType + " input"; + // The generated CallMyActivityAsync helper appends "= default" for nullable inputs because the + // input is followed by an optional options parameter. The [ActivityTrigger] function does not, + // because its input is followed by required parameters (see issue #730). + string expectedCallInputParameter = inputType + " input"; if (inputType.EndsWith('?')) { - expectedInputParameter += " = default"; + expectedCallInputParameter += " = default"; } string expectedOutput = TestHelpers.WrapAndFormat( @@ -233,13 +234,13 @@ public class MyActivity : TaskActivity<{inputType}, {outputType}> /// Calls the activity. /// /// -public static Task<{outputType}> CallMyActivityAsync(this TaskOrchestrationContext ctx, {inputType} input, TaskOptions? options = null) +public static Task<{outputType}> CallMyActivityAsync(this TaskOrchestrationContext ctx, {expectedCallInputParameter}, TaskOptions? options = null) {{ return ctx.CallActivityAsync<{outputType}>(""MyActivity"", input, options); }} [Function(nameof(MyActivity))] -public static async Task<{outputType}> MyActivity([ActivityTrigger] {expectedInputParameter}, string instanceId, FunctionContext executionContext) +public static async Task<{outputType}> MyActivity([ActivityTrigger] {inputType} input, string instanceId, FunctionContext executionContext) {{ ITaskActivity activity = ActivatorUtilities.GetServiceOrCreateInstance(executionContext.InstanceServices); TaskActivityContext context = new GeneratedActivityContext(""MyActivity"", instanceId);