Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ namespace TUnit.Mocks.Generated

public global::IConfigProvider GetConfigProvider()
{
return _engine.HandleCallWithReturn<global::IConfigProvider>(1, "GetConfigProvider", global::System.Array.Empty<object?>(), default!);
return (global::IConfigProvider)_engine.HandleCallWithReturn<object?>(1, "GetConfigProvider", global::System.Array.Empty<object?>(), null)!;
}

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
Expand Down Expand Up @@ -80,10 +80,10 @@ namespace TUnit.Mocks.Generated
return new IMyService_GetValue_M0_MockCall(global::TUnit.Mocks.Mock.GetEngine(mock), 0, "GetValue", matchers);
}

public static global::TUnit.Mocks.MockMethodCall<global::IConfigProvider> GetConfigProvider(this global::TUnit.Mocks.Mock<global::IMyService> mock)
public static global::TUnit.Mocks.VoidMockMethodCall GetConfigProvider(this global::TUnit.Mocks.Mock<global::IMyService> mock)
{
var matchers = global::System.Array.Empty<global::TUnit.Mocks.Arguments.IArgumentMatcher>();
return new global::TUnit.Mocks.MockMethodCall<global::IConfigProvider>(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "GetConfigProvider", matchers);
return new global::TUnit.Mocks.VoidMockMethodCall(global::TUnit.Mocks.Mock.GetEngine(mock), 1, "GetConfigProvider", matchers);
}
}

Expand Down
80 changes: 79 additions & 1 deletion TUnit.Mocks.SourceGenerator/Builders/MockBridgeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,14 @@ private static void GenerateStaticPropertyDim(CodeWriter writer, MockMemberModel
writer.OpenBrace();
writer.AppendLine($"var __engine = {safeName}_StaticEngine.Engine;");
writer.AppendLine("if (__engine is null) return default!;");
writer.AppendLine($"return __engine.HandleCallWithReturn<{prop.ReturnType}>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), {prop.SmartDefault});");
if (prop.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"return ({prop.ReturnType})__engine.HandleCallWithReturn<object?>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), null)!;");
}
else
{
writer.AppendLine($"return __engine.HandleCallWithReturn<{prop.ReturnType}>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), {prop.SmartDefault});");
}
writer.CloseBrace();
}

