@@ -110,21 +110,23 @@ private static void GenerateUnifiedSealedClass(CodeWriter writer, MockMemberMode
110110
111111 var wrapperName = GetWrapperName ( safeName , method ) ;
112112 var matchableParams = method . Parameters . Where ( p => p . Direction != ParameterDirection . Out && ! p . IsRefStruct ) . ToList ( ) ;
113+ var hasRefStructParams = method . HasRefStructParams ;
114+ var allNonOutParams = method . Parameters . Where ( p => p . Direction != ParameterDirection . Out ) . ToList ( ) ;
113115
114116 // Ref struct returns use the void wrapper (can't use generic type args with ref structs)
115117 if ( method . IsVoid || method . IsRefStructReturn )
116118 {
117- GenerateVoidUnifiedClass ( writer , wrapperName , matchableParams , events , method . Parameters ) ;
119+ GenerateVoidUnifiedClass ( writer , wrapperName , matchableParams , events , method . Parameters , hasRefStructParams , allNonOutParams ) ;
118120 }
119121 else
120122 {
121- GenerateReturnUnifiedClass ( writer , wrapperName , matchableParams , setupReturnType , events , method . Parameters ) ;
123+ GenerateReturnUnifiedClass ( writer , wrapperName , matchableParams , setupReturnType , events , method . Parameters , hasRefStructParams , allNonOutParams ) ;
122124 }
123125 }
124126
125127 private static void GenerateReturnUnifiedClass ( CodeWriter writer , string wrapperName ,
126128 List < MockParameterModel > nonOutParams , string returnType , EquatableArray < MockEventModel > events ,
127- EquatableArray < MockParameterModel > allParameters )
129+ EquatableArray < MockParameterModel > allParameters , bool hasRefStructParams , List < MockParameterModel > allNonOutParams )
128130 {
129131 var builderType = $ "global::TUnit.Mocks.Setup.MethodSetupBuilder<{ returnType } >";
130132 var hasOutRef = allParameters . Any ( p => p . Direction == ParameterDirection . Out || p . Direction == ParameterDirection . Ref ) ;
@@ -198,11 +200,30 @@ private static void GenerateReturnUnifiedClass(CodeWriter writer, string wrapper
198200 if ( nonOutParams . Count >= 1 )
199201 {
200202 writer . AppendLine ( ) ;
201- GenerateTypedReturnsOverload ( writer , nonOutParams , returnType , wrapperName ) ;
202- writer . AppendLine ( ) ;
203- GenerateTypedCallbackOverload ( writer , nonOutParams , wrapperName ) ;
204- writer . AppendLine ( ) ;
205- GenerateTypedThrowsOverload ( writer , nonOutParams , wrapperName ) ;
203+ if ( hasRefStructParams )
204+ {
205+ writer . AppendLine ( "#if NET9_0_OR_GREATER" ) ;
206+ GenerateTypedReturnsOverload ( writer , nonOutParams , returnType , wrapperName , allNonOutParams ) ;
207+ writer . AppendLine ( ) ;
208+ GenerateTypedCallbackOverload ( writer , nonOutParams , wrapperName , allNonOutParams ) ;
209+ writer . AppendLine ( ) ;
210+ GenerateTypedThrowsOverload ( writer , nonOutParams , wrapperName , allNonOutParams ) ;
211+ writer . AppendLine ( "#else" ) ;
212+ GenerateTypedReturnsOverload ( writer , nonOutParams , returnType , wrapperName ) ;
213+ writer . AppendLine ( ) ;
214+ GenerateTypedCallbackOverload ( writer , nonOutParams , wrapperName ) ;
215+ writer . AppendLine ( ) ;
216+ GenerateTypedThrowsOverload ( writer , nonOutParams , wrapperName ) ;
217+ writer . AppendLine ( "#endif" ) ;
218+ }
219+ else
220+ {
221+ GenerateTypedReturnsOverload ( writer , nonOutParams , returnType , wrapperName ) ;
222+ writer . AppendLine ( ) ;
223+ GenerateTypedCallbackOverload ( writer , nonOutParams , wrapperName ) ;
224+ writer . AppendLine ( ) ;
225+ GenerateTypedThrowsOverload ( writer , nonOutParams , wrapperName ) ;
226+ }
206227 }
207228
208229 // Typed out/ref parameter setters
@@ -239,7 +260,7 @@ private static void GenerateReturnUnifiedClass(CodeWriter writer, string wrapper
239260
240261 private static void GenerateVoidUnifiedClass ( CodeWriter writer , string wrapperName ,
241262 List < MockParameterModel > nonOutParams , EquatableArray < MockEventModel > events ,
242- EquatableArray < MockParameterModel > allParameters )
263+ EquatableArray < MockParameterModel > allParameters , bool hasRefStructParams , List < MockParameterModel > allNonOutParams )
243264 {
244265 var builderType = "global::TUnit.Mocks.Setup.VoidMethodSetupBuilder" ;
245266 var hasOutRef = allParameters . Any ( p => p . Direction == ParameterDirection . Out || p . Direction == ParameterDirection . Ref ) ;
@@ -307,9 +328,24 @@ private static void GenerateVoidUnifiedClass(CodeWriter writer, string wrapperNa
307328 if ( nonOutParams . Count >= 1 )
308329 {
309330 writer . AppendLine ( ) ;
310- GenerateTypedCallbackOverload ( writer , nonOutParams , wrapperName ) ;
311- writer . AppendLine ( ) ;
312- GenerateTypedThrowsOverload ( writer , nonOutParams , wrapperName ) ;
331+ if ( hasRefStructParams )
332+ {
333+ writer . AppendLine ( "#if NET9_0_OR_GREATER" ) ;
334+ GenerateTypedCallbackOverload ( writer , nonOutParams , wrapperName , allNonOutParams ) ;
335+ writer . AppendLine ( ) ;
336+ GenerateTypedThrowsOverload ( writer , nonOutParams , wrapperName , allNonOutParams ) ;
337+ writer . AppendLine ( "#else" ) ;
338+ GenerateTypedCallbackOverload ( writer , nonOutParams , wrapperName ) ;
339+ writer . AppendLine ( ) ;
340+ GenerateTypedThrowsOverload ( writer , nonOutParams , wrapperName ) ;
341+ writer . AppendLine ( "#endif" ) ;
342+ }
343+ else
344+ {
345+ GenerateTypedCallbackOverload ( writer , nonOutParams , wrapperName ) ;
346+ writer . AppendLine ( ) ;
347+ GenerateTypedThrowsOverload ( writer , nonOutParams , wrapperName ) ;
348+ }
313349 }
314350
315351 // Typed out/ref parameter setters
@@ -345,11 +381,11 @@ private static void GenerateVoidUnifiedClass(CodeWriter writer, string wrapperNa
345381 }
346382
347383 private static void GenerateTypedReturnsOverload ( CodeWriter writer , List < MockParameterModel > nonOutParams ,
348- string returnType , string wrapperName )
384+ string returnType , string wrapperName , List < MockParameterModel > ? allNonOutParams = null )
349385 {
350386 var typeList = string . Join ( ", " , nonOutParams . Select ( p => p . FullyQualifiedType ) ) ;
351387 var funcType = $ "global::System.Func<{ typeList } , { returnType } >";
352- var castArgs = BuildCastArgs ( nonOutParams ) ;
388+ var castArgs = BuildCastArgs ( nonOutParams , allNonOutParams ) ;
353389
354390 writer . AppendLine ( "/// <summary>Configure a typed computed return value using the actual method parameters.</summary>" ) ;
355391 using ( writer . Block ( $ "public { wrapperName } Returns({ funcType } factory)") )
@@ -360,11 +396,11 @@ private static void GenerateTypedReturnsOverload(CodeWriter writer, List<MockPar
360396 }
361397
362398 private static void GenerateTypedCallbackOverload ( CodeWriter writer , List < MockParameterModel > nonOutParams ,
363- string wrapperName )
399+ string wrapperName , List < MockParameterModel > ? allNonOutParams = null )
364400 {
365401 var typeList = string . Join ( ", " , nonOutParams . Select ( p => p . FullyQualifiedType ) ) ;
366402 var actionType = $ "global::System.Action<{ typeList } >";
367- var castArgs = BuildCastArgs ( nonOutParams ) ;
403+ var castArgs = BuildCastArgs ( nonOutParams , allNonOutParams ) ;
368404
369405 writer . AppendLine ( "/// <summary>Execute a typed callback using the actual method parameters.</summary>" ) ;
370406 using ( writer . Block ( $ "public { wrapperName } Callback({ actionType } callback)") )
@@ -375,11 +411,11 @@ private static void GenerateTypedCallbackOverload(CodeWriter writer, List<MockPa
375411 }
376412
377413 private static void GenerateTypedThrowsOverload ( CodeWriter writer , List < MockParameterModel > nonOutParams ,
378- string wrapperName )
414+ string wrapperName , List < MockParameterModel > ? allNonOutParams = null )
379415 {
380416 var typeList = string . Join ( ", " , nonOutParams . Select ( p => p . FullyQualifiedType ) ) ;
381417 var funcType = $ "global::System.Func<{ typeList } , global::System.Exception>";
382- var castArgs = BuildCastArgs ( nonOutParams ) ;
418+ var castArgs = BuildCastArgs ( nonOutParams , allNonOutParams ) ;
383419
384420 writer . AppendLine ( "/// <summary>Configure a typed computed exception using the actual method parameters.</summary>" ) ;
385421 using ( writer . Block ( $ "public { wrapperName } Throws({ funcType } exceptionFactory)") )
@@ -442,13 +478,32 @@ private static void GenerateTypedOutRefMethods(CodeWriter writer, EquatableArray
442478 private static string ToPascalCase ( string name )
443479 => string . IsNullOrEmpty ( name ) ? name : char . ToUpperInvariant ( name [ 0 ] ) + name [ 1 ..] ;
444480
445- private static string BuildCastArgs ( List < MockParameterModel > nonOutParams )
481+ private static string BuildCastArgs ( List < MockParameterModel > nonOutParams , List < MockParameterModel > ? allNonOutParams = null )
446482 {
447- return string . Join ( ", " , nonOutParams . Select ( ( p , i ) =>
448- $ "({ p . FullyQualifiedType } )args[{ i } ]!") ) ;
483+ if ( allNonOutParams is null )
484+ return string . Join ( ", " , nonOutParams . Select ( ( p , i ) => $ "({ p . FullyQualifiedType } )args[{ i } ]!") ) ;
485+
486+ var indexMap = allNonOutParams . Select ( ( p , i ) => ( p , i ) ) . ToDictionary ( x => x . p , x => x . i ) ;
487+ return string . Join ( ", " , nonOutParams . Select ( p => $ "({ p . FullyQualifiedType } )args[{ indexMap [ p ] } ]!") ) ;
449488 }
450489
451490 private static void GenerateMemberMethod ( CodeWriter writer , MockMemberModel method , MockTypeModel model , string safeName )
491+ {
492+ if ( method . HasRefStructParams )
493+ {
494+ writer . AppendLine ( "#if NET9_0_OR_GREATER" ) ;
495+ EmitMemberMethodBody ( writer , method , model , safeName , includeRefStructArgs : true ) ;
496+ writer . AppendLine ( "#else" ) ;
497+ EmitMemberMethodBody ( writer , method , model , safeName , includeRefStructArgs : false ) ;
498+ writer . AppendLine ( "#endif" ) ;
499+ }
500+ else
501+ {
502+ EmitMemberMethodBody ( writer , method , model , safeName , includeRefStructArgs : false ) ;
503+ }
504+ }
505+
506+ private static void EmitMemberMethodBody ( CodeWriter writer , MockMemberModel method , MockTypeModel model , string safeName , bool includeRefStructArgs )
452507 {
453508 // For async methods (Task<T>/ValueTask<T>), unwrap the return type so users write .Returns(5) not .Returns(Task.FromResult(5))
454509 // For void-async methods (Task/ValueTask), IsVoid is already true
@@ -474,7 +529,7 @@ private static void GenerateMemberMethod(CodeWriter writer, MockMemberModel meth
474529 returnType = $ "global::TUnit.Mocks.MockMethodCall<{ setupReturnType } >";
475530 }
476531
477- var paramList = GetArgParameterList ( method ) ;
532+ var paramList = GetArgParameterList ( method , includeRefStructArgs ) ;
478533 var typeParams = GetTypeParameterList ( method ) ;
479534 var constraints = GetConstraintClauses ( method ) ;
480535
@@ -484,9 +539,10 @@ private static void GenerateMemberMethod(CodeWriter writer, MockMemberModel meth
484539
485540 using ( writer . Block ( $ "public static { returnType } { safeMemberName } { typeParams } ({ fullParamList } ){ constraints } ") )
486541 {
487- // Build matchers array (exclude out and ref struct params)
488- var matchableParams = method . Parameters
489- . Where ( p => p . Direction != ParameterDirection . Out && ! p . IsRefStruct ) . ToList ( ) ;
542+ // Build matchers array
543+ var matchableParams = includeRefStructArgs
544+ ? method . Parameters . Where ( p => p . Direction != ParameterDirection . Out ) . ToList ( )
545+ : method . Parameters . Where ( p => p . Direction != ParameterDirection . Out && ! p . IsRefStruct ) . ToList ( ) ;
490546
491547 if ( matchableParams . Count == 0 )
492548 {
@@ -576,13 +632,23 @@ private static void GenerateRaiseExtensionMethods(CodeWriter writer, MockTypeMod
576632 }
577633 }
578634
579- private static string GetArgParameterList ( MockMemberModel method )
635+ private static string GetArgParameterList ( MockMemberModel method , bool includeRefStructArgs )
580636 {
581- // Only include non-out, non-ref-struct parameters as Arg<T> in setup
582- // (ref structs cannot be used as generic type arguments)
583- return string . Join ( ", " , method . Parameters
584- . Where ( p => p . Direction != ParameterDirection . Out && ! p . IsRefStruct )
585- . Select ( p => $ "global::TUnit.Mocks.Arguments.Arg<{ p . FullyQualifiedType } > { p . Name } ") ) ;
637+ var parts = new List < string > ( ) ;
638+ foreach ( var p in method . Parameters )
639+ {
640+ if ( p . Direction == ParameterDirection . Out ) continue ;
641+ if ( p . IsRefStruct )
642+ {
643+ if ( includeRefStructArgs )
644+ parts . Add ( $ "global::TUnit.Mocks.Arguments.RefStructArg<{ p . FullyQualifiedType } > { p . Name } ") ;
645+ }
646+ else
647+ {
648+ parts . Add ( $ "global::TUnit.Mocks.Arguments.Arg<{ p . FullyQualifiedType } > { p . Name } ") ;
649+ }
650+ }
651+ return string . Join ( ", " , parts ) ;
586652 }
587653
588654 private static string GetTypeParameterList ( MockMemberModel method )
0 commit comments