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
35 changes: 35 additions & 0 deletions src/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,21 @@ private static void GenerateTypedReturnsAsyncOverload(CodeWriter writer, List<Mo
writer.AppendLine($"EnsureSetup().ReturnsRaw(args => (object?)factory({castArgs}));");
writer.AppendLine("return this;");
}

// Returns alias, so an `async (a, b) => ...` lambda binds here rather than failing against
// the synchronous typed overload with CS4010. Deprioritised — and therefore net9.0+ only —
// for the same reasons as the parameterless alias; see EmitReturnsAsyncOverloads.
// See issue #6495.
writer.AppendLine();
writer.AppendLine("#if NET9_0_OR_GREATER");
writer.AppendLine("/// <summary>Configure a typed computed async return value using the actual method parameters. The returned task is handed back as-is, so an async factory stays pending until it completes.</summary>");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Async Returns remains unavailable on net8

When a .NET 8 consumer configures an async member with Returns(async (...) => ...), this conditional removes both new async-factory overload forms from the generated mock, so the lambda is still considered against the synchronous factory overload and compilation fails with CS4010.

writer.AppendLine(PriorityMinusOneAttribute);
using (writer.Block($"public {wrapperName} Returns({funcType} factory)"))
{
writer.AppendLine($"EnsureSetup().ReturnsRaw(args => (object?)factory({castArgs}));");
writer.AppendLine("return this;");
}
writer.AppendLine("#endif");
}