Expand Down Expand Up @@ -158,51 +165,122 @@ private static void GenerateStaticEngineDispatchBody(CodeWriter writer, MockMemb
else if (method.IsVoid && method.IsAsync)
{
if (method.IsValueTask)
{
writer.AppendLine("if (__engine is null) return default(global::System.Threading.Tasks.ValueTask);");
}
else
{
writer.AppendLine("if (__engine is null) return global::System.Threading.Tasks.Task.CompletedTask;");
}

using (writer.Block("try"))
{
writer.AppendLine($"__engine.HandleCall({method.MemberId}, \"{method.Name}\", {argsArray});");
MockImplBuilder.EmitOutRefReadback(writer, method);
if (method.IsValueTask)
{
writer.AppendLine("return default(global::System.Threading.Tasks.ValueTask);");
}
else
{
writer.AppendLine("return global::System.Threading.Tasks.Task.CompletedTask;");
}
}
using (writer.Block("catch (global::System.Exception __ex)"))
{
if (method.IsValueTask)
{
writer.AppendLine("return new global::System.Threading.Tasks.ValueTask(global::System.Threading.Tasks.Task.FromException(__ex));");
}
else
{
writer.AppendLine("return global::System.Threading.Tasks.Task.FromException(__ex);");
}
}
}
else if (method.IsAsync && method.IsReturnTypeStaticAbstractInterface)
{
// Async method whose unwrapped return type has static abstract members (CS8920).
// Use object? and cast instead.
if (method.IsValueTask)
{
writer.AppendLine($"if (__engine is null) return new global::System.Threading.Tasks.ValueTask<{method.UnwrappedReturnType}>(default!);");
}
else
{
writer.AppendLine($"if (__engine is null) return global::System.Threading.Tasks.Task.FromResult<{method.UnwrappedReturnType}>(default!);");
}

using (writer.Block("try"))
{
writer.AppendLine($"var __result = ({method.UnwrappedReturnType})__engine.HandleCallWithReturn<object?>({method.MemberId}, \"{method.Name}\", {argsArray}, null)!;");
MockImplBuilder.EmitOutRefReadback(writer, method);
if (method.IsValueTask)
{
writer.AppendLine($"return new global::System.Threading.Tasks.ValueTask<{method.UnwrappedReturnType}>(__result);");
}
else
{
writer.AppendLine($"return global::System.Threading.Tasks.Task.FromResult<{method.UnwrappedReturnType}>(__result);");
}
}
using (writer.Block("catch (global::System.Exception __ex)"))
{
if (method.IsValueTask)
{
writer.AppendLine($"return new global::System.Threading.Tasks.ValueTask<{method.UnwrappedReturnType}>(global::System.Threading.Tasks.Task.FromException<{method.UnwrappedReturnType}>(__ex));");
}
else
{
writer.AppendLine($"return global::System.Threading.Tasks.Task.FromException<{method.UnwrappedReturnType}>(__ex);");
}
}
}
else if (method.IsAsync)
{
if (method.IsValueTask)
{
writer.AppendLine($"if (__engine is null) return new global::System.Threading.Tasks.ValueTask<{method.UnwrappedReturnType}>({method.UnwrappedSmartDefault});");
}
else
{
writer.AppendLine($"if (__engine is null) return global::System.Threading.Tasks.Task.FromResult<{method.UnwrappedReturnType}>({method.UnwrappedSmartDefault});");
}

using (writer.Block("try"))
{
writer.AppendLine($"var __result = __engine.HandleCallWithReturn<{method.UnwrappedReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.UnwrappedSmartDefault});");
MockImplBuilder.EmitOutRefReadback(writer, method);
if (method.IsValueTask)
{
writer.AppendLine($"return new global::System.Threading.Tasks.ValueTask<{method.UnwrappedReturnType}>(__result);");
}
else
{
writer.AppendLine($"return global::System.Threading.Tasks.Task.FromResult<{method.UnwrappedReturnType}>(__result);");
}
}
using (writer.Block("catch (global::System.Exception __ex)"))
{
if (method.IsValueTask)
{
writer.AppendLine($"return new global::System.Threading.Tasks.ValueTask<{method.UnwrappedReturnType}>(global::System.Threading.Tasks.Task.FromException<{method.UnwrappedReturnType}>(__ex));");
}
else
{
writer.AppendLine($"return global::System.Threading.Tasks.Task.FromException<{method.UnwrappedReturnType}>(__ex);");
}
}
}
else if (method.IsReturnTypeStaticAbstractInterface)
{
// Return type has static abstract members — can't use as generic type argument (CS8920).
// Use object? and cast instead.
writer.AppendLine("if (__engine is null) return default!;");
writer.AppendLine($"var __result = __engine.HandleCallWithReturn<object?>({method.MemberId}, \"{method.Name}\", {argsArray}, null);");
MockImplBuilder.EmitOutRefReadback(writer, method);
writer.AppendLine($"return ({method.ReturnType})__result!;");
}
else
{
writer.AppendLine("if (__engine is null) return default!;");
Expand Down
113 changes: 110 additions & 3 deletions TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,19 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me
}
else if (method.IsAsync)
{
writer.AppendLine($"if (_engine.TryHandleCallWithReturn<{method.UnwrappedReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.UnwrappedSmartDefault}, out var __result))");
var tryArg = method.IsReturnTypeStaticAbstractInterface ? "object?" : method.UnwrappedReturnType;
var tryDefault = method.IsReturnTypeStaticAbstractInterface ? "null" : method.UnwrappedSmartDefault;
writer.AppendLine($"if (_engine.TryHandleCallWithReturn<{tryArg}>({method.MemberId}, \"{method.Name}\", {argsArray}, {tryDefault}, out var __rawResult))");
writer.AppendLine("{");
writer.IncreaseIndent();
if (method.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"var __result = ({method.UnwrappedReturnType})__rawResult!;");
}
else
{
writer.AppendLine($"var __result = __rawResult;");
}
EmitOutRefReadback(writer, method);
if (method.IsValueTask)
{
Expand Down Expand Up @@ -281,6 +291,18 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me
writer.AppendLine("}");
writer.AppendLine($"return _wrappedInstance.{method.Name}({argPassList});");
}
else if (method.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"if (_engine.TryHandleCallWithReturn<object?>({method.MemberId}, \"{method.Name}\", {argsArray}, null, out var __rawResult))");
writer.AppendLine("{");
writer.IncreaseIndent();
writer.AppendLine($"var __result = ({method.ReturnType})__rawResult!;");
EmitOutRefReadback(writer, method);
writer.AppendLine("return __result;");
writer.DecreaseIndent();
writer.AppendLine("}");
writer.AppendLine($"return _wrappedInstance.{method.Name}({argPassList});");
}
else
{
writer.AppendLine($"if (_engine.TryHandleCallWithReturn<{method.ReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.SmartDefault}, out var __result))");
Expand Down Expand Up @@ -326,10 +348,27 @@ private static void GenerateWrapProperty(CodeWriter writer, MockMemberModel prop
writer.CloseBrace();
}
}
else if (prop.IsAbstractMember && prop.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"get => ({prop.ReturnType})_engine.HandleCallWithReturn<object?>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), null)!;");
}
else if (prop.IsAbstractMember)
{
writer.AppendLine($"get => _engine.HandleCallWithReturn<{prop.ReturnType}>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), {prop.SmartDefault});");
}
else if (prop.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine("get");
writer.OpenBrace();
writer.AppendLine($"if (_engine.TryHandleCallWithReturn<object?>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), null, out var __rawResult))");
writer.AppendLine("{");
writer.IncreaseIndent();
writer.AppendLine($"return ({prop.ReturnType})__rawResult!;");
writer.DecreaseIndent();
writer.AppendLine("}");
writer.AppendLine($"return _wrappedInstance.{prop.Name};");
writer.CloseBrace();
}
else
{
writer.AppendLine("get");
Expand Down Expand Up @@ -543,9 +582,19 @@ private static void GeneratePartialMethodBody(CodeWriter writer, MockMemberModel
else if (method.IsAsync)
{
// async method with return (Task<T>/ValueTask<T>)
writer.AppendLine($"if (_engine.TryHandleCallWithReturn<{method.UnwrappedReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.UnwrappedSmartDefault}, out var __result))");
var tryArg = method.IsReturnTypeStaticAbstractInterface ? "object?" : method.UnwrappedReturnType;
var tryDefault = method.IsReturnTypeStaticAbstractInterface ? "null" : method.UnwrappedSmartDefault;
writer.AppendLine($"if (_engine.TryHandleCallWithReturn<{tryArg}>({method.MemberId}, \"{method.Name}\", {argsArray}, {tryDefault}, out var __rawResult))");
writer.AppendLine("{");
writer.IncreaseIndent();
if (method.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"var __result = ({method.UnwrappedReturnType})__rawResult!;");
}
else
{
writer.AppendLine($"var __result = __rawResult;");
}
EmitOutRefReadback(writer, method);
if (method.IsValueTask)
{
Expand Down Expand Up @@ -578,6 +627,18 @@ private static void GeneratePartialMethodBody(CodeWriter writer, MockMemberModel
writer.AppendLine("}");
writer.AppendLine($"return base.{method.Name}({argPassList});");
}
else if (method.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"if (_engine.TryHandleCallWithReturn<object?>({method.MemberId}, \"{method.Name}\", {argsArray}, null, out var __rawResult))");
writer.AppendLine("{");
writer.IncreaseIndent();
writer.AppendLine($"var __result = ({method.ReturnType})__rawResult!;");
EmitOutRefReadback(writer, method);
writer.AppendLine("return __result;");
writer.DecreaseIndent();
writer.AppendLine("}");
writer.AppendLine($"return base.{method.Name}({argPassList});");
}
else
{
// synchronous method with return value
Expand Down Expand Up @@ -644,9 +705,18 @@ private static void GenerateEngineDispatchBody(CodeWriter writer, MockMemberMode
else if (method.IsAsync)
{
// Async method with return value (Task<T> or ValueTask<T>)
var unwrappedArg = method.IsReturnTypeStaticAbstractInterface ? "object?" : method.UnwrappedReturnType;
var unwrappedDefault = method.IsReturnTypeStaticAbstractInterface ? "null" : method.UnwrappedSmartDefault;
using (writer.Block("try"))
{
writer.AppendLine($"var __result = _engine.HandleCallWithReturn<{method.UnwrappedReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.UnwrappedSmartDefault});");
if (method.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"var __result = ({method.UnwrappedReturnType})_engine.HandleCallWithReturn<{unwrappedArg}>({method.MemberId}, \"{method.Name}\", {argsArray}, {unwrappedDefault})!;");
}
else
{
writer.AppendLine($"var __result = _engine.HandleCallWithReturn<{unwrappedArg}>({method.MemberId}, \"{method.Name}\", {argsArray}, {unwrappedDefault});");
}
EmitOutRefReadback(writer, method);
if (method.IsValueTask)
{
Expand Down Expand Up @@ -686,6 +756,21 @@ private static void GenerateEngineDispatchBody(CodeWriter writer, MockMemberMode
writer.AppendLine("return default;");
}
}
else if (method.IsReturnTypeStaticAbstractInterface)
{
// Return type is an interface with static abstract members — CS8920 prevents using it
// as a generic type argument. Use object? and cast.
if (hasOutRef)
{
writer.AppendLine($"var __result = ({method.ReturnType})_engine.HandleCallWithReturn<object?>({method.MemberId}, \"{method.Name}\", {argsArray}, null)!;");
EmitOutRefReadback(writer, method);
writer.AppendLine("return __result;");
}
else
{
writer.AppendLine($"return ({method.ReturnType})_engine.HandleCallWithReturn<object?>({method.MemberId}, \"{method.Name}\", {argsArray}, null)!;");
}
}
else
{
// Synchronous method with return value — need to read back out/ref before returning
Expand Down Expand Up @@ -718,6 +803,10 @@ private static void GenerateInterfaceProperty(CodeWriter writer, MockMemberModel
writer.AppendLine("return default;");
writer.CloseBrace();
}
else if (prop.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"get => ({prop.ReturnType})_engine.HandleCallWithReturn<object?>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), null)!;");
}
else
{
writer.AppendLine($"get => _engine.HandleCallWithReturn<{prop.ReturnType}>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), {prop.SmartDefault});");
Expand Down Expand Up @@ -772,10 +861,28 @@ private static void GeneratePartialProperty(CodeWriter writer, MockMemberModel p
writer.CloseBrace();
}
}
else if (prop.IsAbstractMember && prop.IsReturnTypeStaticAbstractInterface)
{
writer.AppendLine($"get => ({prop.ReturnType})_engine.HandleCallWithReturn<object?>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), null)!;");
}
else if (prop.IsAbstractMember)
{
writer.AppendLine($"get => _engine.HandleCallWithReturn<{prop.ReturnType}>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), {prop.SmartDefault});");
}
else if (prop.IsReturnTypeStaticAbstractInterface)
{
// Virtual property getter: try engine, fall back to base (CS8920-safe)
writer.AppendLine("get");
writer.OpenBrace();
writer.AppendLine($"if (_engine.TryHandleCallWithReturn<object?>({prop.MemberId}, \"get_{prop.Name}\", global::System.Array.Empty<object?>(), null, out var __rawResult))");
writer.AppendLine("{");
writer.IncreaseIndent();
writer.AppendLine($"return ({prop.ReturnType})__rawResult!;");
writer.DecreaseIndent();
writer.AppendLine("}");
writer.AppendLine($"return base.{prop.Name};");
writer.CloseBrace();
}
else
{
// Virtual property getter: try engine, fall back to base
Expand Down
Loading
Loading