diff --git a/src/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs b/src/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs index af0d054e02..56ced6565a 100644 --- a/src/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs +++ b/src/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs @@ -1516,9 +1516,20 @@ private static void EmitRawReturnCheck(CodeWriter writer, MockMemberModel method // IMPORTANT: This check must appear synchronously (no await) after the engine // dispatch call. The [ThreadStatic] RawReturnContext requires same-thread consumption. + // The pattern type must drop an outer nullable annotation (Task? → Task): + // nullable types are never legal in an `is` pattern (CS8116). For an outer-nullable + // member, a null raw value is a legitimate contract value — ReturnsAsync accepts a null + // task there — so it is returned rather than falling into the mismatch throw. + var patternType = method.ReturnType.TrimEnd('?'); + var isOuterNullable = patternType.Length != method.ReturnType.Length; writer.AppendLine($"if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync))"); writer.OpenBrace(); - writer.AppendLine($"if (__rawAsync is {method.ReturnType} __typedAsync) return __typedAsync;"); + if (isOuterNullable) + { + writer.AppendLine("if (__rawAsync is null) return null;"); + } + + writer.AppendLine($"if (__rawAsync is {patternType} __typedAsync) return __typedAsync;"); writer.AppendLine($"throw new global::System.InvalidOperationException($\"ReturnsAsync: expected {method.ReturnType} but got {{__rawAsync?.GetType().Name ?? \"null\"}}\");"); writer.CloseBrace(); } diff --git a/src/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs b/src/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs index 09ddd824dd..270aef4509 100644 --- a/src/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs +++ b/src/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs @@ -509,9 +509,10 @@ private static void GenerateReturnUnifiedClass(CodeWriter writer, string wrapper writer.AppendLine($"/// "); writer.AppendLine($"public {wrapperTypeName} Then() {{ EnsureSetup().Then(); return this; }}"); + var aliasTypeParam = GetAsyncAliasTypeParamName(model, method); if (isAsync && fullReturnType is not null) { - EmitReturnsAsyncOverloads(writer, wrapperTypeName, fullReturnType, isValueTask); + EmitReturnsAsyncOverloads(writer, wrapperTypeName, fullReturnType, isValueTask, aliasTypeParam, GetDefaultableTypeParameterNames(model, method), GetTypeParameterNames(model, method)); } // Typed parameter overloads (only for methods with typed params within the arity limit). @@ -521,14 +522,14 @@ private static void GenerateReturnUnifiedClass(CodeWriter writer, string wrapper if (hasRefStructParams) { writer.AppendLine("#if NET9_0_OR_GREATER"); - EmitTypedOverloads(writer, nonOutParams, returnType, wrapperTypeName, isAsync, fullReturnType, allNonOutParams); + EmitTypedOverloads(writer, nonOutParams, returnType, wrapperTypeName, isAsync, fullReturnType, aliasTypeParam, allNonOutParams); writer.AppendLine("#else"); - EmitTypedOverloads(writer, nonOutParams, returnType, wrapperTypeName, isAsync, fullReturnType); + EmitTypedOverloads(writer, nonOutParams, returnType, wrapperTypeName, isAsync, fullReturnType, aliasTypeParam); writer.AppendLine("#endif"); } else { - EmitTypedOverloads(writer, nonOutParams, returnType, wrapperTypeName, isAsync, fullReturnType); + EmitTypedOverloads(writer, nonOutParams, returnType, wrapperTypeName, isAsync, fullReturnType, aliasTypeParam); } } @@ -645,7 +646,7 @@ private static void GenerateVoidUnifiedClass(CodeWriter writer, string wrapperCt var taskType = isValueTask ? "global::System.Threading.Tasks.ValueTask" : "global::System.Threading.Tasks.Task"; - EmitReturnsAsyncOverloads(writer, wrapperTypeName, taskType, isValueTask); + EmitReturnsAsyncOverloads(writer, wrapperTypeName, taskType, isValueTask, GetAsyncAliasTypeParamName(model, method), GetDefaultableTypeParameterNames(model, method), GetTypeParameterNames(model, method)); } // Typed parameter overloads (only for methods with typed params within the arity limit). @@ -724,13 +725,13 @@ private static void GenerateTypedReturnsOverload(CodeWriter writer, List nonOutParams, string returnType, string wrapperName, bool isAsync, string? fullReturnType, - List? allNonOutParams = null) + string aliasTypeParam, List? allNonOutParams = null) { GenerateTypedReturnsOverload(writer, nonOutParams, returnType, wrapperName, allNonOutParams); if (isAsync && fullReturnType is not null) { writer.AppendLine(); - GenerateTypedReturnsAsyncOverload(writer, nonOutParams, fullReturnType, wrapperName, allNonOutParams); + GenerateTypedReturnsAsyncOverload(writer, nonOutParams, fullReturnType, wrapperName, aliasTypeParam, allNonOutParams); } writer.AppendLine(); GenerateTypedCallbackOverload(writer, nonOutParams, wrapperName, allNonOutParams); @@ -739,7 +740,7 @@ private static void EmitTypedOverloads(CodeWriter writer, List nonOutParams, - string taskType, string wrapperName, List? allNonOutParams = null) + string taskType, string wrapperName, string aliasTypeParam, List? allNonOutParams = null) { var typeList = string.Join(", ", nonOutParams.Select(p => p.FullyQualifiedType)); var funcType = $"global::System.Func<{typeList}, {taskType}>"; @@ -753,10 +754,34 @@ private static void GenerateTypedReturnsAsyncOverload(CodeWriter writer, List ...` 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. + // the synchronous typed overload with CS4010. Same two-alias shape as the parameterless + // variant — generic (primary, all TFMs, converted to the declared task type) plus a + // net9.0+-only ORP(-1) non-generic alias for typeless async lambda bodies; see + // EmitReturnsAsyncOverloads for the full rationale. The __TUnitMocksConvertAsyncResult + // helpers are emitted there, and that always runs for the wrappers that reach here. + // aliasTypeParam is uniquified against the mocked type's and method's own type + // parameters — a same-name inner declaration would shadow (CS0693) and break the + // conversion helper's result-type reference. See issues #6495 and #6515. + var genericTaskKind = taskType.Substring(0, taskType.IndexOf('<')); + var isValueTaskKind = genericTaskKind.EndsWith("ValueTask"); + var genericFuncType = $"global::System.Func<{typeList}, {genericTaskKind}<{aliasTypeParam}>>"; writer.AppendLine(); + writer.AppendLine("/// 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."); + using (writer.Block($"public {wrapperName} Returns<{aliasTypeParam}>({genericFuncType} factory)")) + { + if (isValueTaskKind) + { + writer.AppendLine($"EnsureSetup().ReturnsRaw(args => (object?)__TUnitMocksConvertAsyncResult<{aliasTypeParam}>(factory({castArgs})));"); + } + else + { + // A null task from the factory is contractually valid for outer-nullable members; + // it must bypass the conversion helper (whose exact-type pattern cannot match null) + // and flow to the raw-return check, which accepts null for those members. + writer.AppendLine($"EnsureSetup().ReturnsRaw(args => {{ var task = factory({castArgs}); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult<{aliasTypeParam}>(task); }});"); + } + writer.AppendLine("return this;"); + } writer.AppendLine("#if NET9_0_OR_GREATER"); writer.AppendLine("/// 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."); writer.AppendLine(PriorityMinusOneAttribute); @@ -1809,7 +1834,7 @@ private static string BuildExtensionMethodParameterList(MockTypeModel model, str return string.IsNullOrEmpty(paramList) ? extensionParam : $"{extensionParam}, {paramList}"; } - private static void EmitReturnsAsyncOverloads(CodeWriter writer, string wrapperName, string taskType, bool isValueTask) + private static void EmitReturnsAsyncOverloads(CodeWriter writer, string wrapperName, string taskType, bool isValueTask, string aliasTypeParam, HashSet defaultableTypeParameters, HashSet typeParameterNames) { var taskLabel = isValueTask ? "ValueTask" : "Task"; writer.AppendLine(); @@ -1830,22 +1855,552 @@ private static void EmitReturnsAsyncOverloads(CodeWriter writer, string wrapperN // having to know about ReturnsAsync (the synchronous Func overload rejects it with // CS4010). The returned task is handed back as-is, so it stays pending. See issue #6495. // - // Deprioritised against the synchronous Returns(Func) sibling: when T is a reference - // type, a lambda whose body pins nothing — Returns(() => null), Returns(() => throw ...) — - // converts equally well to Func and Func>, which would be CS0121. The priority - // breaks that tie back to the pre-existing synchronous meaning. A genuine async lambda is - // unaffected: it is not convertible to Func 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($"/// 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."); - writer.AppendLine(PriorityMinusOneAttribute); - writer.AppendLine($"public {wrapperName} Returns(global::System.Func<{taskType}> taskFactory) {{ EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }}"); - writer.AppendLine("#endif"); + // For Task/ValueTask, the alias must not collide with the synchronous + // Returns(Func) sibling: when the result type is a reference type, a lambda whose body + // pins nothing — Returns(() => null), Returns(() => throw ...) — converts equally well to + // Func and Func>, which would be CS0121. On net9.0+, + // [OverloadResolutionPriority(-1)] breaks that tie back to the pre-existing synchronous + // meaning; a genuine async lambda is unaffected, as it is not convertible to Func at + // all. The attribute only reaches the consumer's compilation on net9.0+, so below that the + // alias is generic instead (#6515): a typeless lambda body (null / throw / default) cannot + // infer the result type parameter, which excludes the alias from the candidate set and + // resolves the same tie the same way — while an async lambda with an inferable body binds + // it, ValueTask included. The generic form trades the ORP alias's compile-time result-type + // check for inference (a wrong-typed factory surfaces when the setup is consumed), which + // is why the ORP alias remains the net9.0+ shape. Bare Task/ValueTask members have no + // synchronous Returns sibling to collide with, so their alias needs neither. + var aliasDoc = $"/// 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."; + if (IsGenericTaskType(taskType)) + { + // The generic alias is the primary shape on EVERY target framework: + // - A typeless lambda body (null / throw / default) cannot infer the type parameter, + // so the alias drops out and Returns(() => null) keeps binding the synchronous + // factory — no CS0121, no ORP needed for that tie. + // - A lambda-to-delegate conversion beats lambda-to-object, so on members declared + // Task the alias wins over Returns(object value) — which an ORP(-1) + // non-generic alias LOSES (priority pruning runs before betterness), silently + // boxing the natural-typed lambda as the value. + // - Inference may pick a type OTHER than the declared result (Task member, + // `async () => "value"` infers Task; Task member, `async () => 1` + // infers Task) and Task/ValueTask are invariant, so the factory task is + // converted to the declared task type: an exact-typed task passes through untouched + // (identity preserved), anything else is mirrored by an async wrapper that stays + // pending until the inner task completes. The wrapper honors reference/boxing + // conversions, C#'s implicit numeric widening table (numeric-primitive results), + // and C#'s element-wise implicit tuple conversions (value-tuple results), and + // nothing more: narrowing/rounding (long → int, double → int), + // a null result for a non-nullable value-type member, and any genuinely wrong-typed + // factory all surface as an informative InvalidCastException when the task + // completes. (User-defined implicit conversions are not replayed at runtime — cast + // the factory result to the declared type in the lambda.) + // - A null Task from the factory (valid for outer-nullable members) bypasses the + // conversion and flows to the raw-return check, which accepts null for those + // members. + // The net9.0+-only ORP(-1) non-generic alias below covers the one shape the generic + // alias cannot: an async lambda whose body pins no type (`async () => null`) — it has + // no natural type, so no other overload applies and the deprioritised alias is the + // sole candidate. ORP pruning also guarantees the two aliases are never ambiguous + // with each other. + var taskKind = isValueTask ? "global::System.Threading.Tasks.ValueTask" : "global::System.Threading.Tasks.Task"; + var resultType = GetTaskResultType(taskType); + // Tuple element names are not permitted in typeof (and add nothing to conversion + // identity), so the helper's pattern/typeof always use the bare form; for non-tuple + // result types this is the unchanged type string. + var bareResultType = StripTupleElementNames(resultType); + writer.AppendLine(aliasDoc); + if (isValueTask) + { + writer.AppendLine($"public {wrapperName} Returns<{aliasTypeParam}>(global::System.Func<{taskKind}<{aliasTypeParam}>> taskFactory) {{ EnsureSetup().ReturnsRaw(() => (object?)__TUnitMocksConvertAsyncResult<{aliasTypeParam}>(taskFactory())); return this; }}"); + } + else + { + writer.AppendLine($"public {wrapperName} Returns<{aliasTypeParam}>(global::System.Func<{taskKind}<{aliasTypeParam}>> taskFactory) {{ EnsureSetup().ReturnsRaw(() => {{ var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult<{aliasTypeParam}>(task); }}); return this; }}"); + } + writer.AppendLine(); + writer.AppendLine($"private static {taskKind}<{resultType}> __TUnitMocksConvertAsyncResult<{aliasTypeParam}>({taskKind}<{aliasTypeParam}> task)"); + writer.AppendLine($" => task is {taskKind}<{bareResultType}> exact ? exact : __TUnitMocksAwaitAndConvert(task);"); + writer.AppendLine(); + writer.AppendLine($"private static async {taskKind}<{resultType}> __TUnitMocksAwaitAndConvert<{aliasTypeParam}>({taskKind}<{aliasTypeParam}> task)"); + var pendingTupleItems = new List<(string Suffix, string Type)>(); + using (writer.Block()) + { + writer.AppendLine("object? value = await task.ConfigureAwait(false);"); + writer.AppendLine("switch (value)"); + using (writer.Block()) + { + EmitAsyncResultConversionCases(writer, resultType, defaultableTypeParameters, typeParameterNames, aliasTypeParam, "", pendingTupleItems); + } + } + // Value-tuple results convert element-wise; each element gets its own converter so + // nested tuples recurse and every element replays the same conversion set as a + // whole result would. The list grows while iterating (nested tuples enqueue). + for (var itemIndex = 0; itemIndex < pendingTupleItems.Count; itemIndex++) + { + var (suffix, itemType) = pendingTupleItems[itemIndex]; + writer.AppendLine(); + writer.AppendLine($"private static {itemType} __TUnitMocksConvertAsyncTupleItem{suffix}(object? value)"); + using (writer.Block()) + { + writer.AppendLine("switch (value)"); + using (writer.Block()) + { + EmitAsyncResultConversionCases(writer, itemType, defaultableTypeParameters, typeParameterNames, null, suffix, pendingTupleItems); + } + } + } + writer.AppendLine("#if NET9_0_OR_GREATER"); + writer.AppendLine(aliasDoc); + writer.AppendLine(PriorityMinusOneAttribute); + writer.AppendLine($"public {wrapperName} Returns(global::System.Func<{taskType}> taskFactory) {{ EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }}"); + writer.AppendLine("#endif"); + } + else + { + writer.AppendLine(aliasDoc); + writer.AppendLine($"public {wrapperName} Returns(global::System.Func<{taskType}> taskFactory) {{ EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; }}"); + } + } + + /// + /// Emits the case set replaying C#'s implicit conversions for one boxed async factory + /// result — or, when is null, one value-tuple element of + /// it: exact type, null (guarded for non-nullable value types), implicit numeric widening, + /// implicit constant conversions, constant zero-to-enum, and element-wise value-tuple + /// conversion. Everything else takes an informative InvalidCastException. Tuple elements + /// enqueue their own converter methods on (nested + /// tuples recurse). + /// + private static void EmitAsyncResultConversionCases(CodeWriter writer, string declaredType, + HashSet defaultableTypeParameters, HashSet typeParameterNames, + string? aliasTypeParam, string helperSuffix, + List<(string Suffix, string Type)> pendingTupleItems) + { + var bareType = StripTupleElementNames(declaredType); + // `dynamic` erases to object at runtime but is illegal as a pattern type (CS8208) + // and as a typeof operand (CS1962), so the helper's pattern/typeof spell it object — + // object → dynamic is an identity conversion, so `return exact;` still satisfies the + // declared result type. Constructed types containing dynamic (Task, + // List) are legal in both and need no mapping. + var patternType = bareType.TrimEnd('?'); + if (patternType == "dynamic") + { + patternType = "object"; + } + + var isTuple = TryGetTupleElementTypes(patternType, out var tupleElements); + var declaredDesc = aliasTypeParam is not null ? "the member's declared result type" : "the declared tuple element type"; + var producedWhat = aliasTypeParam is not null ? "a result" : "a tuple element"; + + if (!isTuple) + { + writer.AppendLine($"case {patternType} exact: return exact;"); + } + + // On an unconstrained (or class-constrained) type parameter, a trailing '?' + // is the "defaultable" annotation, NOT Nullable: Task with T = int is + // Task, so the runtime value-type guard must still run — typeof(T) + // judges each instantiation (T = int throws on null; T = int? / T = string + // accept it). Only a struct-constrained T? (genuine Nullable) or a + // concrete annotated type may skip the guard at generation time. + var nullableAnnotationIsDefaultable = defaultableTypeParameters.Contains(declaredType.TrimEnd('?')); + var isNonNullable = !declaredType.EndsWith("?") || nullableAnnotationIsDefaultable; + if (isTuple && isNonNullable) + { + // A value-tuple type is statically a non-nullable value type, so null always + // throws — no runtime type checks, and no typeof on a tuple type (tuple element + // names aside, the diagnostic only needs the type's spelling). + var origin = aliasTypeParam is not null + ? $"\"The async factory (result type '\" + typeof({aliasTypeParam}) + \"') produced a null result, but {declaredDesc} '{patternType}' is a non-nullable value type. Return a non-null value of the declared type from the factory.\"" + : $"\"The async factory produced a null tuple element, but {declaredDesc} '{patternType}' is a non-nullable value type. Return a non-null value of the declared type from the factory.\""; + writer.AppendLine($"case null: throw new global::System.InvalidCastException({origin});"); + } + else + { + if (isNonNullable && patternType is not ("object" or "string")) + { + // A null result silently becoming default(T) (zero) would corrupt data + // for non-nullable value-type members (Task member, alias inferring + // Task, factory yielding null). The check is a runtime one because + // a generic result type's nullability depends on the instantiation; + // genuinely nullable results (trailing '?' on a non-defaultable type) + // skip it at generation time, and object/string are known reference types. + var origin = aliasTypeParam is not null + ? $"\"The async factory (result type '\" + typeof({aliasTypeParam}) + \"') produced a null result, but {declaredDesc} '\" + typeof({patternType}) + \"' is a non-nullable value type. Return a non-null value of the declared type from the factory.\"" + : $"\"The async factory produced a null tuple element, but {declaredDesc} '\" + typeof({patternType}) + \"' is a non-nullable value type. Return a non-null value of the declared type from the factory.\""; + writer.AppendLine($"case null when typeof({patternType}).IsValueType && global::System.Nullable.GetUnderlyingType(typeof({patternType})) is null: throw new global::System.InvalidCastException({origin});"); + } + writer.AppendLine($"case null: return default({bareType})!;"); + } + + if (!isTuple) + { + // A type parameter may be NAMED like a BCL numeric type (interface IFoo), + // but C# grants no implicit numeric conversions on an open T, and emitting the + // numeric cases into a method returning T would not compile (CS0029). Type + // parameters take only the exact/null/zero-to-enum cases, all runtime-guarded. + var isTypeParameter = typeParameterNames.Contains(patternType); + if (!isTypeParameter) + { + // Only the implicit numeric widening conversions C# itself permits + // (sbyte → long, int → double, ...) are replayed, each as a dedicated case + // whose conversion the compiler verifies. Convert.ChangeType would also + // accept narrowing/rounding conversions (long → int, double → int, + // double → decimal) that C# rejects implicitly, silently corrupting the + // value — those fall through to the informative InvalidCastException below. + foreach (var source in GetImplicitNumericWideningSources(patternType)) + { + writer.AppendLine($"case {source} number: return number;"); + } + // C# also permits implicit *constant expression* conversions (an in-range + // int constant to sbyte/byte/short/ushort/uint/ulong, a non-negative long + // constant to ulong) — `async () => 1` on a Task member compiles + // against the declared delegate, but the alias infers int. Constant-ness is + // erased by the time the boxed result reaches the helper, so replay them as + // range-guarded exact-value conversions; an out-of-range value still takes + // the informative InvalidCastException below. + foreach (var (source, guard) in GetImplicitConstantConversionSources(patternType)) + { + writer.AppendLine($"case {source} number when {guard}: return ({patternType})number;"); + } + } + if (isTypeParameter || MightBeEnumResultType(patternType)) + { + // C# also implicitly converts the constant 0 of any integer type to any + // enum type (`async () => 0` on a Task member compiles against + // the declared delegate, but the alias infers int). Constant-ness is + // erased at runtime, so replay it value-guarded: integral sources only + // (an enum source is excluded — enum → enum is never implicit), exactly + // zero, enum destinations only. Enum-ness is a runtime check because a + // generic result type's enum-ness depends on the instantiation; non-zero + // values still take the informative InvalidCastException below. + writer.AppendLine($"case global::System.IConvertible zero when typeof({patternType}).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return ({patternType})global::System.Enum.ToObject(typeof({patternType}), 0);"); + } + } + else + { + // C#'s implicit tuple conversions are element-wise, so they are replayed the same + // way: any value tuple of matching arity (a System.Tuple is a class and never + // implicitly converts, hence the IsValueType guard) has each element converted by + // its own helper — an inconvertible element throws its informative + // InvalidCastException from there. There is no exact-type fast case: this case + // also handles the exact tuple, element by element, keeping tuple type patterns + // (whose element-name/annotation rules differ) out of the generated code. + for (var i = 0; i < tupleElements.Length; i++) + { + pendingTupleItems.Add(($"{helperSuffix}_{i}", tupleElements[i])); + } + var converted = string.Join(", ", tupleElements.Select((_, i) => $"__TUnitMocksConvertAsyncTupleItem{helperSuffix}_{i}(tuple[{i}])")); + writer.AppendLine($"case global::System.Runtime.CompilerServices.ITuple tuple when tuple.GetType().IsValueType && tuple.Length == {tupleElements.Length}: return ({converted});"); + } + + var declaredSpelling = isTuple ? $"'{patternType}'" : $"'\" + typeof({patternType}) + \"'"; + writer.AppendLine($"default: throw new global::System.InvalidCastException(\"The async factory produced {producedWhat} of type '\" + value.GetType() + \"', which is not convertible to {declaredDesc} {declaredSpelling}. Cast the factory result to the declared type in the lambda.\");"); + } + + /// + /// True when is value-tuple syntax (T1, T2, ...) — the + /// parenthesized form the display format produces for ValueTuple types. Outputs the element + /// types with any element names removed. + /// + private static bool TryGetTupleElementTypes(string type, out string[] elements) + { + elements = []; + if (type.Length < 2 || type[0] != '(' || type[type.Length - 1] != ')') + { + return false; + } + + // The trailing ')' must close the LEADING '(' — otherwise this is not tuple syntax. + var depth = 0; + for (var i = 0; i < type.Length - 1; i++) + { + var c = type[i]; + if (c is '<' or '(' or '[') + { + depth++; + } + else if (c is '>' or ')' or ']') + { + depth--; + } + + if (depth == 0) + { + return false; + } + } + + var parts = SplitTopLevelTypeArguments(type.Substring(1, type.Length - 2)); + if (parts.Count < 2) + { + return false; + } + + elements = parts.Select(StripTupleElementName).ToArray(); + return true; + } + + /// + /// Splits a comma-separated type list at the top level, respecting angle-bracket, paren, + /// and square-bracket nesting. Input is the INSIDE of a tuple/generic bracket pair. + /// + private static List SplitTopLevelTypeArguments(string list) + { + var result = new List(); + var depth = 0; + var start = 0; + for (var i = 0; i < list.Length; i++) + { + var c = list[i]; + if (c is '<' or '(' or '[') + { + depth++; + } + else if (c is '>' or ')' or ']') + { + depth--; + } + else if (c == ',' && depth == 0) + { + result.Add(list.Substring(start, i - start).Trim()); + start = i + 1; + } + } + + result.Add(list.Substring(start).Trim()); + return result; + } + + /// + /// Drops a trailing tuple element NAME (global::N.Type Countglobal::N.Type). + /// A type display string only ends in a space-separated plain identifier when an element + /// name follows the type (spaces inside generic arguments are followed by more type syntax). + /// + private static string StripTupleElementName(string element) + { + var lastSpace = element.LastIndexOf(' '); + if (lastSpace < 0) + { + return element; + } + + var candidate = element.Substring(lastSpace + 1); + if (candidate.Length == 0 || char.IsDigit(candidate[0])) + { + return element; + } + + foreach (var c in candidate) + { + if (!char.IsLetterOrDigit(c) && c != '_' && c != '@') + { + return element; + } + } + + return element.Substring(0, lastSpace).TrimEnd(); + } + + /// + /// Rewrites a type display string with all tuple element names removed, recursing into + /// tuple elements and generic type arguments — element names are not permitted in + /// typeof (and add nothing to conversion identity), so generated helper code always + /// uses the bare form. Non-tuple types come back unchanged. + /// + private static string StripTupleElementNames(string type) + { + if (type.EndsWith("?")) + { + return StripTupleElementNames(type.Substring(0, type.Length - 1)) + "?"; + } + + if (type.EndsWith("[]")) + { + return StripTupleElementNames(type.Substring(0, type.Length - 2)) + "[]"; + } + + if (TryGetTupleElementTypes(type, out var elements)) + { + return "(" + string.Join(", ", elements.Select(StripTupleElementNames)) + ")"; + } + + var open = type.IndexOf('<'); + if (open >= 0 && type.EndsWith(">")) + { + var args = SplitTopLevelTypeArguments(type.Substring(open + 1, type.Length - open - 2)); + return type.Substring(0, open) + "<" + string.Join(", ", args.Select(StripTupleElementNames)) + ">"; + } + + return type; + } + + /// + /// True for Task<T>/ValueTask<T> type strings, false for bare Task/ValueTask. + /// An outer-nullable member type (Task<string>?) carries a trailing ?, + /// so trim it before testing — misclassifying it as bare would emit the ungated alias next + /// to the synchronous factory and make Returns(() => null) ambiguous (CS0121). + /// + private static bool IsGenericTaskType(string taskType) => taskType.TrimEnd('?').EndsWith(">"); + + /// + /// The type parameter name for the generic async-factory Returns alias, uniquified against + /// the mocked type's and the method's own type parameters (both are in scope inside the + /// wrapper class): a same-name declaration would shadow (CS0693) and silently rebind the + /// conversion helper's result-type reference to the alias's parameter. + /// + private static string GetAsyncAliasTypeParamName(MockTypeModel model, MockMemberModel method) + { + var name = "TAsyncFactoryResult"; + while (model.TypeParameters.Any(tp => tp.Name == name) || method.TypeParameters.Any(tp => tp.Name == name)) + { + name += "_"; + } + return name; + } + + /// + /// The C# source types whose values convert to via an implicit + /// numeric widening conversion (C# spec §10.2.3) — the exact set the async conversion + /// helper replays for a boxed factory result, each as a compiler-verified cast case + /// (a boxed value cannot be unboxed as a wider type). Empty for non-numeric types and + /// for numeric types with no implicit sources (sbyte, byte, char). Note decimal: + /// integral → decimal is implicit, but float/double → decimal (and decimal → anything) + /// is not. + /// + private static string[] GetImplicitNumericWideningSources(string type) + { + var name = NormalizeNumericTypeName(type); + + return name switch + { + "short" or "Int16" => ["sbyte", "byte"], + "ushort" or "UInt16" => ["byte", "char"], + "int" or "Int32" => ["sbyte", "byte", "short", "ushort", "char"], + "uint" or "UInt32" => ["byte", "ushort", "char"], + // Native integers participate on both sides: nint → long (and nuint → ulong) are + // implicit, as are the small integral types → nint/nuint. On the supported TFMs + // (net7+ NumericIntPtr) System.IntPtr IS nint to the compiler, so both spellings map. + "long" or "Int64" => ["sbyte", "byte", "short", "ushort", "int", "uint", "char", "nint"], + "ulong" or "UInt64" => ["byte", "ushort", "uint", "char", "nuint"], + "float" or "Single" => ["sbyte", "byte", "short", "ushort", "int", "uint", "long", "ulong", "char", "nint", "nuint"], + "double" or "Double" => ["sbyte", "byte", "short", "ushort", "int", "uint", "long", "ulong", "char", "float", "nint", "nuint"], + "decimal" or "Decimal" => ["sbyte", "byte", "short", "ushort", "int", "uint", "long", "ulong", "char", "nint", "nuint"], + "nint" or "IntPtr" => ["sbyte", "byte", "short", "ushort", "int", "char"], + "nuint" or "UIntPtr" => ["byte", "ushort", "uint", "char"], + _ => [], + }; + } + + /// + /// The (source, guard) pairs replaying C#'s implicit constant expression conversions + /// (spec §10.2.11): an in-range int constant converts to sbyte/byte/short/ushort/uint/ulong, + /// and a non-negative long constant to ulong. Constant-ness is erased at runtime — the boxed + /// factory result of async () => 1 on a Task<byte> member is just an int — so + /// the guard checks the value's range instead: exact-value narrowing only, never rounding, + /// with out-of-range values falling through to the informative InvalidCastException. + /// + private static (string Source, string Guard)[] GetImplicitConstantConversionSources(string type) + { + return NormalizeNumericTypeName(type) switch + { + "sbyte" or "SByte" => [("int", "number >= global::System.SByte.MinValue && number <= global::System.SByte.MaxValue")], + "byte" or "Byte" => [("int", "number >= global::System.Byte.MinValue && number <= global::System.Byte.MaxValue")], + "short" or "Int16" => [("int", "number >= global::System.Int16.MinValue && number <= global::System.Int16.MaxValue")], + "ushort" or "UInt16" => [("int", "number >= global::System.UInt16.MinValue && number <= global::System.UInt16.MaxValue")], + "uint" or "UInt32" => [("int", "number >= 0")], + "ulong" or "UInt64" => [("int", "number >= 0"), ("long", "number >= 0")], + // A non-negative int constant also converts to nuint (int → nint needs no entry: + // it is an ordinary implicit conversion, replayed by the widening table). + "nuint" or "UIntPtr" => [("int", "number >= 0")], + _ => [], + }; + } + + /// + /// Whether the declared async result type could be an enum at runtime — a named type that + /// is not a known primitive/special type. Gates emission of the constant-zero-to-enum + /// conversion case, which is itself runtime-guarded by typeof(T).IsEnum. IConvertible + /// is excluded because the zero case's own pattern is case IConvertible — emitted + /// after case IConvertible exact it would be unreachable, and CS8120 is an error. + /// (Type parameters bypass this check entirely at the call site.) + /// + private static bool MightBeEnumResultType(string type) + { + return NormalizeNumericTypeName(type) switch + { + "object" or "string" or "dynamic" or "bool" or "Boolean" or "char" or "Char" + or "sbyte" or "SByte" or "byte" or "Byte" or "short" or "Int16" or "ushort" or "UInt16" + or "int" or "Int32" or "uint" or "UInt32" or "long" or "Int64" or "ulong" or "UInt64" + or "float" or "Single" or "double" or "Double" or "decimal" or "Decimal" + or "nint" or "IntPtr" or "nuint" or "UIntPtr" or "IConvertible" => false, + _ => true, + }; + } + + /// + /// Type parameters (containing type's and the method's own) for which a trailing '?' is the + /// "defaultable" annotation rather than Nullable<T> — everything except + /// struct-constrained parameters, whose T? genuinely erases to Nullable. + /// + private static HashSet GetDefaultableTypeParameterNames(MockTypeModel model, MockMemberModel method) + { + var names = new HashSet(); + foreach (var tp in model.TypeParameters) + { + if (!tp.HasValueTypeConstraint) + { + names.Add(tp.Name); + } + } + + foreach (var tp in method.TypeParameters) + { + if (!tp.HasValueTypeConstraint) + { + names.Add(tp.Name); + } + } + + return names; + } + + /// + /// Every in-scope type parameter name (containing type's and the method's own). A result + /// type matching one of these is an open type parameter regardless of what it is named — + /// a parameter called Int32 must not be mistaken for System.Int32 by the + /// name-based numeric conversion tables. + /// + private static HashSet GetTypeParameterNames(MockTypeModel model, MockMemberModel method) + { + var names = new HashSet(); + foreach (var tp in model.TypeParameters) + { + names.Add(tp.Name); + } + + foreach (var tp in method.TypeParameters) + { + names.Add(tp.Name); + } + + return names; + } + + private static string NormalizeNumericTypeName(string type) + { + var name = type.StartsWith("global::") ? type.Substring("global::".Length) : type; + return name.StartsWith("System.") ? name.Substring("System.".Length) : name; + } + + /// + /// The declared result type inside a Task<T>/ValueTask<T> type string, ignoring an + /// outer nullable annotation. Only valid when is true. + /// + private static string GetTaskResultType(string taskType) + { + var trimmed = taskType.TrimEnd('?'); + var open = trimmed.IndexOf('<'); + return trimmed.Substring(open + 1, trimmed.Length - open - 2); } private static void EmitEnsureSetup(CodeWriter writer, string builderType, bool hasTypeArguments) diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs b/tests/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs index ef4e9590c7..66f8511c4f 100644 --- a/tests/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs @@ -437,6 +437,262 @@ void M() return VerifyGeneratorOutput(source); } + [Test] + public Task Interface_With_Small_Numeric_Async_Results() + { + // #6518 review: `async () => 1` on a Task member compiles via C#'s implicit + // constant expression conversion, but the alias infers int — the conversion helper must + // carry range-guarded int → byte/short/ulong cases, not only widening ones. + var source = """ + using System.Threading.Tasks; + using TUnit.Mocks; + + public interface ISmallNumericService + { + Task GetByteAsync(); + ValueTask GetShortAsync(); + Task GetUnsignedAsync(); + } + + public class TestUsage + { + void M() + { + var mock = Mock.Of(); + } + } + """; + + return VerifyGeneratorOutput(source); + } + + [Test] + public Task Interface_With_Native_Integer_Async_Results() + { + // #6518 review: nint → long is an ordinary implicit numeric conversion (and native + // integers are valid destinations for int / non-negative int constants) — the + // conversion tables must include nint/nuint on both sides. + var source = """ + using System.Threading.Tasks; + using TUnit.Mocks; + + public interface INativeIntService + { + Task GetLongAsync(); + Task GetNativeAsync(); + ValueTask GetUnsignedNativeAsync(); + } + + public class TestUsage + { + void M() + { + var mock = Mock.Of(); + } + } + """; + + return VerifyGeneratorOutput(source); + } + + [Test] + public Task Interface_With_Enum_And_Defaultable_Generic_Async_Results() + { + // #6518 review round 6: the conversion helper must replay C#'s implicit + // constant-zero-to-enum conversion (value-guarded, enum destinations only), and a + // trailing '?' on an UNCONSTRAINED type parameter (Task, T = int is Task) must + // keep the runtime value-type null guard — while a struct-constrained T? (genuine + // Nullable) still skips it. + var source = """ + using System.Threading.Tasks; + using TUnit.Mocks; + + public enum Color + { + None = 0, + Red = 1, + } + + public interface IEnumAndGenericService + { + Task GetColorAsync(); + ValueTask GetColorValueAsync(); + Task FindAsync(int id); + Task FindStructAsync(int id) where T : struct; + } + + public class TestUsage + { + void M() + { + var mock = Mock.Of(); + } + } + """; + + return VerifyGeneratorOutput(source); + } + + [Test] + public Task Interface_With_Dynamic_Async_Result() + { + // #6518 review: `dynamic` is illegal as a pattern type (CS8208) and as a typeof operand + // (CS1962), so the async conversion helper must spell it `object` — merely mocking this + // interface used to break the consumer's compilation. + var source = """ + using System.Threading.Tasks; + using TUnit.Mocks; + + public interface IDynamicService + { + Task GetAsync(); + ValueTask ComputeAsync(); + } + + public class TestUsage + { + void M() + { + var mock = Mock.Of(); + } + } + """; + + return VerifyGeneratorOutput(source); + } + + [Test] + public Task Interface_With_Tuple_Async_Results() + { + // #6518 review: value-tuple results need C#'s element-wise implicit tuple conversions + // replayed (the generic alias infers the factory's tuple type, e.g. (string, string) + // for a Task<(object, object)> member), and tuple element names must never reach the + // helper's typeof/patterns — they are not permitted there. + var source = """ + using System.Threading.Tasks; + using TUnit.Mocks; + + public interface ITupleService + { + Task<(object, object)> GetPairAsync(); + ValueTask<(int Id, string Name)> GetNamedAsync(); + Task<(long, (object, object))> GetNestedAsync(); + Task<(int, string)?> GetOptionalPairAsync(); + } + + public class TestUsage + { + void M() + { + var mock = Mock.Of(); + } + } + """; + + return VerifyGeneratorOutput(source); + } + + [Test] + public Task Interface_With_IConvertible_Async_Result() + { + // #6518 review: the zero-to-enum case's own pattern is `case IConvertible` — on a + // member declared Task it would land right after `case IConvertible exact` + // and be unreachable, and CS8120 is an error. The case must not be emitted here. + var source = """ + using System; + using System.Threading.Tasks; + using TUnit.Mocks; + + public interface IConvertibleService + { + Task GetAsync(); + ValueTask ComputeAsync(); + } + + public class TestUsage + { + void M() + { + var mock = Mock.Of(); + } + } + """; + + return VerifyGeneratorOutput(source); + } + + [Test] + public Task Interface_With_Type_Parameters_Named_Like_Numeric_Types() + { + // #6518 review: the conversion tables match type names textually, so a type parameter + // literally named Int32 was mistaken for System.Int32 and numeric widening cases were + // emitted into a helper returning the open parameter (CS0029). Type parameters must + // take only the exact/null/zero-to-enum cases, all runtime-guarded. + var source = """ + using System.Threading.Tasks; + using TUnit.Mocks; + + public interface INumericNamed + { + Task GetAsync(); + Task RoundtripAsync(Int64 value); + } + + public class TestUsage + { + void M() + { + var mock = Mock.Of>(); + } + } + """; + + return VerifyGeneratorOutput(source); + } + + [Test] + public void Interface_With_IConvertible_And_Numeric_Named_Type_Parameters_Compiles() + { + var source = """ + using System; + using System.Threading.Tasks; + using TUnit.Mocks; + + public interface IConvertibleService + { + Task GetAsync(); + } + + public interface INumericNamed + { + Task GetAsync(); + Task RoundtripAsync(Int64 value); + } + + public class TestUsage + { + void M() + { + var mock = Mock.Of(); + var mock2 = Mock.Of>(); + } + } + """; + + var errors = GetGeneratedCompilationErrors(source); + + // CS8120: unreachable switch case (IConvertible zero case after IConvertible exact); + // CS0029: numeric widening cases emitted into a helper returning an open type parameter + foreach (var id in (string[])["CS8120", "CS0029"]) + { + var match = errors.FirstOrDefault(e => string.Equals(e.Id, id, StringComparison.Ordinal)); + if (match is not null) + { + throw new InvalidOperationException($"Generated code produced {id}: {match}"); + } + } + } + [Test] public Task Interface_With_Generic_Methods() { @@ -2382,6 +2638,45 @@ public class TestUsage AssertNoGeneratedError(source, "CS0535"); } + [Test] + public void Outer_Nullable_Task_Member_Keeps_Null_Lambda_Unambiguous() + { + // Regression (#6518 review): `Task?` ends in '?', so the generic-task check + // misread it as bare Task and emitted the ungated non-generic alias next to the + // synchronous factory — making the pre-existing `Returns(() => null)` setup CS0121. + var source = """ + #nullable enable + using System.Threading.Tasks; + using TUnit.Mocks; + + public interface IOuterNullableTask + { + Task? GetNameAsync(); + } + + public class TestUsage + { + void M() + { + var mock = Mock.Of(); + mock.GetNameAsync().Returns(() => null); + } + } + """; + + var output = GetGeneratedOutput(source); + + // The alias must be the gated shape (ORP on net9.0+, generic below), never the + // ungated bare-task alias. + AssertContains(output, "Returns"); + AssertContains(output, "[global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)]"); + + AssertNoGeneratedError(source, "CS0121"); + // Pre-existing on outer-nullable async members: the ReturnsAsync raw-return check used + // the annotated type in an `is` pattern. + AssertNoGeneratedError(source, "CS8116"); + } + [Test] public Task Abstract_Class_With_Abstract_Indexer() { diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_FluentUI_Shape_Nullable_Warnings.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_FluentUI_Shape_Nullable_Warnings.verified.txt index 6d51594ae5..ddfbbc0d35 100644 --- a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_FluentUI_Shape_Nullable_Warnings.verified.txt +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_FluentUI_Shape_Nullable_Warnings.verified.txt @@ -204,6 +204,24 @@ namespace TUnit.Mocks.Generated public IDialogReference_GetReturnValueAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public IDialogReference_GetReturnValueAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IDialogReference_GetReturnValueAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case T exact: return exact; + case null when typeof(T).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(T)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(T) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(T?)!; + case global::System.IConvertible zero when typeof(T).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return (T)global::System.Enum.ToObject(typeof(T), 0); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(T) + "'. Cast the factory result to the declared type in the lambda."); + } + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -600,6 +618,23 @@ namespace TUnit.Mocks.Generated public IDialogService_UpdateDialogAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public IDialogService_UpdateDialogAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IDialogService_UpdateDialogAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case global::IDialogReference exact: return exact; + case null: return default(global::IDialogReference?)!; + case global::System.IConvertible zero when typeof(global::IDialogReference).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return (global::IDialogReference)global::System.Enum.ToObject(typeof(global::IDialogReference), 0); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(global::IDialogReference) + "'. Cast the factory result to the declared type in the lambda."); + } + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -620,6 +655,12 @@ namespace TUnit.Mocks.Generated return this; } + /// 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. + public IDialogService_UpdateDialogAsync_M0_MockCall Returns(global::System.Func, global::System.Threading.Tasks.Task> factory) + { + EnsureSetup().ReturnsRaw(args => { var task = factory((string)args[0]!, (global::DialogParameters)args[1]!); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); + return this; + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -722,6 +763,24 @@ namespace TUnit.Mocks.Generated public IDialogService_ShowDialogAsync_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public IDialogService_ShowDialogAsync_M1_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IDialogService_ShowDialogAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case global::IDialogReference exact: return exact; + case null when typeof(global::IDialogReference).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(global::IDialogReference)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(global::IDialogReference) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(global::IDialogReference)!; + case global::System.IConvertible zero when typeof(global::IDialogReference).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return (global::IDialogReference)global::System.Enum.ToObject(typeof(global::IDialogReference), 0); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(global::IDialogReference) + "'. Cast the factory result to the declared type in the lambda."); + } + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -742,6 +801,12 @@ namespace TUnit.Mocks.Generated return this; } + /// 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. + public IDialogService_ShowDialogAsync_M1_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => { var task = factory((object)args[0]!, (global::DialogParameters)args[1]!); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); + return this; + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt index aa7468284c..d2d032acea 100644 --- a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt @@ -301,6 +301,22 @@ namespace TUnit.Mocks.Generated public IAsyncService_GetValueAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public IAsyncService_GetValueAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IAsyncService_GetValueAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case string exact: return exact; + case null: return default(string)!; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(string) + "'. Cast the factory result to the declared type in the lambda."); + } + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -321,6 +337,12 @@ namespace TUnit.Mocks.Generated return this; } + /// 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. + public IAsyncService_GetValueAsync_M0_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => { var task = factory((string)args[0]!); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); + return this; + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -415,11 +437,8 @@ namespace TUnit.Mocks.Generated public IAsyncService_DoWorkAsync_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public IAsyncService_DoWorkAsync_M1_MockCall ReturnsAsync(global::System.Func taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } - #if NET9_0_OR_GREATER /// 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. - [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] public IAsyncService_DoWorkAsync_M1_MockCall Returns(global::System.Func taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } - #endif // ICallVerification /// @@ -496,6 +515,28 @@ namespace TUnit.Mocks.Generated public IAsyncService_ComputeAsync_M2_MockCall ReturnsAsync(global::System.Threading.Tasks.ValueTask task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built ValueTask from a factory, invoked on each call. public IAsyncService_ComputeAsync_M2_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IAsyncService_ComputeAsync_M2_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)__TUnitMocksConvertAsyncResult(taskFactory())); return this; } + + private static global::System.Threading.Tasks.ValueTask __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.ValueTask task) + => task is global::System.Threading.Tasks.ValueTask exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.ValueTask __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.ValueTask task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case int exact: return exact; + case null when typeof(int).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(int)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(int) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(int)!; + case sbyte number: return number; + case byte number: return number; + case short number: return number; + case ushort number: return number; + case char number: return number; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(int) + "'. Cast the factory result to the declared type in the lambda."); + } + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -516,6 +557,12 @@ namespace TUnit.Mocks.Generated return this; } + /// 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. + public IAsyncService_ComputeAsync_M2_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => (object?)__TUnitMocksConvertAsyncResult(factory((int)args[0]!))); + return this; + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -612,11 +659,8 @@ namespace TUnit.Mocks.Generated public IAsyncService_InitializeAsync_M3_MockCall ReturnsAsync(global::System.Threading.Tasks.ValueTask task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built ValueTask from a factory, invoked on each call. public IAsyncService_InitializeAsync_M3_MockCall ReturnsAsync(global::System.Func taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } - #if NET9_0_OR_GREATER /// 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. - [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] public IAsyncService_InitializeAsync_M3_MockCall Returns(global::System.Func taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } - #endif /// Execute a typed callback using the actual method parameters. public IAsyncService_InitializeAsync_M3_MockCall Callback(global::System.Action callback) diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Dynamic_Async_Result.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Dynamic_Async_Result.verified.txt new file mode 100644 index 0000000000..8e52895ebd --- /dev/null +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Dynamic_Async_Result.verified.txt @@ -0,0 +1,399 @@ +// +#pragma warning disable +#nullable enable + +public sealed class IDynamicServiceMock : global::TUnit.Mocks.Mock, global::IDynamicService +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal IDynamicServiceMock(global::IDynamicService mockObject, global::TUnit.Mocks.MockEngine engine) + : base(mockObject, engine) { } + + global::System.Threading.Tasks.Task global::IDynamicService.GetAsync() => Object.GetAsync(); + + global::System.Threading.Tasks.ValueTask global::IDynamicService.ComputeAsync() => Object.ComputeAsync(); +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +file sealed class IDynamicServiceMockImpl : global::IDynamicService, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject +{ + private readonly global::TUnit.Mocks.MockEngine _engine; + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + + internal IDynamicServiceMockImpl(global::TUnit.Mocks.MockEngine engine) + { + _engine = engine; + } + + public global::System.Threading.Tasks.Task GetAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(0, "GetAsync", global::System.Array.Empty(), default!); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + public global::System.Threading.Tasks.ValueTask ComputeAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(1, "ComputeAsync", global::System.Array.Empty(), default!); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.ValueTask __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.ValueTask but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return new global::System.Threading.Tasks.ValueTask(__result); + } + catch (global::System.Exception __ex) + { + return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(__ex)); + } + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public void RaiseEvent(string eventName, object? args) + { + throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock."); + } +} + +internal static class IDynamicServiceMockFactory +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void Register() + { + global::TUnit.Mocks.MockRegistry.RegisterFactory(Create); + } + + internal static global::TUnit.Mocks.Mock CreateAutoMock(global::TUnit.Mocks.MockBehavior behavior) + { + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new IDynamicServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new IDynamicServiceMock(impl, engine); + return mock; + } + + internal static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) + { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IDynamicService' does not support constructor arguments, but {constructorArgs.Length} were provided."); + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new IDynamicServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new IDynamicServiceMock(impl, engine); + return mock; + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated +{ + public static class IDynamicService_MockMemberExtensions + { + public static IDynamicService_GetAsync_M0_MockCall GetAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new IDynamicService_GetAsync_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "GetAsync", matchers); + } + + public static IDynamicService_ComputeAsync_M1_MockCall ComputeAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new IDynamicService_ComputeAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "ComputeAsync", matchers); + } + + #if NET9_0_OR_GREATER + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void Reset(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.Reset(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyAll(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyAll(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyNoOtherCalls(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyNoOtherCalls(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetupAllProperties(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.SetupAllProperties(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static global::TUnit.Mocks.Diagnostics.MockDiagnostics GetDiagnostics(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.GetDiagnostics(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetState(this global::TUnit.Mocks.Mock mock, string? stateName) + => global::TUnit.Mocks.Mock.SetState(mock, stateName); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void InState(this global::TUnit.Mocks.Mock mock, string stateName, global::System.Action> configure) + => global::TUnit.Mocks.Mock.InState(mock, stateName, configure); + + extension(global::TUnit.Mocks.Mock mock) + { + public global::System.Collections.Generic.IReadOnlyList Invocations => global::TUnit.Mocks.Mock.Invocations(mock); + + public global::TUnit.Mocks.MockBehavior Behavior => global::TUnit.Mocks.Mock.Behavior(mock); + + public global::TUnit.Mocks.IDefaultValueProvider? DefaultValueProvider + { + get => global::TUnit.Mocks.Mock.GetDefaultValueProvider(mock); + set => global::TUnit.Mocks.Mock.SetDefaultValueProvider(mock, value); + } + } + #endif + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IDynamicService_GetAsync_M0_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal IDynamicService_GetAsync_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public IDynamicService_GetAsync_M0_MockCall Returns(dynamic value) { EnsureSetup().Returns(value); return this; } + /// + public IDynamicService_GetAsync_M0_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IDynamicService_GetAsync_M0_MockCall ReturnsSequentially(params dynamic[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public IDynamicService_GetAsync_M0_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IDynamicService_GetAsync_M0_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IDynamicService_GetAsync_M0_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IDynamicService_GetAsync_M0_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IDynamicService_GetAsync_M0_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public IDynamicService_GetAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public IDynamicService_GetAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IDynamicService_GetAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case object exact: return exact; + case null: return default(dynamic)!; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(object) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public IDynamicService_GetAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IDynamicService_ComputeAsync_M1_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal IDynamicService_ComputeAsync_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public IDynamicService_ComputeAsync_M1_MockCall Returns(dynamic value) { EnsureSetup().Returns(value); return this; } + /// + public IDynamicService_ComputeAsync_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IDynamicService_ComputeAsync_M1_MockCall ReturnsSequentially(params dynamic[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public IDynamicService_ComputeAsync_M1_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IDynamicService_ComputeAsync_M1_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IDynamicService_ComputeAsync_M1_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IDynamicService_ComputeAsync_M1_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IDynamicService_ComputeAsync_M1_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built ValueTask directly (e.g., from a TaskCompletionSource). + /// The same ValueTask instance is returned on every call. Since ValueTask may only be awaited once, + /// use the factory overload if the mock will be called multiple times, or ensure the ValueTask is backed by a Task. + public IDynamicService_ComputeAsync_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.ValueTask task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built ValueTask from a factory, invoked on each call. + public IDynamicService_ComputeAsync_M1_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IDynamicService_ComputeAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)__TUnitMocksConvertAsyncResult(taskFactory())); return this; } + + private static global::System.Threading.Tasks.ValueTask __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.ValueTask task) + => task is global::System.Threading.Tasks.ValueTask exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.ValueTask __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.ValueTask task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case object exact: return exact; + case null: return default(dynamic)!; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(object) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public IDynamicService_ComputeAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks +{ + public static class IDynamicService_MockStaticExtension + { + extension(global::IDynamicService _) + { + public static global::IDynamicServiceMock Mock() + { + return (global::IDynamicServiceMock)global::IDynamicServiceMockFactory.CreateAutoMock(global::TUnit.Mocks.Mock.DefaultBehavior); + } + + public static global::IDynamicServiceMock Mock(global::TUnit.Mocks.MockBehavior behavior) + { + return (global::IDynamicServiceMock)global::IDynamicServiceMockFactory.CreateAutoMock(behavior); + } + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated; \ No newline at end of file diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Enum_And_Defaultable_Generic_Async_Results.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Enum_And_Defaultable_Generic_Async_Results.verified.txt new file mode 100644 index 0000000000..a08f83441c --- /dev/null +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Enum_And_Defaultable_Generic_Async_Results.verified.txt @@ -0,0 +1,754 @@ +// +#pragma warning disable +#nullable enable + +public sealed class IEnumAndGenericServiceMock : global::TUnit.Mocks.Mock, global::IEnumAndGenericService +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal IEnumAndGenericServiceMock(global::IEnumAndGenericService mockObject, global::TUnit.Mocks.MockEngine engine) + : base(mockObject, engine) { } + + global::System.Threading.Tasks.Task global::IEnumAndGenericService.GetColorAsync() => Object.GetColorAsync(); + + global::System.Threading.Tasks.ValueTask global::IEnumAndGenericService.GetColorValueAsync() => Object.GetColorValueAsync(); + + global::System.Threading.Tasks.Task global::IEnumAndGenericService.FindAsync(int id) where T : default => Object.FindAsync(id); + + global::System.Threading.Tasks.Task global::IEnumAndGenericService.FindStructAsync(int id) where T : struct => Object.FindStructAsync(id); +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +file sealed class IEnumAndGenericServiceMockImpl : global::IEnumAndGenericService, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject +{ + private readonly global::TUnit.Mocks.MockEngine _engine; + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + + internal IEnumAndGenericServiceMockImpl(global::TUnit.Mocks.MockEngine engine) + { + _engine = engine; + } + + public global::System.Threading.Tasks.Task GetColorAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(0, "GetColorAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + public global::System.Threading.Tasks.ValueTask GetColorValueAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(1, "GetColorValueAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.ValueTask __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.ValueTask but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return new global::System.Threading.Tasks.ValueTask(__result); + } + catch (global::System.Exception __ex) + { + return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(__ex)); + } + } + + public global::System.Threading.Tasks.Task FindAsync(int id) + { + try + { + var __result = _engine.HandleCallWithReturn(2, "FindAsync", new object?[] { id }, default, global::TUnit.Mocks.TypeArguments.Of.Value); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + public global::System.Threading.Tasks.Task FindStructAsync(int id) where T : struct + { + try + { + var __result = _engine.HandleCallWithReturn(3, "FindStructAsync", new object?[] { id }, default, global::TUnit.Mocks.TypeArguments.Of.Value); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public void RaiseEvent(string eventName, object? args) + { + throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock."); + } +} + +internal static class IEnumAndGenericServiceMockFactory +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void Register() + { + global::TUnit.Mocks.MockRegistry.RegisterFactory(Create); + } + + internal static global::TUnit.Mocks.Mock CreateAutoMock(global::TUnit.Mocks.MockBehavior behavior) + { + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new IEnumAndGenericServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new IEnumAndGenericServiceMock(impl, engine); + return mock; + } + + internal static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) + { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IEnumAndGenericService' does not support constructor arguments, but {constructorArgs.Length} were provided."); + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new IEnumAndGenericServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new IEnumAndGenericServiceMock(impl, engine); + return mock; + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated +{ + public static class IEnumAndGenericService_MockMemberExtensions + { + public static IEnumAndGenericService_GetColorAsync_M0_MockCall GetColorAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new IEnumAndGenericService_GetColorAsync_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "GetColorAsync", matchers); + } + + public static IEnumAndGenericService_GetColorValueAsync_M1_MockCall GetColorValueAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new IEnumAndGenericService_GetColorValueAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "GetColorValueAsync", matchers); + } + + public static IEnumAndGenericService_FindAsync_M2_MockCall FindAsync(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg id) + { + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { id.Matcher }; + return new IEnumAndGenericService_FindAsync_M2_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 2, "FindAsync", matchers, global::TUnit.Mocks.TypeArguments.Of.Value); + } + + public static IEnumAndGenericService_FindAsync_M2_MockCall FindAsync(this global::TUnit.Mocks.Mock mock, global::System.Func id) + { + global::TUnit.Mocks.Arguments.Arg __fa_id = id; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_id.Matcher }; + return new IEnumAndGenericService_FindAsync_M2_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 2, "FindAsync", matchers, global::TUnit.Mocks.TypeArguments.Of.Value); + } + + public static IEnumAndGenericService_FindStructAsync_M3_MockCall FindStructAsync(this global::TUnit.Mocks.Mock mock, global::TUnit.Mocks.Arguments.Arg id) where T : struct + { + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { id.Matcher }; + return new IEnumAndGenericService_FindStructAsync_M3_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 3, "FindStructAsync", matchers, global::TUnit.Mocks.TypeArguments.Of.Value); + } + + public static IEnumAndGenericService_FindStructAsync_M3_MockCall FindStructAsync(this global::TUnit.Mocks.Mock mock, global::System.Func id) where T : struct + { + global::TUnit.Mocks.Arguments.Arg __fa_id = id; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_id.Matcher }; + return new IEnumAndGenericService_FindStructAsync_M3_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 3, "FindStructAsync", matchers, global::TUnit.Mocks.TypeArguments.Of.Value); + } + + #if NET9_0_OR_GREATER + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void Reset(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.Reset(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyAll(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyAll(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyNoOtherCalls(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyNoOtherCalls(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetupAllProperties(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.SetupAllProperties(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static global::TUnit.Mocks.Diagnostics.MockDiagnostics GetDiagnostics(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.GetDiagnostics(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetState(this global::TUnit.Mocks.Mock mock, string? stateName) + => global::TUnit.Mocks.Mock.SetState(mock, stateName); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void InState(this global::TUnit.Mocks.Mock mock, string stateName, global::System.Action> configure) + => global::TUnit.Mocks.Mock.InState(mock, stateName, configure); + + extension(global::TUnit.Mocks.Mock mock) + { + public global::System.Collections.Generic.IReadOnlyList Invocations => global::TUnit.Mocks.Mock.Invocations(mock); + + public global::TUnit.Mocks.MockBehavior Behavior => global::TUnit.Mocks.Mock.Behavior(mock); + + public global::TUnit.Mocks.IDefaultValueProvider? DefaultValueProvider + { + get => global::TUnit.Mocks.Mock.GetDefaultValueProvider(mock); + set => global::TUnit.Mocks.Mock.SetDefaultValueProvider(mock, value); + } + } + #endif + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IEnumAndGenericService_GetColorAsync_M0_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal IEnumAndGenericService_GetColorAsync_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public IEnumAndGenericService_GetColorAsync_M0_MockCall Returns(global::Color value) { EnsureSetup().Returns(value); return this; } + /// + public IEnumAndGenericService_GetColorAsync_M0_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IEnumAndGenericService_GetColorAsync_M0_MockCall ReturnsSequentially(params global::Color[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public IEnumAndGenericService_GetColorAsync_M0_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IEnumAndGenericService_GetColorAsync_M0_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IEnumAndGenericService_GetColorAsync_M0_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IEnumAndGenericService_GetColorAsync_M0_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IEnumAndGenericService_GetColorAsync_M0_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public IEnumAndGenericService_GetColorAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public IEnumAndGenericService_GetColorAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IEnumAndGenericService_GetColorAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case global::Color exact: return exact; + case null when typeof(global::Color).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(global::Color)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(global::Color) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(global::Color)!; + case global::System.IConvertible zero when typeof(global::Color).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return (global::Color)global::System.Enum.ToObject(typeof(global::Color), 0); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(global::Color) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public IEnumAndGenericService_GetColorAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IEnumAndGenericService_GetColorValueAsync_M1_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal IEnumAndGenericService_GetColorValueAsync_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall Returns(global::Color value) { EnsureSetup().Returns(value); return this; } + /// + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall ReturnsSequentially(params global::Color[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built ValueTask directly (e.g., from a TaskCompletionSource). + /// The same ValueTask instance is returned on every call. Since ValueTask may only be awaited once, + /// use the factory overload if the mock will be called multiple times, or ensure the ValueTask is backed by a Task. + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.ValueTask task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built ValueTask from a factory, invoked on each call. + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)__TUnitMocksConvertAsyncResult(taskFactory())); return this; } + + private static global::System.Threading.Tasks.ValueTask __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.ValueTask task) + => task is global::System.Threading.Tasks.ValueTask exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.ValueTask __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.ValueTask task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case global::Color exact: return exact; + case null when typeof(global::Color).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(global::Color)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(global::Color) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(global::Color)!; + case global::System.IConvertible zero when typeof(global::Color).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return (global::Color)global::System.Enum.ToObject(typeof(global::Color), 0); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(global::Color) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public IEnumAndGenericService_GetColorValueAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IEnumAndGenericService_FindAsync_M2_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private readonly global::System.Collections.Immutable.ImmutableArray _typeArguments; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal IEnumAndGenericService_FindAsync_M2_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers, global::System.Collections.Immutable.ImmutableArray typeArguments) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + _typeArguments = typeArguments; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName, _typeArguments); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public IEnumAndGenericService_FindAsync_M2_MockCall Returns(T? value) { EnsureSetup().Returns(value); return this; } + /// + public IEnumAndGenericService_FindAsync_M2_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IEnumAndGenericService_FindAsync_M2_MockCall ReturnsSequentially(params T?[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public IEnumAndGenericService_FindAsync_M2_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IEnumAndGenericService_FindAsync_M2_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IEnumAndGenericService_FindAsync_M2_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IEnumAndGenericService_FindAsync_M2_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IEnumAndGenericService_FindAsync_M2_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public IEnumAndGenericService_FindAsync_M2_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public IEnumAndGenericService_FindAsync_M2_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IEnumAndGenericService_FindAsync_M2_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case T exact: return exact; + case null when typeof(T).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(T)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(T) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(T?)!; + case global::System.IConvertible zero when typeof(T).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return (T)global::System.Enum.ToObject(typeof(T), 0); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(T) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public IEnumAndGenericService_FindAsync_M2_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + /// Configure a typed computed return value using the actual method parameters. + public IEnumAndGenericService_FindAsync_M2_MockCall Returns(global::System.Func factory) + { + EnsureSetup().Returns(args => factory((int)args[0]!)); + return this; + } + + /// Configure a typed computed async return value using the actual method parameters. + public IEnumAndGenericService_FindAsync_M2_MockCall ReturnsAsync(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => (object?)factory((int)args[0]!)); + return this; + } + + /// 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. + public IEnumAndGenericService_FindAsync_M2_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => { var task = factory((int)args[0]!); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); + return this; + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public IEnumAndGenericService_FindAsync_M2_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => (object?)factory((int)args[0]!)); + return this; + } + #endif + + /// Execute a typed callback using the actual method parameters. + public IEnumAndGenericService_FindAsync_M2_MockCall Callback(global::System.Action callback) + { + EnsureSetup().Callback(callback); + return this; + } + + /// Configure a typed computed exception using the actual method parameters. + public IEnumAndGenericService_FindAsync_M2_MockCall Throws(global::System.Func exceptionFactory) + { + EnsureSetup().Throws(args => exceptionFactory((int)args[0]!)); + return this; + } + + // ICallVerification + /// + public void WasCalled() => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(times, message); + /// + public void WasCalled(string? message) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(message); + /// + public void WasNeverCalled() => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IEnumAndGenericService_FindStructAsync_M3_MockCall : global::TUnit.Mocks.Verification.ICallVerification where T : struct + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private readonly global::System.Collections.Immutable.ImmutableArray _typeArguments; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal IEnumAndGenericService_FindStructAsync_M3_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers, global::System.Collections.Immutable.ImmutableArray typeArguments) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + _typeArguments = typeArguments; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName, _typeArguments); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public IEnumAndGenericService_FindStructAsync_M3_MockCall Returns(T? value) { EnsureSetup().Returns(value); return this; } + /// + public IEnumAndGenericService_FindStructAsync_M3_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IEnumAndGenericService_FindStructAsync_M3_MockCall ReturnsSequentially(params T?[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public IEnumAndGenericService_FindStructAsync_M3_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IEnumAndGenericService_FindStructAsync_M3_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IEnumAndGenericService_FindStructAsync_M3_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IEnumAndGenericService_FindStructAsync_M3_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IEnumAndGenericService_FindStructAsync_M3_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public IEnumAndGenericService_FindStructAsync_M3_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public IEnumAndGenericService_FindStructAsync_M3_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IEnumAndGenericService_FindStructAsync_M3_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case T exact: return exact; + case null: return default(T?)!; + case global::System.IConvertible zero when typeof(T).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return (T)global::System.Enum.ToObject(typeof(T), 0); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(T) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public IEnumAndGenericService_FindStructAsync_M3_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + /// Configure a typed computed return value using the actual method parameters. + public IEnumAndGenericService_FindStructAsync_M3_MockCall Returns(global::System.Func factory) + { + EnsureSetup().Returns(args => factory((int)args[0]!)); + return this; + } + + /// Configure a typed computed async return value using the actual method parameters. + public IEnumAndGenericService_FindStructAsync_M3_MockCall ReturnsAsync(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => (object?)factory((int)args[0]!)); + return this; + } + + /// 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. + public IEnumAndGenericService_FindStructAsync_M3_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => { var task = factory((int)args[0]!); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); + return this; + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public IEnumAndGenericService_FindStructAsync_M3_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => (object?)factory((int)args[0]!)); + return this; + } + #endif + + /// Execute a typed callback using the actual method parameters. + public IEnumAndGenericService_FindStructAsync_M3_MockCall Callback(global::System.Action callback) + { + EnsureSetup().Callback(callback); + return this; + } + + /// Configure a typed computed exception using the actual method parameters. + public IEnumAndGenericService_FindStructAsync_M3_MockCall Throws(global::System.Func exceptionFactory) + { + EnsureSetup().Throws(args => exceptionFactory((int)args[0]!)); + return this; + } + + // ICallVerification + /// + public void WasCalled() => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(times, message); + /// + public void WasCalled(string? message) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(message); + /// + public void WasNeverCalled() => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasNeverCalled(message); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks +{ + public static class IEnumAndGenericService_MockStaticExtension + { + extension(global::IEnumAndGenericService _) + { + public static global::IEnumAndGenericServiceMock Mock() + { + return (global::IEnumAndGenericServiceMock)global::IEnumAndGenericServiceMockFactory.CreateAutoMock(global::TUnit.Mocks.Mock.DefaultBehavior); + } + + public static global::IEnumAndGenericServiceMock Mock(global::TUnit.Mocks.MockBehavior behavior) + { + return (global::IEnumAndGenericServiceMock)global::IEnumAndGenericServiceMockFactory.CreateAutoMock(behavior); + } + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated; \ No newline at end of file diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_IConvertible_Async_Result.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_IConvertible_Async_Result.verified.txt new file mode 100644 index 0000000000..54a7041680 --- /dev/null +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_IConvertible_Async_Result.verified.txt @@ -0,0 +1,401 @@ +// +#pragma warning disable +#nullable enable + +public sealed class IConvertibleServiceMock : global::TUnit.Mocks.Mock, global::IConvertibleService +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal IConvertibleServiceMock(global::IConvertibleService mockObject, global::TUnit.Mocks.MockEngine engine) + : base(mockObject, engine) { } + + global::System.Threading.Tasks.Task global::IConvertibleService.GetAsync() => Object.GetAsync(); + + global::System.Threading.Tasks.ValueTask global::IConvertibleService.ComputeAsync() => Object.ComputeAsync(); +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +file sealed class IConvertibleServiceMockImpl : global::IConvertibleService, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject +{ + private readonly global::TUnit.Mocks.MockEngine _engine; + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + + internal IConvertibleServiceMockImpl(global::TUnit.Mocks.MockEngine engine) + { + _engine = engine; + } + + public global::System.Threading.Tasks.Task GetAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(0, "GetAsync", global::System.Array.Empty(), default!); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + public global::System.Threading.Tasks.ValueTask ComputeAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(1, "ComputeAsync", global::System.Array.Empty(), default!); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.ValueTask __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.ValueTask but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return new global::System.Threading.Tasks.ValueTask(__result); + } + catch (global::System.Exception __ex) + { + return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(__ex)); + } + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public void RaiseEvent(string eventName, object? args) + { + throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock."); + } +} + +internal static class IConvertibleServiceMockFactory +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void Register() + { + global::TUnit.Mocks.MockRegistry.RegisterFactory(Create); + } + + internal static global::TUnit.Mocks.Mock CreateAutoMock(global::TUnit.Mocks.MockBehavior behavior) + { + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new IConvertibleServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new IConvertibleServiceMock(impl, engine); + return mock; + } + + internal static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) + { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::IConvertibleService' does not support constructor arguments, but {constructorArgs.Length} were provided."); + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new IConvertibleServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new IConvertibleServiceMock(impl, engine); + return mock; + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated +{ + public static class IConvertibleService_MockMemberExtensions + { + public static IConvertibleService_GetAsync_M0_MockCall GetAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new IConvertibleService_GetAsync_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "GetAsync", matchers); + } + + public static IConvertibleService_ComputeAsync_M1_MockCall ComputeAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new IConvertibleService_ComputeAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "ComputeAsync", matchers); + } + + #if NET9_0_OR_GREATER + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void Reset(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.Reset(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyAll(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyAll(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyNoOtherCalls(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyNoOtherCalls(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetupAllProperties(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.SetupAllProperties(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static global::TUnit.Mocks.Diagnostics.MockDiagnostics GetDiagnostics(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.GetDiagnostics(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetState(this global::TUnit.Mocks.Mock mock, string? stateName) + => global::TUnit.Mocks.Mock.SetState(mock, stateName); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void InState(this global::TUnit.Mocks.Mock mock, string stateName, global::System.Action> configure) + => global::TUnit.Mocks.Mock.InState(mock, stateName, configure); + + extension(global::TUnit.Mocks.Mock mock) + { + public global::System.Collections.Generic.IReadOnlyList Invocations => global::TUnit.Mocks.Mock.Invocations(mock); + + public global::TUnit.Mocks.MockBehavior Behavior => global::TUnit.Mocks.Mock.Behavior(mock); + + public global::TUnit.Mocks.IDefaultValueProvider? DefaultValueProvider + { + get => global::TUnit.Mocks.Mock.GetDefaultValueProvider(mock); + set => global::TUnit.Mocks.Mock.SetDefaultValueProvider(mock, value); + } + } + #endif + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IConvertibleService_GetAsync_M0_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal IConvertibleService_GetAsync_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public IConvertibleService_GetAsync_M0_MockCall Returns(global::System.IConvertible value) { EnsureSetup().Returns(value); return this; } + /// + public IConvertibleService_GetAsync_M0_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IConvertibleService_GetAsync_M0_MockCall ReturnsSequentially(params global::System.IConvertible[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public IConvertibleService_GetAsync_M0_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IConvertibleService_GetAsync_M0_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IConvertibleService_GetAsync_M0_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IConvertibleService_GetAsync_M0_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IConvertibleService_GetAsync_M0_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public IConvertibleService_GetAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public IConvertibleService_GetAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IConvertibleService_GetAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case global::System.IConvertible exact: return exact; + case null when typeof(global::System.IConvertible).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(global::System.IConvertible)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(global::System.IConvertible) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(global::System.IConvertible)!; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(global::System.IConvertible) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public IConvertibleService_GetAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class IConvertibleService_ComputeAsync_M1_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal IConvertibleService_ComputeAsync_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public IConvertibleService_ComputeAsync_M1_MockCall Returns(global::System.IConvertible value) { EnsureSetup().Returns(value); return this; } + /// + public IConvertibleService_ComputeAsync_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public IConvertibleService_ComputeAsync_M1_MockCall ReturnsSequentially(params global::System.IConvertible[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public IConvertibleService_ComputeAsync_M1_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public IConvertibleService_ComputeAsync_M1_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public IConvertibleService_ComputeAsync_M1_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public IConvertibleService_ComputeAsync_M1_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public IConvertibleService_ComputeAsync_M1_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built ValueTask directly (e.g., from a TaskCompletionSource). + /// The same ValueTask instance is returned on every call. Since ValueTask may only be awaited once, + /// use the factory overload if the mock will be called multiple times, or ensure the ValueTask is backed by a Task. + public IConvertibleService_ComputeAsync_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.ValueTask task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built ValueTask from a factory, invoked on each call. + public IConvertibleService_ComputeAsync_M1_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IConvertibleService_ComputeAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)__TUnitMocksConvertAsyncResult(taskFactory())); return this; } + + private static global::System.Threading.Tasks.ValueTask __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.ValueTask task) + => task is global::System.Threading.Tasks.ValueTask exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.ValueTask __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.ValueTask task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case global::System.IConvertible exact: return exact; + case null when typeof(global::System.IConvertible).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(global::System.IConvertible)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(global::System.IConvertible) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(global::System.IConvertible)!; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(global::System.IConvertible) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public IConvertibleService_ComputeAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks +{ + public static class IConvertibleService_MockStaticExtension + { + extension(global::IConvertibleService _) + { + public static global::IConvertibleServiceMock Mock() + { + return (global::IConvertibleServiceMock)global::IConvertibleServiceMockFactory.CreateAutoMock(global::TUnit.Mocks.Mock.DefaultBehavior); + } + + public static global::IConvertibleServiceMock Mock(global::TUnit.Mocks.MockBehavior behavior) + { + return (global::IConvertibleServiceMock)global::IConvertibleServiceMockFactory.CreateAutoMock(behavior); + } + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated; \ No newline at end of file diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt index 7efa47cbc1..80cbe2c140 100644 --- a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt @@ -318,6 +318,22 @@ namespace TUnit.Mocks.Generated public IService_GetAsync_M3_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public IService_GetAsync_M3_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IService_GetAsync_M3_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case string exact: return exact; + case null: return default(string)!; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(string) + "'. Cast the factory result to the declared type in the lambda."); + } + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -338,6 +354,12 @@ namespace TUnit.Mocks.Generated return this; } + /// 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. + public IService_GetAsync_M3_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => { var task = factory((int)args[0]!); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); + return this; + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Native_Integer_Async_Results.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Native_Integer_Async_Results.verified.txt new file mode 100644 index 0000000000..378158392c --- /dev/null +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Native_Integer_Async_Results.verified.txt @@ -0,0 +1,542 @@ +// +#pragma warning disable +#nullable enable + +public sealed class INativeIntServiceMock : global::TUnit.Mocks.Mock, global::INativeIntService +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal INativeIntServiceMock(global::INativeIntService mockObject, global::TUnit.Mocks.MockEngine engine) + : base(mockObject, engine) { } + + global::System.Threading.Tasks.Task global::INativeIntService.GetLongAsync() => Object.GetLongAsync(); + + global::System.Threading.Tasks.Task global::INativeIntService.GetNativeAsync() => Object.GetNativeAsync(); + + global::System.Threading.Tasks.ValueTask global::INativeIntService.GetUnsignedNativeAsync() => Object.GetUnsignedNativeAsync(); +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +file sealed class INativeIntServiceMockImpl : global::INativeIntService, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject +{ + private readonly global::TUnit.Mocks.MockEngine _engine; + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + + internal INativeIntServiceMockImpl(global::TUnit.Mocks.MockEngine engine) + { + _engine = engine; + } + + public global::System.Threading.Tasks.Task GetLongAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(0, "GetLongAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + public global::System.Threading.Tasks.Task GetNativeAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(1, "GetNativeAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + public global::System.Threading.Tasks.ValueTask GetUnsignedNativeAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(2, "GetUnsignedNativeAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.ValueTask __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.ValueTask but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return new global::System.Threading.Tasks.ValueTask(__result); + } + catch (global::System.Exception __ex) + { + return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(__ex)); + } + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public void RaiseEvent(string eventName, object? args) + { + throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock."); + } +} + +internal static class INativeIntServiceMockFactory +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void Register() + { + global::TUnit.Mocks.MockRegistry.RegisterFactory(Create); + } + + internal static global::TUnit.Mocks.Mock CreateAutoMock(global::TUnit.Mocks.MockBehavior behavior) + { + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new INativeIntServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new INativeIntServiceMock(impl, engine); + return mock; + } + + internal static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) + { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::INativeIntService' does not support constructor arguments, but {constructorArgs.Length} were provided."); + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new INativeIntServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new INativeIntServiceMock(impl, engine); + return mock; + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated +{ + public static class INativeIntService_MockMemberExtensions + { + public static INativeIntService_GetLongAsync_M0_MockCall GetLongAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new INativeIntService_GetLongAsync_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "GetLongAsync", matchers); + } + + public static INativeIntService_GetNativeAsync_M1_MockCall GetNativeAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new INativeIntService_GetNativeAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "GetNativeAsync", matchers); + } + + public static INativeIntService_GetUnsignedNativeAsync_M2_MockCall GetUnsignedNativeAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new INativeIntService_GetUnsignedNativeAsync_M2_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 2, "GetUnsignedNativeAsync", matchers); + } + + #if NET9_0_OR_GREATER + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void Reset(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.Reset(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyAll(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyAll(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyNoOtherCalls(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyNoOtherCalls(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetupAllProperties(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.SetupAllProperties(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static global::TUnit.Mocks.Diagnostics.MockDiagnostics GetDiagnostics(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.GetDiagnostics(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetState(this global::TUnit.Mocks.Mock mock, string? stateName) + => global::TUnit.Mocks.Mock.SetState(mock, stateName); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void InState(this global::TUnit.Mocks.Mock mock, string stateName, global::System.Action> configure) + => global::TUnit.Mocks.Mock.InState(mock, stateName, configure); + + extension(global::TUnit.Mocks.Mock mock) + { + public global::System.Collections.Generic.IReadOnlyList Invocations => global::TUnit.Mocks.Mock.Invocations(mock); + + public global::TUnit.Mocks.MockBehavior Behavior => global::TUnit.Mocks.Mock.Behavior(mock); + + public global::TUnit.Mocks.IDefaultValueProvider? DefaultValueProvider + { + get => global::TUnit.Mocks.Mock.GetDefaultValueProvider(mock); + set => global::TUnit.Mocks.Mock.SetDefaultValueProvider(mock, value); + } + } + #endif + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class INativeIntService_GetLongAsync_M0_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal INativeIntService_GetLongAsync_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public INativeIntService_GetLongAsync_M0_MockCall Returns(long value) { EnsureSetup().Returns(value); return this; } + /// + public INativeIntService_GetLongAsync_M0_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public INativeIntService_GetLongAsync_M0_MockCall ReturnsSequentially(params long[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public INativeIntService_GetLongAsync_M0_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public INativeIntService_GetLongAsync_M0_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public INativeIntService_GetLongAsync_M0_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public INativeIntService_GetLongAsync_M0_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public INativeIntService_GetLongAsync_M0_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public INativeIntService_GetLongAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public INativeIntService_GetLongAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public INativeIntService_GetLongAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case long exact: return exact; + case null when typeof(long).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(long)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(long) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(long)!; + case sbyte number: return number; + case byte number: return number; + case short number: return number; + case ushort number: return number; + case int number: return number; + case uint number: return number; + case char number: return number; + case nint number: return number; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(long) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public INativeIntService_GetLongAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class INativeIntService_GetNativeAsync_M1_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal INativeIntService_GetNativeAsync_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public INativeIntService_GetNativeAsync_M1_MockCall Returns(nint value) { EnsureSetup().Returns(value); return this; } + /// + public INativeIntService_GetNativeAsync_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public INativeIntService_GetNativeAsync_M1_MockCall ReturnsSequentially(params nint[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public INativeIntService_GetNativeAsync_M1_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public INativeIntService_GetNativeAsync_M1_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public INativeIntService_GetNativeAsync_M1_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public INativeIntService_GetNativeAsync_M1_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public INativeIntService_GetNativeAsync_M1_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public INativeIntService_GetNativeAsync_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public INativeIntService_GetNativeAsync_M1_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public INativeIntService_GetNativeAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case nint exact: return exact; + case null when typeof(nint).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(nint)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(nint) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(nint)!; + case sbyte number: return number; + case byte number: return number; + case short number: return number; + case ushort number: return number; + case int number: return number; + case char number: return number; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(nint) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public INativeIntService_GetNativeAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class INativeIntService_GetUnsignedNativeAsync_M2_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal INativeIntService_GetUnsignedNativeAsync_M2_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall Returns(nuint value) { EnsureSetup().Returns(value); return this; } + /// + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall ReturnsSequentially(params nuint[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built ValueTask directly (e.g., from a TaskCompletionSource). + /// The same ValueTask instance is returned on every call. Since ValueTask may only be awaited once, + /// use the factory overload if the mock will be called multiple times, or ensure the ValueTask is backed by a Task. + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall ReturnsAsync(global::System.Threading.Tasks.ValueTask task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built ValueTask from a factory, invoked on each call. + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)__TUnitMocksConvertAsyncResult(taskFactory())); return this; } + + private static global::System.Threading.Tasks.ValueTask __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.ValueTask task) + => task is global::System.Threading.Tasks.ValueTask exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.ValueTask __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.ValueTask task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case nuint exact: return exact; + case null when typeof(nuint).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(nuint)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(nuint) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(nuint)!; + case byte number: return number; + case ushort number: return number; + case uint number: return number; + case char number: return number; + case int number when number >= 0: return (nuint)number; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(nuint) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public INativeIntService_GetUnsignedNativeAsync_M2_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks +{ + public static class INativeIntService_MockStaticExtension + { + extension(global::INativeIntService _) + { + public static global::INativeIntServiceMock Mock() + { + return (global::INativeIntServiceMock)global::INativeIntServiceMockFactory.CreateAutoMock(global::TUnit.Mocks.Mock.DefaultBehavior); + } + + public static global::INativeIntServiceMock Mock(global::TUnit.Mocks.MockBehavior behavior) + { + return (global::INativeIntServiceMock)global::INativeIntServiceMockFactory.CreateAutoMock(behavior); + } + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated; \ No newline at end of file diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt index f5262ada93..5f142df970 100644 --- a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt @@ -615,6 +615,22 @@ namespace TUnit.Mocks.Generated public IFoo_GetAsync_M3_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public IFoo_GetAsync_M3_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IFoo_GetAsync_M3_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case string exact: return exact; + case null: return default(string?)!; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(string) + "'. Cast the factory result to the declared type in the lambda."); + } + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -635,6 +651,12 @@ namespace TUnit.Mocks.Generated return this; } + /// 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. + public IFoo_GetAsync_M3_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => { var task = factory((string?)args[0]); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); + return this; + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Obsolete_Members.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Obsolete_Members.verified.txt index 8eb72543ea..a9d001ecbc 100644 --- a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Obsolete_Members.verified.txt +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Obsolete_Members.verified.txt @@ -709,6 +709,22 @@ namespace TUnit.Mocks.Generated public IDialogService_ShowPanel_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public IDialogService_ShowPanel_M1_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IDialogService_ShowPanel_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case string exact: return exact; + case null: return default(string?)!; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(string) + "'. Cast the factory result to the declared type in the lambda."); + } + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] @@ -729,6 +745,12 @@ namespace TUnit.Mocks.Generated return this; } + /// 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. + public IDialogService_ShowPanel_M1_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => { var task = factory((TData?)args[0]); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); + return this; + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Small_Numeric_Async_Results.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Small_Numeric_Async_Results.verified.txt new file mode 100644 index 0000000000..0d46ff0565 --- /dev/null +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Small_Numeric_Async_Results.verified.txt @@ -0,0 +1,534 @@ +// +#pragma warning disable +#nullable enable + +public sealed class ISmallNumericServiceMock : global::TUnit.Mocks.Mock, global::ISmallNumericService +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal ISmallNumericServiceMock(global::ISmallNumericService mockObject, global::TUnit.Mocks.MockEngine engine) + : base(mockObject, engine) { } + + global::System.Threading.Tasks.Task global::ISmallNumericService.GetByteAsync() => Object.GetByteAsync(); + + global::System.Threading.Tasks.ValueTask global::ISmallNumericService.GetShortAsync() => Object.GetShortAsync(); + + global::System.Threading.Tasks.Task global::ISmallNumericService.GetUnsignedAsync() => Object.GetUnsignedAsync(); +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +file sealed class ISmallNumericServiceMockImpl : global::ISmallNumericService, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject +{ + private readonly global::TUnit.Mocks.MockEngine _engine; + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + + internal ISmallNumericServiceMockImpl(global::TUnit.Mocks.MockEngine engine) + { + _engine = engine; + } + + public global::System.Threading.Tasks.Task GetByteAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(0, "GetByteAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + public global::System.Threading.Tasks.ValueTask GetShortAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(1, "GetShortAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.ValueTask __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.ValueTask but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return new global::System.Threading.Tasks.ValueTask(__result); + } + catch (global::System.Exception __ex) + { + return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(__ex)); + } + } + + public global::System.Threading.Tasks.Task GetUnsignedAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(2, "GetUnsignedAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public void RaiseEvent(string eventName, object? args) + { + throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock."); + } +} + +internal static class ISmallNumericServiceMockFactory +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void Register() + { + global::TUnit.Mocks.MockRegistry.RegisterFactory(Create); + } + + internal static global::TUnit.Mocks.Mock CreateAutoMock(global::TUnit.Mocks.MockBehavior behavior) + { + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new ISmallNumericServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new ISmallNumericServiceMock(impl, engine); + return mock; + } + + internal static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) + { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::ISmallNumericService' does not support constructor arguments, but {constructorArgs.Length} were provided."); + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new ISmallNumericServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new ISmallNumericServiceMock(impl, engine); + return mock; + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated +{ + public static class ISmallNumericService_MockMemberExtensions + { + public static ISmallNumericService_GetByteAsync_M0_MockCall GetByteAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new ISmallNumericService_GetByteAsync_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "GetByteAsync", matchers); + } + + public static ISmallNumericService_GetShortAsync_M1_MockCall GetShortAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new ISmallNumericService_GetShortAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "GetShortAsync", matchers); + } + + public static ISmallNumericService_GetUnsignedAsync_M2_MockCall GetUnsignedAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new ISmallNumericService_GetUnsignedAsync_M2_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 2, "GetUnsignedAsync", matchers); + } + + #if NET9_0_OR_GREATER + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void Reset(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.Reset(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyAll(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyAll(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyNoOtherCalls(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyNoOtherCalls(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetupAllProperties(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.SetupAllProperties(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static global::TUnit.Mocks.Diagnostics.MockDiagnostics GetDiagnostics(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.GetDiagnostics(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetState(this global::TUnit.Mocks.Mock mock, string? stateName) + => global::TUnit.Mocks.Mock.SetState(mock, stateName); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void InState(this global::TUnit.Mocks.Mock mock, string stateName, global::System.Action> configure) + => global::TUnit.Mocks.Mock.InState(mock, stateName, configure); + + extension(global::TUnit.Mocks.Mock mock) + { + public global::System.Collections.Generic.IReadOnlyList Invocations => global::TUnit.Mocks.Mock.Invocations(mock); + + public global::TUnit.Mocks.MockBehavior Behavior => global::TUnit.Mocks.Mock.Behavior(mock); + + public global::TUnit.Mocks.IDefaultValueProvider? DefaultValueProvider + { + get => global::TUnit.Mocks.Mock.GetDefaultValueProvider(mock); + set => global::TUnit.Mocks.Mock.SetDefaultValueProvider(mock, value); + } + } + #endif + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class ISmallNumericService_GetByteAsync_M0_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal ISmallNumericService_GetByteAsync_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public ISmallNumericService_GetByteAsync_M0_MockCall Returns(byte value) { EnsureSetup().Returns(value); return this; } + /// + public ISmallNumericService_GetByteAsync_M0_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public ISmallNumericService_GetByteAsync_M0_MockCall ReturnsSequentially(params byte[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public ISmallNumericService_GetByteAsync_M0_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public ISmallNumericService_GetByteAsync_M0_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public ISmallNumericService_GetByteAsync_M0_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public ISmallNumericService_GetByteAsync_M0_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public ISmallNumericService_GetByteAsync_M0_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public ISmallNumericService_GetByteAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public ISmallNumericService_GetByteAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public ISmallNumericService_GetByteAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case byte exact: return exact; + case null when typeof(byte).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(byte)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(byte) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(byte)!; + case int number when number >= global::System.Byte.MinValue && number <= global::System.Byte.MaxValue: return (byte)number; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(byte) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public ISmallNumericService_GetByteAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class ISmallNumericService_GetShortAsync_M1_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal ISmallNumericService_GetShortAsync_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public ISmallNumericService_GetShortAsync_M1_MockCall Returns(short value) { EnsureSetup().Returns(value); return this; } + /// + public ISmallNumericService_GetShortAsync_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public ISmallNumericService_GetShortAsync_M1_MockCall ReturnsSequentially(params short[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public ISmallNumericService_GetShortAsync_M1_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public ISmallNumericService_GetShortAsync_M1_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public ISmallNumericService_GetShortAsync_M1_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public ISmallNumericService_GetShortAsync_M1_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public ISmallNumericService_GetShortAsync_M1_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built ValueTask directly (e.g., from a TaskCompletionSource). + /// The same ValueTask instance is returned on every call. Since ValueTask may only be awaited once, + /// use the factory overload if the mock will be called multiple times, or ensure the ValueTask is backed by a Task. + public ISmallNumericService_GetShortAsync_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.ValueTask task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built ValueTask from a factory, invoked on each call. + public ISmallNumericService_GetShortAsync_M1_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public ISmallNumericService_GetShortAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)__TUnitMocksConvertAsyncResult(taskFactory())); return this; } + + private static global::System.Threading.Tasks.ValueTask __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.ValueTask task) + => task is global::System.Threading.Tasks.ValueTask exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.ValueTask __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.ValueTask task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case short exact: return exact; + case null when typeof(short).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(short)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(short) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(short)!; + case sbyte number: return number; + case byte number: return number; + case int number when number >= global::System.Int16.MinValue && number <= global::System.Int16.MaxValue: return (short)number; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(short) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public ISmallNumericService_GetShortAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class ISmallNumericService_GetUnsignedAsync_M2_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal ISmallNumericService_GetUnsignedAsync_M2_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public ISmallNumericService_GetUnsignedAsync_M2_MockCall Returns(ulong value) { EnsureSetup().Returns(value); return this; } + /// + public ISmallNumericService_GetUnsignedAsync_M2_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public ISmallNumericService_GetUnsignedAsync_M2_MockCall ReturnsSequentially(params ulong[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public ISmallNumericService_GetUnsignedAsync_M2_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public ISmallNumericService_GetUnsignedAsync_M2_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public ISmallNumericService_GetUnsignedAsync_M2_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public ISmallNumericService_GetUnsignedAsync_M2_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public ISmallNumericService_GetUnsignedAsync_M2_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public ISmallNumericService_GetUnsignedAsync_M2_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public ISmallNumericService_GetUnsignedAsync_M2_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public ISmallNumericService_GetUnsignedAsync_M2_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case ulong exact: return exact; + case null when typeof(ulong).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(ulong)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(ulong) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(ulong)!; + case byte number: return number; + case ushort number: return number; + case uint number: return number; + case char number: return number; + case nuint number: return number; + case int number when number >= 0: return (ulong)number; + case long number when number >= 0: return (ulong)number; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(ulong) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public ISmallNumericService_GetUnsignedAsync_M2_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks +{ + public static class ISmallNumericService_MockStaticExtension + { + extension(global::ISmallNumericService _) + { + public static global::ISmallNumericServiceMock Mock() + { + return (global::ISmallNumericServiceMock)global::ISmallNumericServiceMockFactory.CreateAutoMock(global::TUnit.Mocks.Mock.DefaultBehavior); + } + + public static global::ISmallNumericServiceMock Mock(global::TUnit.Mocks.MockBehavior behavior) + { + return (global::ISmallNumericServiceMock)global::ISmallNumericServiceMockFactory.CreateAutoMock(behavior); + } + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated; \ No newline at end of file diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Tuple_Async_Results.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Tuple_Async_Results.verified.txt new file mode 100644 index 0000000000..fccf6cdb97 --- /dev/null +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Tuple_Async_Results.verified.txt @@ -0,0 +1,762 @@ +// +#pragma warning disable +#nullable enable + +public sealed class ITupleServiceMock : global::TUnit.Mocks.Mock, global::ITupleService +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal ITupleServiceMock(global::ITupleService mockObject, global::TUnit.Mocks.MockEngine engine) + : base(mockObject, engine) { } + + global::System.Threading.Tasks.Task<(object, object)> global::ITupleService.GetPairAsync() => Object.GetPairAsync(); + + global::System.Threading.Tasks.ValueTask<(int Id, string Name)> global::ITupleService.GetNamedAsync() => Object.GetNamedAsync(); + + global::System.Threading.Tasks.Task<(long, (object, object))> global::ITupleService.GetNestedAsync() => Object.GetNestedAsync(); + + global::System.Threading.Tasks.Task<(int, string)?> global::ITupleService.GetOptionalPairAsync() => Object.GetOptionalPairAsync(); +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +file sealed class ITupleServiceMockImpl : global::ITupleService, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject +{ + private readonly global::TUnit.Mocks.MockEngine _engine; + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + + internal ITupleServiceMockImpl(global::TUnit.Mocks.MockEngine engine) + { + _engine = engine; + } + + public global::System.Threading.Tasks.Task<(object, object)> GetPairAsync() + { + try + { + var __result = _engine.HandleCallWithReturn<(object, object)>(0, "GetPairAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task<(object, object)> __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task<(object, object)> but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult<(object, object)>(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException<(object, object)>(__ex); + } + } + + public global::System.Threading.Tasks.ValueTask<(int Id, string Name)> GetNamedAsync() + { + try + { + var __result = _engine.HandleCallWithReturn<(int Id, string Name)>(1, "GetNamedAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.ValueTask<(int Id, string Name)> __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.ValueTask<(int Id, string Name)> but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return new global::System.Threading.Tasks.ValueTask<(int Id, string Name)>(__result); + } + catch (global::System.Exception __ex) + { + return new global::System.Threading.Tasks.ValueTask<(int Id, string Name)>(global::System.Threading.Tasks.Task.FromException<(int Id, string Name)>(__ex)); + } + } + + public global::System.Threading.Tasks.Task<(long, (object, object))> GetNestedAsync() + { + try + { + var __result = _engine.HandleCallWithReturn<(long, (object, object))>(2, "GetNestedAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task<(long, (object, object))> __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task<(long, (object, object))> but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult<(long, (object, object))>(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException<(long, (object, object))>(__ex); + } + } + + public global::System.Threading.Tasks.Task<(int, string)?> GetOptionalPairAsync() + { + try + { + var __result = _engine.HandleCallWithReturn<(int, string)?>(3, "GetOptionalPairAsync", global::System.Array.Empty(), default); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task<(int, string)?> __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task<(int, string)?> but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult<(int, string)?>(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException<(int, string)?>(__ex); + } + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public void RaiseEvent(string eventName, object? args) + { + throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock."); + } +} + +internal static class ITupleServiceMockFactory +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void Register() + { + global::TUnit.Mocks.MockRegistry.RegisterFactory(Create); + } + + internal static global::TUnit.Mocks.Mock CreateAutoMock(global::TUnit.Mocks.MockBehavior behavior) + { + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new ITupleServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new ITupleServiceMock(impl, engine); + return mock; + } + + internal static global::TUnit.Mocks.Mock Create(global::TUnit.Mocks.MockBehavior behavior, object[] constructorArgs) + { + if (constructorArgs.Length > 0) throw new global::System.ArgumentException($"Interface mock 'global::ITupleService' does not support constructor arguments, but {constructorArgs.Length} were provided."); + var engine = new global::TUnit.Mocks.MockEngine(behavior); + var impl = new ITupleServiceMockImpl(engine); + engine.Raisable = impl; + var mock = new ITupleServiceMock(impl, engine); + return mock; + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated +{ + public static class ITupleService_MockMemberExtensions + { + public static ITupleService_GetPairAsync_M0_MockCall GetPairAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new ITupleService_GetPairAsync_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "GetPairAsync", matchers); + } + + public static ITupleService_GetNamedAsync_M1_MockCall GetNamedAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new ITupleService_GetNamedAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "GetNamedAsync", matchers); + } + + public static ITupleService_GetNestedAsync_M2_MockCall GetNestedAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new ITupleService_GetNestedAsync_M2_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 2, "GetNestedAsync", matchers); + } + + public static ITupleService_GetOptionalPairAsync_M3_MockCall GetOptionalPairAsync(this global::TUnit.Mocks.Mock mock) + { + var matchers = global::System.Array.Empty(); + return new ITupleService_GetOptionalPairAsync_M3_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 3, "GetOptionalPairAsync", matchers); + } + + #if NET9_0_OR_GREATER + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void Reset(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.Reset(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyAll(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyAll(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyNoOtherCalls(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.VerifyNoOtherCalls(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetupAllProperties(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.SetupAllProperties(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static global::TUnit.Mocks.Diagnostics.MockDiagnostics GetDiagnostics(this global::TUnit.Mocks.Mock mock) + => global::TUnit.Mocks.Mock.GetDiagnostics(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetState(this global::TUnit.Mocks.Mock mock, string? stateName) + => global::TUnit.Mocks.Mock.SetState(mock, stateName); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void InState(this global::TUnit.Mocks.Mock mock, string stateName, global::System.Action> configure) + => global::TUnit.Mocks.Mock.InState(mock, stateName, configure); + + extension(global::TUnit.Mocks.Mock mock) + { + public global::System.Collections.Generic.IReadOnlyList Invocations => global::TUnit.Mocks.Mock.Invocations(mock); + + public global::TUnit.Mocks.MockBehavior Behavior => global::TUnit.Mocks.Mock.Behavior(mock); + + public global::TUnit.Mocks.IDefaultValueProvider? DefaultValueProvider + { + get => global::TUnit.Mocks.Mock.GetDefaultValueProvider(mock); + set => global::TUnit.Mocks.Mock.SetDefaultValueProvider(mock, value); + } + } + #endif + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class ITupleService_GetPairAsync_M0_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(object, object)>? _builder; + + internal ITupleService_GetPairAsync_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(object, object)> EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(object, object)> EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder<(object, object)>(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public ITupleService_GetPairAsync_M0_MockCall Returns((object, object) value) { EnsureSetup().Returns(value); return this; } + /// + public ITupleService_GetPairAsync_M0_MockCall Returns(global::System.Func<(object, object)> factory) { EnsureSetup().Returns(factory); return this; } + /// + public ITupleService_GetPairAsync_M0_MockCall ReturnsSequentially(params (object, object)[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public ITupleService_GetPairAsync_M0_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public ITupleService_GetPairAsync_M0_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public ITupleService_GetPairAsync_M0_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public ITupleService_GetPairAsync_M0_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public ITupleService_GetPairAsync_M0_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public ITupleService_GetPairAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task<(object, object)> task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public ITupleService_GetPairAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public ITupleService_GetPairAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task<(object, object)> __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task<(object, object)> exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task<(object, object)> __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '(object, object)' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case global::System.Runtime.CompilerServices.ITuple tuple when tuple.GetType().IsValueType && tuple.Length == 2: return (__TUnitMocksConvertAsyncTupleItem_0(tuple[0]), __TUnitMocksConvertAsyncTupleItem_1(tuple[1])); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '(object, object)'. Cast the factory result to the declared type in the lambda."); + } + } + + private static object __TUnitMocksConvertAsyncTupleItem_0(object? value) + { + switch (value) + { + case object exact: return exact; + case null: return default(object)!; + default: throw new global::System.InvalidCastException("The async factory produced a tuple element of type '" + value.GetType() + "', which is not convertible to the declared tuple element type '" + typeof(object) + "'. Cast the factory result to the declared type in the lambda."); + } + } + + private static object __TUnitMocksConvertAsyncTupleItem_1(object? value) + { + switch (value) + { + case object exact: return exact; + case null: return default(object)!; + default: throw new global::System.InvalidCastException("The async factory produced a tuple element of type '" + value.GetType() + "', which is not convertible to the declared tuple element type '" + typeof(object) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public ITupleService_GetPairAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class ITupleService_GetNamedAsync_M1_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(int Id, string Name)>? _builder; + + internal ITupleService_GetNamedAsync_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(int Id, string Name)> EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(int Id, string Name)> EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder<(int Id, string Name)>(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public ITupleService_GetNamedAsync_M1_MockCall Returns((int Id, string Name) value) { EnsureSetup().Returns(value); return this; } + /// + public ITupleService_GetNamedAsync_M1_MockCall Returns(global::System.Func<(int Id, string Name)> factory) { EnsureSetup().Returns(factory); return this; } + /// + public ITupleService_GetNamedAsync_M1_MockCall ReturnsSequentially(params (int Id, string Name)[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public ITupleService_GetNamedAsync_M1_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public ITupleService_GetNamedAsync_M1_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public ITupleService_GetNamedAsync_M1_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public ITupleService_GetNamedAsync_M1_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public ITupleService_GetNamedAsync_M1_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built ValueTask directly (e.g., from a TaskCompletionSource). + /// The same ValueTask instance is returned on every call. Since ValueTask may only be awaited once, + /// use the factory overload if the mock will be called multiple times, or ensure the ValueTask is backed by a Task. + public ITupleService_GetNamedAsync_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.ValueTask<(int Id, string Name)> task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built ValueTask from a factory, invoked on each call. + public ITupleService_GetNamedAsync_M1_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public ITupleService_GetNamedAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)__TUnitMocksConvertAsyncResult(taskFactory())); return this; } + + private static global::System.Threading.Tasks.ValueTask<(int Id, string Name)> __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.ValueTask task) + => task is global::System.Threading.Tasks.ValueTask<(int, string)> exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.ValueTask<(int Id, string Name)> __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.ValueTask task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '(int, string)' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case global::System.Runtime.CompilerServices.ITuple tuple when tuple.GetType().IsValueType && tuple.Length == 2: return (__TUnitMocksConvertAsyncTupleItem_0(tuple[0]), __TUnitMocksConvertAsyncTupleItem_1(tuple[1])); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '(int, string)'. Cast the factory result to the declared type in the lambda."); + } + } + + private static int __TUnitMocksConvertAsyncTupleItem_0(object? value) + { + switch (value) + { + case int exact: return exact; + case null when typeof(int).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(int)) is null: throw new global::System.InvalidCastException("The async factory produced a null tuple element, but the declared tuple element type '" + typeof(int) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(int)!; + case sbyte number: return number; + case byte number: return number; + case short number: return number; + case ushort number: return number; + case char number: return number; + default: throw new global::System.InvalidCastException("The async factory produced a tuple element of type '" + value.GetType() + "', which is not convertible to the declared tuple element type '" + typeof(int) + "'. Cast the factory result to the declared type in the lambda."); + } + } + + private static string __TUnitMocksConvertAsyncTupleItem_1(object? value) + { + switch (value) + { + case string exact: return exact; + case null: return default(string)!; + default: throw new global::System.InvalidCastException("The async factory produced a tuple element of type '" + value.GetType() + "', which is not convertible to the declared tuple element type '" + typeof(string) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public ITupleService_GetNamedAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class ITupleService_GetNestedAsync_M2_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(long, (object, object))>? _builder; + + internal ITupleService_GetNestedAsync_M2_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(long, (object, object))> EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(long, (object, object))> EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder<(long, (object, object))>(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public ITupleService_GetNestedAsync_M2_MockCall Returns((long, (object, object)) value) { EnsureSetup().Returns(value); return this; } + /// + public ITupleService_GetNestedAsync_M2_MockCall Returns(global::System.Func<(long, (object, object))> factory) { EnsureSetup().Returns(factory); return this; } + /// + public ITupleService_GetNestedAsync_M2_MockCall ReturnsSequentially(params (long, (object, object))[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public ITupleService_GetNestedAsync_M2_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public ITupleService_GetNestedAsync_M2_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public ITupleService_GetNestedAsync_M2_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public ITupleService_GetNestedAsync_M2_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public ITupleService_GetNestedAsync_M2_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public ITupleService_GetNestedAsync_M2_MockCall ReturnsAsync(global::System.Threading.Tasks.Task<(long, (object, object))> task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public ITupleService_GetNestedAsync_M2_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public ITupleService_GetNestedAsync_M2_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task<(long, (object, object))> __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task<(long, (object, object))> exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task<(long, (object, object))> __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '(long, (object, object))' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case global::System.Runtime.CompilerServices.ITuple tuple when tuple.GetType().IsValueType && tuple.Length == 2: return (__TUnitMocksConvertAsyncTupleItem_0(tuple[0]), __TUnitMocksConvertAsyncTupleItem_1(tuple[1])); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '(long, (object, object))'. Cast the factory result to the declared type in the lambda."); + } + } + + private static long __TUnitMocksConvertAsyncTupleItem_0(object? value) + { + switch (value) + { + case long exact: return exact; + case null when typeof(long).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(long)) is null: throw new global::System.InvalidCastException("The async factory produced a null tuple element, but the declared tuple element type '" + typeof(long) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(long)!; + case sbyte number: return number; + case byte number: return number; + case short number: return number; + case ushort number: return number; + case int number: return number; + case uint number: return number; + case char number: return number; + case nint number: return number; + default: throw new global::System.InvalidCastException("The async factory produced a tuple element of type '" + value.GetType() + "', which is not convertible to the declared tuple element type '" + typeof(long) + "'. Cast the factory result to the declared type in the lambda."); + } + } + + private static (object, object) __TUnitMocksConvertAsyncTupleItem_1(object? value) + { + switch (value) + { + case null: throw new global::System.InvalidCastException("The async factory produced a null tuple element, but the declared tuple element type '(object, object)' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case global::System.Runtime.CompilerServices.ITuple tuple when tuple.GetType().IsValueType && tuple.Length == 2: return (__TUnitMocksConvertAsyncTupleItem_1_0(tuple[0]), __TUnitMocksConvertAsyncTupleItem_1_1(tuple[1])); + default: throw new global::System.InvalidCastException("The async factory produced a tuple element of type '" + value.GetType() + "', which is not convertible to the declared tuple element type '(object, object)'. Cast the factory result to the declared type in the lambda."); + } + } + + private static object __TUnitMocksConvertAsyncTupleItem_1_0(object? value) + { + switch (value) + { + case object exact: return exact; + case null: return default(object)!; + default: throw new global::System.InvalidCastException("The async factory produced a tuple element of type '" + value.GetType() + "', which is not convertible to the declared tuple element type '" + typeof(object) + "'. Cast the factory result to the declared type in the lambda."); + } + } + + private static object __TUnitMocksConvertAsyncTupleItem_1_1(object? value) + { + switch (value) + { + case object exact: return exact; + case null: return default(object)!; + default: throw new global::System.InvalidCastException("The async factory produced a tuple element of type '" + value.GetType() + "', which is not convertible to the declared tuple element type '" + typeof(object) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public ITupleService_GetNestedAsync_M2_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class ITupleService_GetOptionalPairAsync_M3_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(int, string)?>? _builder; + + internal ITupleService_GetOptionalPairAsync_M3_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(int, string)?> EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder<(int, string)?> EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder<(int, string)?>(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public ITupleService_GetOptionalPairAsync_M3_MockCall Returns((int, string)? value) { EnsureSetup().Returns(value); return this; } + /// + public ITupleService_GetOptionalPairAsync_M3_MockCall Returns(global::System.Func<(int, string)?> factory) { EnsureSetup().Returns(factory); return this; } + /// + public ITupleService_GetOptionalPairAsync_M3_MockCall ReturnsSequentially(params (int, string)?[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public ITupleService_GetOptionalPairAsync_M3_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public ITupleService_GetOptionalPairAsync_M3_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public ITupleService_GetOptionalPairAsync_M3_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public ITupleService_GetOptionalPairAsync_M3_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public ITupleService_GetOptionalPairAsync_M3_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public ITupleService_GetOptionalPairAsync_M3_MockCall ReturnsAsync(global::System.Threading.Tasks.Task<(int, string)?> task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public ITupleService_GetOptionalPairAsync_M3_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public ITupleService_GetOptionalPairAsync_M3_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task<(int, string)?> __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task<(int, string)?> exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task<(int, string)?> __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case null: return default((int, string)?)!; + case global::System.Runtime.CompilerServices.ITuple tuple when tuple.GetType().IsValueType && tuple.Length == 2: return (__TUnitMocksConvertAsyncTupleItem_0(tuple[0]), __TUnitMocksConvertAsyncTupleItem_1(tuple[1])); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '(int, string)'. Cast the factory result to the declared type in the lambda."); + } + } + + private static int __TUnitMocksConvertAsyncTupleItem_0(object? value) + { + switch (value) + { + case int exact: return exact; + case null when typeof(int).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(int)) is null: throw new global::System.InvalidCastException("The async factory produced a null tuple element, but the declared tuple element type '" + typeof(int) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(int)!; + case sbyte number: return number; + case byte number: return number; + case short number: return number; + case ushort number: return number; + case char number: return number; + default: throw new global::System.InvalidCastException("The async factory produced a tuple element of type '" + value.GetType() + "', which is not convertible to the declared tuple element type '" + typeof(int) + "'. Cast the factory result to the declared type in the lambda."); + } + } + + private static string __TUnitMocksConvertAsyncTupleItem_1(object? value) + { + switch (value) + { + case string exact: return exact; + case null: return default(string)!; + default: throw new global::System.InvalidCastException("The async factory produced a tuple element of type '" + value.GetType() + "', which is not convertible to the declared tuple element type '" + typeof(string) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public ITupleService_GetOptionalPairAsync_M3_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks +{ + public static class ITupleService_MockStaticExtension + { + extension(global::ITupleService _) + { + public static global::ITupleServiceMock Mock() + { + return (global::ITupleServiceMock)global::ITupleServiceMockFactory.CreateAutoMock(global::TUnit.Mocks.Mock.DefaultBehavior); + } + + public static global::ITupleServiceMock Mock(global::TUnit.Mocks.MockBehavior behavior) + { + return (global::ITupleServiceMock)global::ITupleServiceMockFactory.CreateAutoMock(behavior); + } + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated; \ No newline at end of file diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Type_Parameters_Named_Like_Numeric_Types.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Type_Parameters_Named_Like_Numeric_Types.verified.txt new file mode 100644 index 0000000000..254274de22 --- /dev/null +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Type_Parameters_Named_Like_Numeric_Types.verified.txt @@ -0,0 +1,481 @@ +// +#pragma warning disable +#nullable enable + +public sealed class INumericNamed_Int32_Mock : global::TUnit.Mocks.Mock>, global::INumericNamed +{ + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal INumericNamed_Int32_Mock(global::INumericNamed mockObject, global::TUnit.Mocks.MockEngine> engine) + : base(mockObject, engine) { } + + global::System.Threading.Tasks.Task global::INumericNamed.GetAsync() => Object.GetAsync(); + + global::System.Threading.Tasks.Task global::INumericNamed.RoundtripAsync(Int64 value) => Object.RoundtripAsync(value); + + public INumericNamed_Int32__RoundtripAsync_M1_MockCall RoundtripAsync(global::TUnit.Mocks.Arguments.Arg value) + { + var mock = this; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { value.Matcher }; + return new INumericNamed_Int32__RoundtripAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "RoundtripAsync", matchers, global::TUnit.Mocks.TypeArguments.Of.Value); + } + + public INumericNamed_Int32__RoundtripAsync_M1_MockCall RoundtripAsync(global::System.Func value) + { + var mock = this; + global::TUnit.Mocks.Arguments.Arg __fa_value = value; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_value.Matcher }; + return new INumericNamed_Int32__RoundtripAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "RoundtripAsync", matchers, global::TUnit.Mocks.TypeArguments.Of.Value); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +file sealed class INumericNamed_Int32_MockImpl : global::INumericNamed, global::TUnit.Mocks.IRaisable, global::TUnit.Mocks.IMockObject +{ + private readonly global::TUnit.Mocks.MockEngine> _engine; + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + global::TUnit.Mocks.IMock? global::TUnit.Mocks.IMockObject.MockWrapper { get; set; } + + internal INumericNamed_Int32_MockImpl(global::TUnit.Mocks.MockEngine> engine) + { + _engine = engine; + } + + public global::System.Threading.Tasks.Task GetAsync() + { + try + { + var __result = _engine.HandleCallWithReturn(0, "GetAsync", global::System.Array.Empty(), default!); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + public global::System.Threading.Tasks.Task RoundtripAsync(Int64 value) + { + try + { + var __result = _engine.HandleCallWithReturn(1, "RoundtripAsync", new object?[] { value }, default!, global::TUnit.Mocks.TypeArguments.Of.Value); + if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) + { + if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; + throw new global::System.InvalidOperationException($"ReturnsAsync: expected global::System.Threading.Tasks.Task but got {__rawAsync?.GetType().Name ?? "null"}"); + } + return global::System.Threading.Tasks.Task.FromResult(__result); + } + catch (global::System.Exception __ex) + { + return global::System.Threading.Tasks.Task.FromException(__ex); + } + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public void RaiseEvent(string eventName, object? args) + { + throw new global::System.InvalidOperationException($"No event named '{eventName}' exists on this mock."); + } +} + +internal static class INumericNamed_Int32_MockFactory +{ + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void Register() + { + global::TUnit.Mocks.MockRegistry.RegisterOpenGenericFactory( + typeof(global::INumericNamed<>), + typeof(INumericNamed_Int32_MockImpl<>), + typeof(INumericNamed_Int32_Mock<>)); + } + + internal static global::TUnit.Mocks.Mock> CreateAutoMock(global::TUnit.Mocks.MockBehavior behavior) + { + var engine = new global::TUnit.Mocks.MockEngine>(behavior); + var impl = new INumericNamed_Int32_MockImpl(engine); + engine.Raisable = impl; + var mock = new INumericNamed_Int32_Mock(impl, engine); + return mock; + } + +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated +{ + public static class INumericNamed_Int32__MockMemberExtensions + { + public static INumericNamed_Int32__GetAsync_M0_MockCall GetAsync(this global::TUnit.Mocks.Mock> mock) + { + var matchers = global::System.Array.Empty(); + return new INumericNamed_Int32__GetAsync_M0_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 0, "GetAsync", matchers); + } + + extension(global::TUnit.Mocks.Mock> mock) + { + public INumericNamed_Int32__RoundtripAsync_M1_MockCall RoundtripAsync(global::TUnit.Mocks.Arguments.Arg value) + { + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { value.Matcher }; + return new INumericNamed_Int32__RoundtripAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "RoundtripAsync", matchers, global::TUnit.Mocks.TypeArguments.Of.Value); + } + + public INumericNamed_Int32__RoundtripAsync_M1_MockCall RoundtripAsync(global::System.Func value) + { + global::TUnit.Mocks.Arguments.Arg __fa_value = value; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_value.Matcher }; + return new INumericNamed_Int32__RoundtripAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "RoundtripAsync", matchers, global::TUnit.Mocks.TypeArguments.Of.Value); + } + } + extension(global::INumericNamed_Int32_Mock mock) + { + public INumericNamed_Int32__RoundtripAsync_M1_MockCall RoundtripAsync(global::TUnit.Mocks.Arguments.Arg value) + { + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { value.Matcher }; + return new INumericNamed_Int32__RoundtripAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "RoundtripAsync", matchers, global::TUnit.Mocks.TypeArguments.Of.Value); + } + + public INumericNamed_Int32__RoundtripAsync_M1_MockCall RoundtripAsync(global::System.Func value) + { + global::TUnit.Mocks.Arguments.Arg __fa_value = value; + var matchers = new global::TUnit.Mocks.Arguments.IArgumentMatcher[] { __fa_value.Matcher }; + return new INumericNamed_Int32__RoundtripAsync_M1_MockCall(global::TUnit.Mocks.MockRegistry.GetEngine(mock), 1, "RoundtripAsync", matchers, global::TUnit.Mocks.TypeArguments.Of.Value); + } + } + + #if NET9_0_OR_GREATER + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void Reset(this global::TUnit.Mocks.Mock> mock) + => global::TUnit.Mocks.Mock.Reset(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyAll(this global::TUnit.Mocks.Mock> mock) + => global::TUnit.Mocks.Mock.VerifyAll(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void VerifyNoOtherCalls(this global::TUnit.Mocks.Mock> mock) + => global::TUnit.Mocks.Mock.VerifyNoOtherCalls(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetupAllProperties(this global::TUnit.Mocks.Mock> mock) + => global::TUnit.Mocks.Mock.SetupAllProperties(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static global::TUnit.Mocks.Diagnostics.MockDiagnostics GetDiagnostics(this global::TUnit.Mocks.Mock> mock) + => global::TUnit.Mocks.Mock.GetDiagnostics(mock); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void SetState(this global::TUnit.Mocks.Mock> mock, string? stateName) + => global::TUnit.Mocks.Mock.SetState(mock, stateName); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public static void InState(this global::TUnit.Mocks.Mock> mock, string stateName, global::System.Action>> configure) + => global::TUnit.Mocks.Mock.InState(mock, stateName, configure); + + extension(global::TUnit.Mocks.Mock> mock) + { + public global::System.Collections.Generic.IReadOnlyList Invocations => global::TUnit.Mocks.Mock.Invocations(mock); + + public global::TUnit.Mocks.MockBehavior Behavior => global::TUnit.Mocks.Mock.Behavior(mock); + + public global::TUnit.Mocks.IDefaultValueProvider? DefaultValueProvider + { + get => global::TUnit.Mocks.Mock.GetDefaultValueProvider(mock); + set => global::TUnit.Mocks.Mock.SetDefaultValueProvider(mock, value); + } + } + #endif + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class INumericNamed_Int32__GetAsync_M0_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal INumericNamed_Int32__GetAsync_M0_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public INumericNamed_Int32__GetAsync_M0_MockCall Returns(Int32 value) { EnsureSetup().Returns(value); return this; } + /// + public INumericNamed_Int32__GetAsync_M0_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public INumericNamed_Int32__GetAsync_M0_MockCall ReturnsSequentially(params Int32[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public INumericNamed_Int32__GetAsync_M0_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public INumericNamed_Int32__GetAsync_M0_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public INumericNamed_Int32__GetAsync_M0_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public INumericNamed_Int32__GetAsync_M0_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public INumericNamed_Int32__GetAsync_M0_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public INumericNamed_Int32__GetAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public INumericNamed_Int32__GetAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public INumericNamed_Int32__GetAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case Int32 exact: return exact; + case null when typeof(Int32).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(Int32)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(Int32) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(Int32)!; + case global::System.IConvertible zero when typeof(Int32).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return (Int32)global::System.Enum.ToObject(typeof(Int32), 0); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(Int32) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public INumericNamed_Int32__GetAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + // ICallVerification + /// + public void WasCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(times, message); + /// + public void WasCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasCalled(message); + /// + public void WasNeverCalled() => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => _engine.CreateVerification(_memberId, _memberName, _matchers).WasNeverCalled(message); + } + + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public sealed class INumericNamed_Int32__RoundtripAsync_M1_MockCall : global::TUnit.Mocks.Verification.ICallVerification + { + private readonly global::TUnit.Mocks.IMockEngineAccess _engine; + private readonly int _memberId; + private readonly string _memberName; + private readonly global::TUnit.Mocks.Arguments.IArgumentMatcher[] _matchers; + private readonly global::System.Collections.Immutable.ImmutableArray _typeArguments; + private global::TUnit.Mocks.Setup.MethodSetupBuilder? _builder; + + internal INumericNamed_Int32__RoundtripAsync_M1_MockCall(global::TUnit.Mocks.IMockEngineAccess engine, int memberId, string memberName, global::TUnit.Mocks.Arguments.IArgumentMatcher[] matchers, global::System.Collections.Immutable.ImmutableArray typeArguments) + { + _engine = engine; + _memberId = memberId; + _memberName = memberName; + _matchers = matchers; + _typeArguments = typeArguments; + } + + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetup() + { + var existing = global::System.Threading.Volatile.Read(ref _builder); + if (existing is not null) return existing; + return EnsureSetupSlow(); + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private global::TUnit.Mocks.Setup.MethodSetupBuilder EnsureSetupSlow() + { + var setup = new global::TUnit.Mocks.Setup.MethodSetup(_memberId, _matchers, _memberName, _typeArguments); + var fresh = new global::TUnit.Mocks.Setup.MethodSetupBuilder(setup); + var prev = global::System.Threading.Interlocked.CompareExchange(ref _builder, fresh, null); + if (prev is not null) return prev; + // AddSetup runs only on the CAS winner. Setup is sequential in practice, + // so a concurrent loser observing the builder before registration is benign. + _engine.AddSetup(setup); + return fresh; + } + + /// + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Returns(Int64 value) { EnsureSetup().Returns(value); return this; } + /// + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Returns(global::System.Func factory) { EnsureSetup().Returns(factory); return this; } + /// + public INumericNamed_Int32__RoundtripAsync_M1_MockCall ReturnsSequentially(params Int64[] values) { EnsureSetup().ReturnsSequentially(values); return this; } + /// + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Throws() where TException : global::System.Exception, new() { EnsureSetup().Throws(); return this; } + /// + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Throws(global::System.Exception exception) { EnsureSetup().Throws(exception); return this; } + /// + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Callback(global::System.Action callback) { EnsureSetup().Callback(callback); return this; } + /// + public INumericNamed_Int32__RoundtripAsync_M1_MockCall TransitionsTo(string stateName) { EnsureSetup().TransitionsTo(stateName); return this; } + /// + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Then() { EnsureSetup().Then(); return this; } + + /// Return a pre-built Task directly (e.g., from a TaskCompletionSource). + public INumericNamed_Int32__RoundtripAsync_M1_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } + /// Return a pre-built Task from a factory, invoked on each call. + public INumericNamed_Int32__RoundtripAsync_M1_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case Int64 exact: return exact; + case null when typeof(Int64).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(Int64)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(Int64) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(Int64)!; + case global::System.IConvertible zero when typeof(Int64).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return (Int64)global::System.Enum.ToObject(typeof(Int64), 0); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(Int64) + "'. Cast the factory result to the declared type in the lambda."); + } + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + #endif + + /// Configure a typed computed return value using the actual method parameters. + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Returns(global::System.Func factory) + { + EnsureSetup().Returns(args => factory((Int64)args[0]!)); + return this; + } + + /// Configure a typed computed async return value using the actual method parameters. + public INumericNamed_Int32__RoundtripAsync_M1_MockCall ReturnsAsync(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => (object?)factory((Int64)args[0]!)); + return this; + } + + /// 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. + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => { var task = factory((Int64)args[0]!); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); + return this; + } + #if NET9_0_OR_GREATER + /// 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. + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Returns(global::System.Func> factory) + { + EnsureSetup().ReturnsRaw(args => (object?)factory((Int64)args[0]!)); + return this; + } + #endif + + /// Execute a typed callback using the actual method parameters. + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Callback(global::System.Action callback) + { + EnsureSetup().Callback(callback); + return this; + } + + /// Configure a typed computed exception using the actual method parameters. + public INumericNamed_Int32__RoundtripAsync_M1_MockCall Throws(global::System.Func exceptionFactory) + { + EnsureSetup().Throws(args => exceptionFactory((Int64)args[0]!)); + return this; + } + + // ICallVerification + /// + public void WasCalled() => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(); + /// + public void WasCalled(global::TUnit.Mocks.Times times) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(times); + /// + public void WasCalled(global::TUnit.Mocks.Times times, string? message) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(times, message); + /// + public void WasCalled(string? message) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasCalled(message); + /// + public void WasNeverCalled() => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasNeverCalled(); + /// + public void WasNeverCalled(string? message) => new global::TUnit.Mocks.MockMethodCall(_engine, _memberId, _memberName, _matchers, _typeArguments).WasNeverCalled(message); + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks +{ + public static class INumericNamed_Int32__MockStaticExtension + { + extension(global::INumericNamed _) + { + public static global::INumericNamed_Int32_Mock Mock() + { + return (global::INumericNamed_Int32_Mock)global::INumericNamed_Int32_MockFactory.CreateAutoMock(global::TUnit.Mocks.Mock.DefaultBehavior); + } + + public static global::INumericNamed_Int32_Mock Mock(global::TUnit.Mocks.MockBehavior behavior) + { + return (global::INumericNamed_Int32_Mock)global::INumericNamed_Int32_MockFactory.CreateAutoMock(behavior); + } + } + } +} + + +// ===== FILE SEPARATOR ===== + +// +#pragma warning disable +#nullable enable + +namespace TUnit.Mocks.Generated; \ No newline at end of file diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Unconstrained_Nullable_Generic.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Unconstrained_Nullable_Generic.verified.txt index ccc4225ff4..6622381a8e 100644 --- a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Unconstrained_Nullable_Generic.verified.txt +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Unconstrained_Nullable_Generic.verified.txt @@ -230,6 +230,24 @@ namespace TUnit.Mocks.Generated public IFoo_DoSomethingAsync_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public IFoo_DoSomethingAsync_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public IFoo_DoSomethingAsync_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case T exact: return exact; + case null when typeof(T).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(T)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(T) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(T?)!; + case global::System.IConvertible zero when typeof(T).IsEnum && zero is not global::System.Enum && zero.GetTypeCode() >= global::System.TypeCode.Char && zero.GetTypeCode() <= global::System.TypeCode.UInt64 && zero.ToDecimal(null) == 0m: return (T)global::System.Enum.ToObject(typeof(T), 0); + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(T) + "'. Cast the factory result to the declared type in the lambda."); + } + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Method_With_More_Params_Than_Func_Action_Arity.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Method_With_More_Params_Than_Func_Action_Arity.verified.txt index c4e58cb706..96911be75a 100644 --- a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Method_With_More_Params_Than_Func_Action_Arity.verified.txt +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Method_With_More_Params_Than_Func_Action_Arity.verified.txt @@ -206,11 +206,8 @@ namespace TUnit.Mocks.Generated public ILongMethodSignatures_SomeMethod_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public ILongMethodSignatures_SomeMethod_M0_MockCall ReturnsAsync(global::System.Func taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } - #if NET9_0_OR_GREATER /// 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. - [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] public ILongMethodSignatures_SomeMethod_M0_MockCall Returns(global::System.Func taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } - #endif // ICallVerification /// diff --git a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Returning_Async_Method_With_More_Params_Than_Func_Action_Arity.verified.txt b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Returning_Async_Method_With_More_Params_Than_Func_Action_Arity.verified.txt index 16f8f4fdd7..0bcc9c467f 100644 --- a/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Returning_Async_Method_With_More_Params_Than_Func_Action_Arity.verified.txt +++ b/tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Returning_Async_Method_With_More_Params_Than_Func_Action_Arity.verified.txt @@ -209,6 +209,28 @@ namespace TUnit.Mocks.Generated public ILongReturningSignature_Sum_M0_MockCall ReturnsAsync(global::System.Threading.Tasks.Task task) { EnsureSetup().ReturnsRaw(task); return this; } /// Return a pre-built Task from a factory, invoked on each call. public ILongReturningSignature_Sum_M0_MockCall ReturnsAsync(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => (object?)taskFactory()); return this; } + /// 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. + public ILongReturningSignature_Sum_M0_MockCall Returns(global::System.Func> taskFactory) { EnsureSetup().ReturnsRaw(() => { var task = taskFactory(); return task is null ? null : (object?)__TUnitMocksConvertAsyncResult(task); }); return this; } + + private static global::System.Threading.Tasks.Task __TUnitMocksConvertAsyncResult(global::System.Threading.Tasks.Task task) + => task is global::System.Threading.Tasks.Task exact ? exact : __TUnitMocksAwaitAndConvert(task); + + private static async global::System.Threading.Tasks.Task __TUnitMocksAwaitAndConvert(global::System.Threading.Tasks.Task task) + { + object? value = await task.ConfigureAwait(false); + switch (value) + { + case int exact: return exact; + case null when typeof(int).IsValueType && global::System.Nullable.GetUnderlyingType(typeof(int)) is null: throw new global::System.InvalidCastException("The async factory (result type '" + typeof(TAsyncFactoryResult) + "') produced a null result, but the member's declared result type '" + typeof(int) + "' is a non-nullable value type. Return a non-null value of the declared type from the factory."); + case null: return default(int)!; + case sbyte number: return number; + case byte number: return number; + case short number: return number; + case ushort number: return number; + case char number: return number; + default: throw new global::System.InvalidCastException("The async factory produced a result of type '" + value.GetType() + "', which is not convertible to the member's declared result type '" + typeof(int) + "'. Cast the factory result to the declared type in the lambda."); + } + } #if NET9_0_OR_GREATER /// 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. [global::System.Runtime.CompilerServices.OverloadResolutionPriority(-1)] diff --git a/tests/TUnit.Mocks.Tests/Issue6495Tests.cs b/tests/TUnit.Mocks.Tests/Issue6495Tests.cs index eb3bc4c51f..5032b02803 100644 --- a/tests/TUnit.Mocks.Tests/Issue6495Tests.cs +++ b/tests/TUnit.Mocks.Tests/Issue6495Tests.cs @@ -32,10 +32,9 @@ public class Issue6495Tests { private const int NeverInTestTimeMs = 30_000; -#if NET9_0_OR_GREATER - // The async-factory Returns overload carries [OverloadResolutionPriority], which only reaches - // a consumer's compilation on net9.0+ — so the overload, and these tests, are net9.0+ only. - // net8.0 consumers keep ReturnsAsync, covered by ReturnsAsync_Factory_Is_Unchanged below. + // The async-factory Returns alias is emitted on every target framework (#6515): ORP-based on + // net9.0+ as before, generic (inference-gated) below that — so these tests run on all of + // them, net472 and net8.0 included. [Test] public async Task Returns_Async_Lambda_Stays_Pending_So_A_Timeout_Can_Win() @@ -120,8 +119,6 @@ public async Task Returns_Async_Lambda_On_Task_Returning_Member_Stays_Pending() await Assert.That(call.IsCompleted).IsFalse(); } -#endif - [Test] public async Task Synchronous_Returns_Factory_Still_Works() { @@ -135,8 +132,9 @@ public async Task Synchronous_Returns_Factory_Still_Works() public async Task Null_Returning_Lambda_Still_Binds_To_The_Synchronous_Factory() { // `() => null` converts to both Func and Func>. The async overload - // is deprioritised, so this keeps its pre-existing meaning: null is the *value*, and the - // member still returns a completed task. + // is deprioritised (net9.0+) or excluded by failed type inference (below), so this keeps + // its pre-existing meaning: null is the *value*, and the member still returns a completed + // task. var mock = IReferenceResultService.Mock(); mock.GetNameAsync().Returns(() => null); @@ -165,7 +163,6 @@ public async Task Null_Returning_Typed_Lambda_Still_Binds_To_The_Synchronous_Fac await Assert.That(await mock.Object.GetNameAsync(1)).IsNull(); } -#if NET9_0_OR_GREATER [Test] public async Task Async_Lambda_Still_Binds_On_A_Reference_Typed_Result() { @@ -181,8 +178,6 @@ public async Task Async_Lambda_Still_Binds_On_A_Reference_Typed_Result() await Assert.That(call.IsCompleted).IsFalse(); } -#endif - [Test] public async Task ReturnsAsync_Factory_Is_Unchanged() { diff --git a/tests/TUnit.Mocks.Tests/Issue6515Tests.cs b/tests/TUnit.Mocks.Tests/Issue6515Tests.cs new file mode 100644 index 0000000000..1b172a4d66 --- /dev/null +++ b/tests/TUnit.Mocks.Tests/Issue6515Tests.cs @@ -0,0 +1,990 @@ +using TUnit.Mocks; + +namespace TUnit.Mocks.Tests; + +// Regression: https://github.com/thomhurst/TUnit/issues/6515 +// The async-factory Returns alias added for #6495 carried [OverloadResolutionPriority] and was +// therefore gated to net9.0+ consumers, so on net8.0 (and net472) `.Returns(async () => ...)` +// did not exist and a mocked async call could never be handed back still-pending. Below net9.0 +// the alias is now generic — Returns(Func>) — so +// typeless lambdas (null/throw) fail type inference and keep binding the synchronous factory, +// while genuine async lambdas bind the alias. This file runs on every target framework the test +// project builds for. + +#region Test types + +public interface ITimeoutClient +{ + Task GetValueAsync(CancellationToken ct); +} + +// Outer-nullable task member (#6518 review finding): the trailing '?' must not demote the alias +// to the ungated bare-task shape, or Returns(() => null) becomes CS0121. +public interface IOuterNullableTaskService +{ + Task? GetNameAsync(); +} + +// Polymorphic results (#6518 review finding): below net9.0 the generic alias infers the async +// lambda's own result type — a SUBTYPE of the declared result here — and Task/ValueTask +// are invariant, so the stored task must be converted to the declared task type. +public interface IPolymorphicResultService +{ + Task GetAsync(); + + ValueTask GetValueAsync(); + + Task GetByIdAsync(int id); +} + +// Numeric widening (#6518 review finding): the generic alias infers the async lambda's own +// numeric type (`async () => 1` infers int on a Task member); the conversion helper must +// replay the implicit widening rather than fail unboxing. +public interface INumericAsyncService +{ + Task GetCountAsync(); + + ValueTask GetRatioAsync(); + + Task AddAsync(int x); + + Task GetSmallAsync(); + + Task GetPriceAsync(); + + Task GetCountOrNullAsync(); +} + +// Implicit constant expression conversions (#6518 Codex round 5): `async () => 1` on a +// Task member compiles against the declared delegate (in-range int constant → byte), but +// the generic alias infers int and the widening table has no int → byte entry. The helper +// replays these as range-guarded exact-value narrowings — constant-ness is erased at runtime. +public interface IConstantNumericAsyncService +{ + Task GetByteAsync(); + + ValueTask GetShortAsync(); + + Task GetUnsignedAsync(); +} + +// Native integers (#6518 Codex round 7): nint → long (and nuint → ulong) are ordinary +// implicit numeric conversions, as are the small integral types → nint and the non-negative +// int constant → nuint — the conversion tables must include native integers on both sides. +public interface INativeIntAsyncService +{ + Task GetLongAsync(); + + Task GetNativeAsync(); + + ValueTask GetUnsignedNativeAsync(); +} + +// Enum results (#6518 Codex round 6): `async () => 0` on a Task member compiles via +// C#'s implicit constant-zero-to-enum conversion, but the generic alias infers int and the +// numeric tables have no enum destinations. The helper replays it value-guarded — integral +// sources, exactly zero, enum destinations only. +public interface IEnumAsyncService +{ + Task GetColorAsync(); + + ValueTask GetColorValueAsync(); + + Task GetColorOrNullAsync(); +} + +public enum AsyncColor +{ + None = 0, + Red = 1, +} + +// Defaultable T? (#6518 Codex round 6): on an unconstrained type parameter the trailing '?' is +// the defaultable annotation, not Nullable — Task with T = int is Task, so a null +// factory result must surface as the informative InvalidCastException, not silently become 0. +public interface IDefaultableGenericAsyncService +{ + Task FindAsync(int id); +} + +// dynamic result (#6518 Codex round 2): `dynamic` is illegal as a pattern type (CS8208) and as +// a typeof operand (CS1962), so the conversion helper must spell it `object` — merely mocking +// this interface failing to COMPILE is the regression. +public interface IDynamicAsyncService +{ + Task GetAsync(); + + ValueTask GetValueAsync(); +} + +// User-named type parameter (#6518 review finding): the alias's own type parameter must be +// uniquified, or it shadows this one (CS0693) and rebinds the conversion helper's result type. +public interface IUserNamedTypeParam +{ + Task RoundtripAsync(TAsyncFactoryResult value); +} + +// Null-task pass-through (#6518 review finding): when the generic alias binds (the factory's +// task type differs from the declared one, so the synchronous factory is inapplicable) and the +// factory produces a null task at runtime, the null must reach the raw-return check instead of +// faulting inside the conversion helper. +public interface IOuterNullableObjectService +{ + Task? GetAsync(); +} + +// Value tuples (#6518 Codex round 8): `async () => ("a", "b")` on a Task<(object, object)> +// member compiles against the declared delegate via C#'s element-wise implicit tuple +// conversion, but the generic alias infers (string, string) — the conversion helper must +// replay the conversion element by element (recursively for nested tuples). Element names +// must never reach the helper's typeof/patterns — they are not permitted there. +public interface ITupleAsyncService +{ + Task<(object, object)> GetPairAsync(); + + ValueTask<(int Id, string Name)> GetNamedAsync(); + + Task<(long, object)> GetWideningPairAsync(); + + Task<(int, string)> GetStrictPairAsync(); + + Task<(object, (object, object))> GetNestedAsync(); + + Task<(int, string)?> GetOptionalPairAsync(); +} + +// IConvertible result (#6518 review round 9): the zero-to-enum case's own pattern is +// `case IConvertible` — on a member declared Task it would land right after +// `case IConvertible exact` and be unreachable, and CS8120 is an error. Merely mocking this +// interface failing to COMPILE is the regression. +public interface IConvertibleAsyncService +{ + Task GetAsync(); + + ValueTask GetValueAsync(); +} + +// Type parameters named like BCL numeric types (#6518 review round 9): the conversion tables +// match type names textually, so a parameter literally named Int32 was mistaken for +// System.Int32 and numeric cases were emitted into a helper returning the open parameter +// (CS0029). Merely mocking these shapes failing to COMPILE is the regression. +public interface INumericNamedTypeParam +{ + Task GetAsync(); + + Task RoundtripAsync(Int64 value); +} + +public class TimeoutConsumer +{ + public async Task GetWithTimeoutAsync(ITimeoutClient client, TimeSpan timeout) + { + using var cts = new CancellationTokenSource(timeout); + var task = client.GetValueAsync(cts.Token); + var completed = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, cts.Token)); + if (completed != task) + { + throw new TimeoutException(); + } + return await task; + } +} + +#endregion + +public class Issue6515Tests +{ + [Test] + public async Task Timeout_Genuinely_Races_A_Pending_Mocked_Call_And_Wins() + { + var client = ITimeoutClient.Mock(); + client.GetValueAsync(Arg.Any()).Returns(async _ => + { + await Task.Delay(30_000); + return 42; + }); + + var consumer = new TimeoutConsumer(); + + await Assert.That(async () => await consumer.GetWithTimeoutAsync(client.Object, TimeSpan.FromMilliseconds(50))) + .Throws(); + } + + [Test] + public async Task Fast_Async_Factory_Completes_Before_The_Timeout() + { + var client = ITimeoutClient.Mock(); + client.GetValueAsync(Arg.Any()).Returns(async _ => + { + await Task.Yield(); + return 42; + }); + + var consumer = new TimeoutConsumer(); + + await Assert.That(await consumer.GetWithTimeoutAsync(client.Object, TimeSpan.FromSeconds(30))).IsEqualTo(42); + } + + [Test] + public async Task Outer_Nullable_Task_Member_Null_Lambda_Still_Binds_The_Synchronous_Factory() + { + var mock = IOuterNullableTaskService.Mock(); + mock.GetNameAsync().Returns(() => null); + + var call = mock.Object.GetNameAsync(); + + await Assert.That(call!.IsCompleted).IsTrue(); + await Assert.That(await call).IsNull(); + } + + [Test] + public async Task Outer_Nullable_Task_Member_Async_Lambda_Stays_Pending() + { + var mock = IOuterNullableTaskService.Mock(); + mock.GetNameAsync().Returns(async () => + { + await Task.Delay(30_000); + return "late"; + }); + + var call = mock.Object.GetNameAsync(); + + await Assert.That(call!.IsCompleted).IsFalse(); + } + + [Test] + public async Task Outer_Nullable_Member_Configured_With_A_Null_Task_Returns_Null() + { + var mock = IOuterNullableTaskService.Mock(); + mock.GetNameAsync().ReturnsAsync((Task?)null); + + // Boxed so the assertion targets the task reference itself, not its awaited result. + await Assert.That((object?)mock.Object.GetNameAsync()).IsNull(); + } + + [Test] + public async Task Async_Lambda_Returning_A_Subtype_Produces_The_Value() + { + // `async () => "value"` infers Task for a Task member below net9.0 — + // the setup must still serve the declared Task. + var mock = IPolymorphicResultService.Mock(); + mock.GetAsync().Returns(async () => + { + await Task.Yield(); + return "value"; + }); + + await Assert.That(await mock.Object.GetAsync()).IsEqualTo("value"); + } + + [Test] + public async Task Async_Lambda_Returning_A_Subtype_Stays_Pending() + { + var mock = IPolymorphicResultService.Mock(); + mock.GetAsync().Returns(async () => + { + await Task.Delay(30_000); + return "late"; + }); + + await Assert.That(mock.Object.GetAsync().IsCompleted).IsFalse(); + } + + [Test] + public async Task Async_Lambda_Returning_A_Subtype_On_ValueTask_Member() + { + var mock = IPolymorphicResultService.Mock(); + mock.GetValueAsync().Returns(async () => + { + await Task.Yield(); + return "vt-value"; + }); + + await Assert.That(await mock.Object.GetValueAsync()).IsEqualTo("vt-value"); + } + + [Test] + public async Task Async_Lambda_Returning_A_Subtype_With_Typed_Parameters() + { + var mock = IPolymorphicResultService.Mock(); + mock.GetByIdAsync(Arg.Any()).Returns(async id => + { + await Task.Yield(); + return $"id-{id}"; + }); + + await Assert.That(await mock.Object.GetByIdAsync(7)).IsEqualTo("id-7"); + } + +#if NET9_0_OR_GREATER + [Test] + public async Task Async_Lambda_With_Null_Body_Binds_The_NonGeneric_Alias() + { + // `async () => null` pins no type: it has no natural type, cannot infer the generic + // alias's type parameter, and is not convertible to Func — the ORP(-1) non-generic + // alias is the sole applicable candidate. That alias only exists on net9.0+. + var mock = IOuterNullableTaskService.Mock(); + mock.GetNameAsync().Returns(async () => null); + + var call = mock.Object.GetNameAsync(); + + await Assert.That(call!.IsCompleted).IsTrue(); + await Assert.That(await call).IsNull(); + } +#endif + + [Test] + public async Task Async_Lambda_With_Narrower_Numeric_Type_Widens_To_The_Declared_Result() + { + // `async () => 1` infers Task on a Task member; a boxed int cannot be + // unboxed as long, so the conversion helper must widen explicitly. + var mock = INumericAsyncService.Mock(); + mock.GetCountAsync().Returns(async () => + { + await Task.Yield(); + return 1; + }); + + await Assert.That(await mock.Object.GetCountAsync()).IsEqualTo(1L); + } + + [Test] + public async Task Async_Lambda_Numeric_Widening_On_ValueTask_Member() + { + var mock = INumericAsyncService.Mock(); + mock.GetRatioAsync().Returns(async () => + { + await Task.Yield(); + return 2; + }); + + await Assert.That(await mock.Object.GetRatioAsync()).IsEqualTo(2d); + } + + [Test] + public async Task Async_Lambda_Numeric_Widening_With_Typed_Parameters() + { + var mock = INumericAsyncService.Mock(); + mock.AddAsync(Arg.Any()).Returns(async x => + { + await Task.Yield(); + return x + 1; + }); + + await Assert.That(await mock.Object.AddAsync(2)).IsEqualTo(3L); + } + + [Test] + public async Task Wrong_Typed_Async_Factory_Surfaces_An_Informative_InvalidCast() + { + // A string is IConvertible, but C# has no string-to-long conversion — the helper must + // not silently parse it, and the failure must name both types. + var mock = INumericAsyncService.Mock(); + mock.GetCountAsync().Returns(async () => + { + await Task.Yield(); + return "nope"; + }); + + await Assert.That(async () => await mock.Object.GetCountAsync()) + .Throws(); + } + + [Test] + public async Task Async_Lambda_With_In_Range_Constant_Int_On_Byte_Member_Converts() + { + // `async () => 1` compiles against Func> only because 1 is an in-range int + // constant; the alias infers int, so the helper must replay the constant conversion. + var mock = IConstantNumericAsyncService.Mock(); + mock.GetByteAsync().Returns(async () => + { + await Task.Yield(); + return 1; + }); + + await Assert.That(await mock.Object.GetByteAsync()).IsEqualTo((byte)1); + } + + [Test] + public async Task Async_Lambda_Constant_Conversion_On_ValueTask_Short_Member() + { + var mock = IConstantNumericAsyncService.Mock(); + mock.GetShortAsync().Returns(async () => + { + await Task.Yield(); + return -5; + }); + + await Assert.That(await mock.Object.GetShortAsync()).IsEqualTo((short)-5); + } + + [Test] + public async Task Async_Lambda_Non_Negative_Int_On_Ulong_Member_Converts() + { + var mock = IConstantNumericAsyncService.Mock(); + mock.GetUnsignedAsync().Returns(async () => + { + await Task.Yield(); + return 7; + }); + + await Assert.That(await mock.Object.GetUnsignedAsync()).IsEqualTo(7UL); + } + + [Test] + public async Task Async_Lambda_Returning_Nint_On_Long_Member_Converts() + { + // nint → long is an ordinary implicit numeric conversion; the alias infers nint and + // the widening table must include the native-integer source. + var mock = INativeIntAsyncService.Mock(); + mock.GetLongAsync().Returns(async () => + { + await Task.Yield(); + return (nint)7; + }); + + await Assert.That(await mock.Object.GetLongAsync()).IsEqualTo(7L); + } + + [Test] + public async Task Async_Lambda_Returning_Int_On_Nint_Member_Converts() + { + // int → nint is an ordinary implicit numeric conversion (native int is at least 32 + // bits); the boxed int must convert via the native-integer destination entry. + var mock = INativeIntAsyncService.Mock(); + mock.GetNativeAsync().Returns(async () => + { + await Task.Yield(); + return 7; + }); + + await Assert.That(await mock.Object.GetNativeAsync()).IsEqualTo((nint)7); + } + + [Test] + public async Task Async_Lambda_Non_Negative_Int_On_Nuint_Member_Converts() + { + // A non-negative int CONSTANT converts implicitly to nuint; replayed value-guarded. + var mock = INativeIntAsyncService.Mock(); + mock.GetUnsignedNativeAsync().Returns(async () => + { + await Task.Yield(); + return 7; + }); + + await Assert.That(await mock.Object.GetUnsignedNativeAsync()).IsEqualTo((nuint)7); + } + + [Test] + public async Task Negative_Int_On_Nuint_Member_Surfaces_An_Informative_InvalidCast() + { + var mock = INativeIntAsyncService.Mock(); + mock.GetUnsignedNativeAsync().Returns(async () => + { + await Task.Yield(); + return -1; + }); + + await Assert.That(async () => await mock.Object.GetUnsignedNativeAsync()) + .Throws(); + } + + [Test] + public async Task Out_Of_Range_Int_On_Byte_Member_Surfaces_An_Informative_InvalidCast() + { + // 300 is not representable as byte — C# would reject the constant conversion too, so + // the helper must not silently truncate; it takes the informative failure path. + var mock = IConstantNumericAsyncService.Mock(); + mock.GetByteAsync().Returns(async () => + { + await Task.Yield(); + return 300; + }); + + await Assert.That(async () => await mock.Object.GetByteAsync()) + .Throws(); + } + + [Test] + public async Task Negative_Int_On_Ulong_Member_Surfaces_An_Informative_InvalidCast() + { + var mock = IConstantNumericAsyncService.Mock(); + mock.GetUnsignedAsync().Returns(async () => + { + await Task.Yield(); + return -1; + }); + + await Assert.That(async () => await mock.Object.GetUnsignedAsync()) + .Throws(); + } + + [Test] + public async Task Zero_On_Enum_Member_Converts() + { + // `async () => 0` compiles against the declared delegate only via the implicit + // constant-zero-to-enum conversion; the alias infers int and the helper must replay it. + var mock = IEnumAsyncService.Mock(); + mock.GetColorAsync().Returns(async () => + { + await Task.Yield(); + return 0; + }); + + await Assert.That(await mock.Object.GetColorAsync()).IsEqualTo(AsyncColor.None); + } + + [Test] + public async Task Zero_On_ValueTask_Enum_Member_Converts() + { + var mock = IEnumAsyncService.Mock(); + mock.GetColorValueAsync().Returns(async () => + { + await Task.Yield(); + return 0; + }); + + await Assert.That(await mock.Object.GetColorValueAsync()).IsEqualTo(AsyncColor.None); + } + + [Test] + public async Task Zero_On_Nullable_Enum_Member_Converts() + { + var mock = IEnumAsyncService.Mock(); + mock.GetColorOrNullAsync().Returns(async () => + { + await Task.Yield(); + return 0; + }); + + await Assert.That(await mock.Object.GetColorOrNullAsync()).IsEqualTo(AsyncColor.None); + } + + [Test] + public async Task Non_Zero_Int_On_Enum_Member_Surfaces_An_Informative_InvalidCast() + { + // C# only converts the CONSTANT ZERO to an enum implicitly — a non-zero int would not + // compile against the declared delegate either, so the helper must not silently cast. + var mock = IEnumAsyncService.Mock(); + mock.GetColorAsync().Returns(async () => + { + await Task.Yield(); + return 1; + }); + + await Assert.That(async () => await mock.Object.GetColorAsync()) + .Throws(); + } + + [Test] + public async Task Null_On_Defaultable_Generic_Value_Instantiation_Throws() + { + // Task with unconstrained T = int is Task — '?' is the defaultable annotation, + // not Nullable. A null factory result silently becoming 0 is the regression. + var mock = IDefaultableGenericAsyncService.Mock(); + mock.FindAsync(Arg.Any()).Returns(async _ => + { + await Task.Yield(); + return (int?)null; + }); + + await Assert.That(async () => await mock.Object.FindAsync(1)) + .Throws(); + } + + [Test] + public async Task Value_On_Defaultable_Generic_Value_Instantiation_Round_Trips() + { + var mock = IDefaultableGenericAsyncService.Mock(); + mock.FindAsync(Arg.Any()).Returns(async _ => + { + await Task.Yield(); + return 5; + }); + + await Assert.That(await mock.Object.FindAsync(1)).IsEqualTo(5); + } + + [Test] + public async Task Null_On_Defaultable_Generic_Reference_Instantiation_Round_Trips() + { + // For T = string the same runtime guard must NOT fire — null is a valid T? value. + var mock = IDefaultableGenericAsyncService.Mock(); + mock.FindAsync(Arg.Any()).Returns(async _ => + { + await Task.Yield(); + return (string?)null; + }); + + await Assert.That(await mock.Object.FindAsync(1)).IsNull(); + } + + [Test] + public async Task Generic_Method_With_User_Named_TAsyncFactoryResult_Still_Converts() + { + // The interface's own type parameter is literally named TAsyncFactoryResult; the alias + // must not shadow it (this test failing to COMPILE is the regression). + var mock = IUserNamedTypeParam.Mock(); + mock.RoundtripAsync(Arg.Any()).Returns(async v => + { + await Task.Yield(); + return v + "!"; + }); + + await Assert.That(await mock.Object.RoundtripAsync("a")).IsEqualTo("a!"); + } + + [Test] + public async Task Null_Task_From_A_Generic_Alias_Factory_Passes_Through_On_Outer_Nullable_Member() + { + // Task? does not convert to the declared Task?, so the synchronous + // factory is inapplicable and the generic alias binds with the factory's own task type. + // Its null result must round-trip as the member's (contractually valid) null task. + Func?> inner = () => null; + + var mock = IOuterNullableObjectService.Mock(); + // The '!' silences the annotation-level mismatch only; the runtime value is still null. + mock.GetAsync().Returns(() => inner()!); + + // Boxed so the assertion targets the task reference itself, not its awaited result. + await Assert.That((object?)mock.Object.GetAsync()).IsNull(); + } + + [Test] + [SkipIfNotDynamicCodeSupported("Consuming an awaited `dynamic` as a typed local makes the C# runtime binder build an expression tree, which NREs under Native AOT.")] + public async Task Task_Of_Dynamic_Member_Converts_The_Factory_Result() + { + // `async () => "dyn"` infers Task, which is invariant-incompatible with the + // declared Task, so the generic alias binds and the conversion helper runs. + var mock = IDynamicAsyncService.Mock(); + mock.GetAsync().Returns(async () => + { + await Task.Yield(); + return "dyn"; + }); + + string result = await mock.Object.GetAsync(); + + await Assert.That(result).IsEqualTo("dyn"); + } + + [Test] + [SkipIfNotDynamicCodeSupported("Consuming an awaited `dynamic` as a typed local makes the C# runtime binder build an expression tree, which NREs under Native AOT.")] + public async Task ValueTask_Of_Dynamic_Member_Converts_The_Factory_Result() + { + var mock = IDynamicAsyncService.Mock(); + mock.GetValueAsync().Returns(async () => + { + await Task.Yield(); + return 7; + }); + + int result = await mock.Object.GetValueAsync(); + + await Assert.That(result).IsEqualTo(7); + } + + [Test] + public async Task Task_Of_Dynamic_Member_Accepts_A_Null_Factory_Result() + { + // dynamic accepts null — the non-nullable value-type null guard must not fire here. + var mock = IDynamicAsyncService.Mock(); + mock.GetAsync().Returns(async () => + { + await Task.Yield(); + return (string?)null; + }); + + object? result = await mock.Object.GetAsync(); + + await Assert.That(result).IsNull(); + } + + [Test] + public async Task Null_From_A_Factory_Inferring_Nullable_On_A_NonNullable_Value_Member_Throws() + { + // `async () => (long?)null` infers Task on the Task member; the null must + // not silently become default(long) — zero would be corrupted data. + var mock = INumericAsyncService.Mock(); + mock.GetCountAsync().Returns(async () => + { + await Task.Yield(); + return (long?)null; + }); + + await Assert.That(async () => await mock.Object.GetCountAsync()) + .Throws(); + } + + [Test] + public async Task Null_From_A_Factory_On_A_Nullable_Value_Member_Round_Trips() + { + // `async () => (int?)null` infers Task on the Task member — a different + // task type, so the conversion helper runs; null is valid for the nullable result. + var mock = INumericAsyncService.Mock(); + mock.GetCountOrNullAsync().Returns(async () => + { + await Task.Yield(); + return (int?)null; + }); + + await Assert.That(await mock.Object.GetCountOrNullAsync()).IsNull(); + } + + [Test] + public async Task Long_To_Int_Narrowing_Attempt_Throws_Instead_Of_Truncating() + { + // long → int is not an implicit C# conversion; the helper must not replay it. + var mock = INumericAsyncService.Mock(); + mock.GetSmallAsync().Returns(async () => + { + await Task.Yield(); + return 5L; + }); + + await Assert.That(async () => await mock.Object.GetSmallAsync()) + .Throws(); + } + + [Test] + public async Task Double_To_Int_Rounding_Attempt_Throws_Instead_Of_Rounding() + { + // Convert.ChangeType would round 1.9 to 2; C# has no double → int implicit conversion. + var mock = INumericAsyncService.Mock(); + mock.GetSmallAsync().Returns(async () => + { + await Task.Yield(); + return 1.9; + }); + + await Assert.That(async () => await mock.Object.GetSmallAsync()) + .Throws(); + } + + [Test] + public async Task Int_To_Decimal_Widening_Works() + { + // int → decimal IS an implicit numeric conversion and must keep working. + var mock = INumericAsyncService.Mock(); + mock.GetPriceAsync().Returns(async () => + { + await Task.Yield(); + return 42; + }); + + await Assert.That(await mock.Object.GetPriceAsync()).IsEqualTo(42m); + } + + [Test] + public async Task Double_To_Decimal_Is_Not_Implicit_And_Throws() + { + // double → decimal is only an EXPLICIT conversion in C#. + var mock = INumericAsyncService.Mock(); + mock.GetPriceAsync().Returns(async () => + { + await Task.Yield(); + return 1.5; + }); + + await Assert.That(async () => await mock.Object.GetPriceAsync()) + .Throws(); + } + + [Test] + public async Task Exactly_Typed_Factory_Task_Is_Handed_Back_As_Is() + { + // The conversion path must not wrap a factory task that already has the declared type — + // reference identity is part of the "returned as-is" contract. + var exact = Task.FromResult(42); + Func> factory = () => exact; + + var mock = ISlowService.Mock(); + mock.GetValueAsync().Returns(factory); + + // Boxed so the assertion targets the task reference itself, not its awaited result. + await Assert.That((object)mock.Object.GetValueAsync()).IsSameReferenceAs(exact); + } + + [Test] + public async Task Tuple_Member_Converts_The_Factory_Elements() + { + // `async () => ("a", "b")` infers (string, string) on the (object, object) member; the + // helper must replay the element-wise implicit tuple conversion. + var mock = ITupleAsyncService.Mock(); + mock.GetPairAsync().Returns(async () => + { + await Task.Yield(); + return ("a", "b"); + }); + + var (first, second) = await mock.Object.GetPairAsync(); + + await Assert.That(first).IsEqualTo("a"); + await Assert.That(second).IsEqualTo("b"); + } + + [Test] + public async Task Named_Tuple_Member_Is_Mockable_And_Round_Trips() + { + // Element names must not leak into the generated helper (typeof rejects them) — merely + // mocking this member is the compile-time half of the regression. + var mock = ITupleAsyncService.Mock(); + mock.GetNamedAsync().Returns(async () => + { + await Task.Yield(); + return (1, "n"); + }); + + var result = await mock.Object.GetNamedAsync(); + + await Assert.That(result.Id).IsEqualTo(1); + await Assert.That(result.Name).IsEqualTo("n"); + } + + [Test] + public async Task Tuple_Element_Numeric_Widening_Works() + { + // Element 0 infers int on a long element — the element converter must replay the + // implicit numeric widening just like a whole numeric result. + var mock = ITupleAsyncService.Mock(); + mock.GetWideningPairAsync().Returns(async () => + { + await Task.Yield(); + return (1, "x"); + }); + + var (count, tag) = await mock.Object.GetWideningPairAsync(); + + await Assert.That(count).IsEqualTo(1L); + await Assert.That(tag).IsEqualTo("x"); + } + + [Test] + public async Task Inconvertible_Tuple_Element_Throws_Instead_Of_Unboxing() + { + // (int, int) on the (int, string) member: element 1 has no implicit conversion. + var mock = ITupleAsyncService.Mock(); + mock.GetStrictPairAsync().Returns(async () => + { + await Task.Yield(); + return (1, 2); + }); + + await Assert.That(async () => await mock.Object.GetStrictPairAsync()) + .Throws(); + } + + [Test] + public async Task Null_Tuple_Element_On_A_NonNullable_Value_Element_Throws() + { + // ((int?)null, "x") infers (int?, string); the null element must not silently become + // default(int) on the (int, string) member. + var mock = ITupleAsyncService.Mock(); + mock.GetStrictPairAsync().Returns(async () => + { + await Task.Yield(); + return ((int?)null, "x"); + }); + + await Assert.That(async () => await mock.Object.GetStrictPairAsync()) + .Throws(); + } + + [Test] + public async Task Nested_Tuple_Elements_Convert_Recursively() + { + var mock = ITupleAsyncService.Mock(); + mock.GetNestedAsync().Returns(async () => + { + await Task.Yield(); + return ("a", ("b", "c")); + }); + + var (outer, inner) = await mock.Object.GetNestedAsync(); + + await Assert.That(outer).IsEqualTo("a"); + await Assert.That(inner.Item1).IsEqualTo("b"); + await Assert.That(inner.Item2).IsEqualTo("c"); + } + + [Test] + public async Task Nullable_Tuple_Member_Accepts_A_Converted_Pair_And_A_Null_Tuple() + { + // (int, string) infers the non-nullable tuple on the (int, string)? member — the + // ITuple case converts and the literal lifts to the nullable declared type. A null + // tuple factory infers (int, string)? and passes through as the exact task type. + var mock = ITupleAsyncService.Mock(); + mock.GetOptionalPairAsync().Returns(async () => + { + await Task.Yield(); + return (1, "a"); + }); + + var value = await mock.Object.GetOptionalPairAsync(); + await Assert.That(value.HasValue).IsTrue(); + await Assert.That(value!.Value.Item2).IsEqualTo("a"); + + mock.GetOptionalPairAsync().Returns(async () => + { + await Task.Yield(); + return ((int, string)?)null; + }); + + await Assert.That((await mock.Object.GetOptionalPairAsync()).HasValue).IsFalse(); + } + + [Test] + public async Task IConvertible_Member_Accepts_A_Factory_Inferring_A_Concrete_Type() + { + // Boxed int implements IConvertible, so the exact reference-conversion case handles it; + // the (dead-at-runtime) zero-to-enum case must not be emitted at all — it would be CS8120. + var mock = IConvertibleAsyncService.Mock(); + mock.GetAsync().Returns(async () => + { + await Task.Yield(); + return 42; + }); + + await Assert.That((int)await mock.Object.GetAsync()).IsEqualTo(42); + } + + [Test] + public async Task IConvertible_ValueTask_Member_Accepts_A_Factory_Inferring_A_Concrete_Type() + { + var mock = IConvertibleAsyncService.Mock(); + mock.GetValueAsync().Returns(async () => + { + await Task.Yield(); + return "converted"; + }); + + await Assert.That((string)await mock.Object.GetValueAsync()).IsEqualTo("converted"); + } + + [Test] + public async Task Type_Parameter_Named_Int32_Round_Trips_Its_Actual_Instantiation() + { + var mock = INumericNamedTypeParam.Mock(); + mock.GetAsync().Returns(async () => + { + await Task.Yield(); + return "not a number"; + }); + + await Assert.That(await mock.Object.GetAsync()).IsEqualTo("not a number"); + } + + [Test] + public async Task Method_Type_Parameter_Named_Int64_Round_Trips_Its_Actual_Instantiation() + { + var mock = INumericNamedTypeParam.Mock(); + mock.RoundtripAsync(Arg.Any()).Returns(async () => + { + await Task.Yield(); + return new Uri("https://example.test/"); + }); + + await Assert.That((await mock.Object.RoundtripAsync(new Uri("https://tunit.dev/"))).Host) + .IsEqualTo("example.test"); + } +}