private static void GenerateTypedCallbackOverload(CodeWriter writer, List<MockParameterModel> nonOutParams,
Expand Down Expand Up @@ -1778,6 +1793,26 @@ private static void EmitReturnsAsyncOverloads(CodeWriter writer, string wrapperN
writer.AppendLine($"public {wrapperName} ReturnsAsync({taskType} task) {{ EnsureSetup().ReturnsRaw(task); return this; }}");
writer.AppendLine($"/// <summary>Return a pre-built {taskLabel} from a factory, invoked on each call.</summary>");
writer.AppendLine($"public {wrapperName} ReturnsAsync(global::System.Func<{taskType}> taskFactory) {{ EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }}");
// Same factory, spelled Returns — so an `async () => ...` lambda binds without the caller
// having to know about ReturnsAsync (the synchronous Func<T> overload rejects it with
// CS4010). The returned task is handed back as-is, so it stays pending. See issue #6495.
Comment thread
greptile-apps[bot] marked this conversation as resolved.
//
// Deprioritised against the synchronous Returns(Func<T>) sibling: when T is a reference
// type, a lambda whose body pins nothing — Returns(() => null), Returns(() => throw ...) —
// converts equally well to Func<T> and Func<Task<T>>, which would be CS0121. The priority
// breaks that tie back to the pre-existing synchronous meaning. A genuine async lambda is
Comment thread
greptile-apps[bot] marked this conversation as resolved.
// unaffected: it is not convertible to Func<T> at all, so it is the only candidate.
//
// That makes the alias inseparable from the attribute, which only reaches the consumer's
// compilation on net9.0+ — TUnit.Mocks polyfills it internally for its own build, so a
// net8.0 consumer would hit CS0246. Emitting the overload there without the priority would
// hand them the ambiguity instead, so the whole alias is net9.0+ (matching the framework
// polyfills below); net8.0 keeps ReturnsAsync, which already returns the task as-is.
writer.AppendLine("#if NET9_0_OR_GREATER");
writer.AppendLine($"/// <summary>Return a {taskLabel} from a factory, invoked on each call. The {taskLabel} is returned as-is, so an async factory stays pending until it completes.</summary>");
writer.AppendLine(PriorityMinusOneAttribute);
writer.AppendLine($"public {wrapperName} Returns(global::System.Func<{taskType}> taskFactory) {{ EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }}");
writer.AppendLine("#endif");
}

private static void EmitEnsureSetup(CodeWriter writer, string builderType, bool hasTypeArguments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ public sealed class IDialogReference_GetReturnValueAsync_M0_MockCall<T> : global
public IDialogReference_GetReturnValueAsync_M0_MockCall<T> ReturnsAsync(global::System.Threading.Tasks.Task<T?> task) { EnsureSetup().ReturnsRaw(task); return this; }
/// <summary>Return a pre-built Task from a factory, invoked on each call.</summary>
public IDialogReference_GetReturnValueAsync_M0_MockCall<T> ReturnsAsync(global::System.Func<global::System.Threading.Tasks.Task<T?>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#if NET9_0_OR_GREATER
/// <summary>Return a Task from a factory, invoked on each call. The Task is returned as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IDialogReference_GetReturnValueAsync_M0_MockCall<T> Returns(global::System.Func<global::System.Threading.Tasks.Task<T?>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#endif

// ICallVerification
/// <inheritdoc />
Expand Down Expand Up @@ -587,6 +592,11 @@ public sealed class IDialogService_UpdateDialogAsync_M0_MockCall<TData> : global
public IDialogService_UpdateDialogAsync_M0_MockCall<TData> ReturnsAsync(global::System.Threading.Tasks.Task<global::IDialogReference?> task) { EnsureSetup().ReturnsRaw(task); return this; }
/// <summary>Return a pre-built Task from a factory, invoked on each call.</summary>
public IDialogService_UpdateDialogAsync_M0_MockCall<TData> ReturnsAsync(global::System.Func<global::System.Threading.Tasks.Task<global::IDialogReference?>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#if NET9_0_OR_GREATER
/// <summary>Return a Task from a factory, invoked on each call. The Task is returned as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IDialogService_UpdateDialogAsync_M0_MockCall<TData> Returns(global::System.Func<global::System.Threading.Tasks.Task<global::IDialogReference?>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#endif

/// <summary>Configure a typed computed return value using the actual method parameters.</summary>
public IDialogService_UpdateDialogAsync_M0_MockCall<TData> Returns(global::System.Func<string, global::DialogParameters<TData>, global::IDialogReference?> factory)
Expand All @@ -602,6 +612,16 @@ public sealed class IDialogService_UpdateDialogAsync_M0_MockCall<TData> : global
return this;
}

#if NET9_0_OR_GREATER
/// <summary>Configure a typed computed async return value using the actual method parameters. The returned task is handed back as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IDialogService_UpdateDialogAsync_M0_MockCall<TData> Returns(global::System.Func<string, global::DialogParameters<TData>, global::System.Threading.Tasks.Task<global::IDialogReference?>> factory)
{
EnsureSetup().ReturnsRaw(args => (object?)factory((string)args[0]!, (global::DialogParameters<TData>)args[1]!));
return this;
}
#endif

/// <summary>Execute a typed callback using the actual method parameters.</summary>
public IDialogService_UpdateDialogAsync_M0_MockCall<TData> Callback(global::System.Action<string, global::DialogParameters<TData>> callback)
{
Expand Down Expand Up @@ -694,6 +714,11 @@ public sealed class IDialogService_ShowDialogAsync_M1_MockCall<TDialog> : global
public IDialogService_ShowDialogAsync_M1_MockCall<TDialog> ReturnsAsync(global::System.Threading.Tasks.Task<global::IDialogReference> task) { EnsureSetup().ReturnsRaw(task); return this; }
/// <summary>Return a pre-built Task from a factory, invoked on each call.</summary>
public IDialogService_ShowDialogAsync_M1_MockCall<TDialog> ReturnsAsync(global::System.Func<global::System.Threading.Tasks.Task<global::IDialogReference>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#if NET9_0_OR_GREATER
/// <summary>Return a Task from a factory, invoked on each call. The Task is returned as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IDialogService_ShowDialogAsync_M1_MockCall<TDialog> Returns(global::System.Func<global::System.Threading.Tasks.Task<global::IDialogReference>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#endif

/// <summary>Configure a typed computed return value using the actual method parameters.</summary>
public IDialogService_ShowDialogAsync_M1_MockCall<TDialog> Returns(global::System.Func<object, global::DialogParameters, global::IDialogReference> factory)
Expand All @@ -709,6 +734,16 @@ public sealed class IDialogService_ShowDialogAsync_M1_MockCall<TDialog> : global
return this;
}

#if NET9_0_OR_GREATER
/// <summary>Configure a typed computed async return value using the actual method parameters. The returned task is handed back as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IDialogService_ShowDialogAsync_M1_MockCall<TDialog> Returns(global::System.Func<object, global::DialogParameters, global::System.Threading.Tasks.Task<global::IDialogReference>> factory)
{
EnsureSetup().ReturnsRaw(args => (object?)factory((object)args[0]!, (global::DialogParameters)args[1]!));
return this;
}
#endif

/// <summary>Execute a typed callback using the actual method parameters.</summary>
public IDialogService_ShowDialogAsync_M1_MockCall<TDialog> Callback(global::System.Action<object, global::DialogParameters> callback)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ public sealed class IAsyncService_GetValueAsync_M0_MockCall : global::TUnit.Mock
public IAsyncService_GetValueAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task<string> task) { EnsureSetup().ReturnsRaw(task); return this; }
/// <summary>Return a pre-built Task from a factory, invoked on each call.</summary>
public IAsyncService_GetValueAsync_M0_MockCall ReturnsAsync(global::System.Func<global::System.Threading.Tasks.Task<string>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#if NET9_0_OR_GREATER
/// <summary>Return a Task from a factory, invoked on each call. The Task is returned as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IAsyncService_GetValueAsync_M0_MockCall Returns(global::System.Func<global::System.Threading.Tasks.Task<string>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#endif

/// <summary>Configure a typed computed return value using the actual method parameters.</summary>
public IAsyncService_GetValueAsync_M0_MockCall Returns(global::System.Func<string, string> factory)
Expand All @@ -314,6 +319,16 @@ public sealed class IAsyncService_GetValueAsync_M0_MockCall : global::TUnit.Mock
return this;
}

#if NET9_0_OR_GREATER
/// <summary>Configure a typed computed async return value using the actual method parameters. The returned task is handed back as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IAsyncService_GetValueAsync_M0_MockCall Returns(global::System.Func<string, global::System.Threading.Tasks.Task<string>> factory)
{
EnsureSetup().ReturnsRaw(args => (object?)factory((string)args[0]!));
return this;
}
#endif

/// <summary>Execute a typed callback using the actual method parameters.</summary>
public IAsyncService_GetValueAsync_M0_MockCall Callback(global::System.Action<string> callback)
{
Expand Down Expand Up @@ -398,6 +413,11 @@ public sealed class IAsyncService_DoWorkAsync_M1_MockCall : global::TUnit.Mocks.
public IAsyncService_DoWorkAsync_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; }
/// <summary>Return a pre-built Task from a factory, invoked on each call.</summary>
public IAsyncService_DoWorkAsync_M1_MockCall ReturnsAsync(global::System.Func<global::System.Threading.Tasks.Task> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#if NET9_0_OR_GREATER
/// <summary>Return a Task from a factory, invoked on each call. The Task is returned as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IAsyncService_DoWorkAsync_M1_MockCall Returns(global::System.Func<global::System.Threading.Tasks.Task> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#endif

// ICallVerification
/// <inheritdoc />
Expand Down Expand Up @@ -474,6 +494,11 @@ public sealed class IAsyncService_ComputeAsync_M2_MockCall : global::TUnit.Mocks
public IAsyncService_ComputeAsync_M2_MockCall ReturnsAsync(global::System.Threading.Tasks.ValueTask<int> task) { EnsureSetup().ReturnsRaw(task); return this; }
/// <summary>Return a pre-built ValueTask from a factory, invoked on each call.</summary>
public IAsyncService_ComputeAsync_M2_MockCall ReturnsAsync(global::System.Func<global::System.Threading.Tasks.ValueTask<int>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#if NET9_0_OR_GREATER
/// <summary>Return a ValueTask from a factory, invoked on each call. The ValueTask is returned as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IAsyncService_ComputeAsync_M2_MockCall Returns(global::System.Func<global::System.Threading.Tasks.ValueTask<int>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#endif

/// <summary>Configure a typed computed return value using the actual method parameters.</summary>
public IAsyncService_ComputeAsync_M2_MockCall Returns(global::System.Func<int, int> factory)
Expand All @@ -489,6 +514,16 @@ public sealed class IAsyncService_ComputeAsync_M2_MockCall : global::TUnit.Mocks
return this;
}

#if NET9_0_OR_GREATER
/// <summary>Configure a typed computed async return value using the actual method parameters. The returned task is handed back as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IAsyncService_ComputeAsync_M2_MockCall Returns(global::System.Func<int, global::System.Threading.Tasks.ValueTask<int>> factory)
{
EnsureSetup().ReturnsRaw(args => (object?)factory((int)args[0]!));
return this;
}
#endif

/// <summary>Execute a typed callback using the actual method parameters.</summary>
public IAsyncService_ComputeAsync_M2_MockCall Callback(global::System.Action<int> callback)
{
Expand Down Expand Up @@ -575,6 +610,11 @@ public sealed class IAsyncService_InitializeAsync_M3_MockCall : global::TUnit.Mo
public IAsyncService_InitializeAsync_M3_MockCall ReturnsAsync(global::System.Threading.Tasks.ValueTask task) { EnsureSetup().ReturnsRaw(task); return this; }
/// <summary>Return a pre-built ValueTask from a factory, invoked on each call.</summary>
public IAsyncService_InitializeAsync_M3_MockCall ReturnsAsync(global::System.Func<global::System.Threading.Tasks.ValueTask> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#if NET9_0_OR_GREATER
/// <summary>Return a ValueTask from a factory, invoked on each call. The ValueTask is returned as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IAsyncService_InitializeAsync_M3_MockCall Returns(global::System.Func<global::System.Threading.Tasks.ValueTask> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#endif

/// <summary>Execute a typed callback using the actual method parameters.</summary>
public IAsyncService_InitializeAsync_M3_MockCall Callback(global::System.Action<global::System.Threading.CancellationToken> callback)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ public sealed class IService_GetAsync_M3_MockCall : global::TUnit.Mocks.Verifica
public IService_GetAsync_M3_MockCall ReturnsAsync(global::System.Threading.Tasks.Task<string> task) { EnsureSetup().ReturnsRaw(task); return this; }
/// <summary>Return a pre-built Task from a factory, invoked on each call.</summary>
public IService_GetAsync_M3_MockCall ReturnsAsync(global::System.Func<global::System.Threading.Tasks.Task<string>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#if NET9_0_OR_GREATER
/// <summary>Return a Task from a factory, invoked on each call. The Task is returned as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IService_GetAsync_M3_MockCall Returns(global::System.Func<global::System.Threading.Tasks.Task<string>> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }
#endif

/// <summary>Configure a typed computed return value using the actual method parameters.</summary>
public IService_GetAsync_M3_MockCall Returns(global::System.Func<int, string> factory)
Expand All @@ -328,6 +333,16 @@ public sealed class IService_GetAsync_M3_MockCall : global::TUnit.Mocks.Verifica
return this;
}

#if NET9_0_OR_GREATER
/// <summary>Configure a typed computed async return value using the actual method parameters. The returned task is handed back as-is, so an async factory stays pending until it completes.</summary>
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]
public IService_GetAsync_M3_MockCall Returns(global::System.Func<int, global::System.Threading.Tasks.Task<string>> factory)
{
EnsureSetup().ReturnsRaw(args => (object?)factory((int)args[0]!));
return this;
}
#endif

/// <summary>Execute a typed callback using the actual method parameters.</summary>
public IService_GetAsync_M3_MockCall Callback(global::System.Action<int> callback)
{
Expand Down
Loading
Loading