@@ -1555,6 +1555,64 @@ private static bool IsFastBufferEligibleMethod(Method method)
15551555 return true ;
15561556 }
15571557
1558+ /// <summary>
1559+ /// A <c>T?</c> return where <c>T</c> is one of the method's generic parameters and is
1560+ /// constrained to a reference type (or any other non-value-type constraint such as
1561+ /// <c>class</c>, <c>class?</c>, an interface, or <c>notnull</c>) cannot be expressed in
1562+ /// the explicit setup-interface implementation: CS0460 forbids restating the inherited
1563+ /// constraint, and <c>where T : default</c> (CS8822) conflicts with those constraints.
1564+ /// Without a constraint clause the compiler resolves the bare <c>T?</c> as
1565+ /// <c>Nullable<T></c> and reports CS0453/CS9334/CS0738/CS0266.
1566+ ///
1567+ /// The fix is to drop the trailing <c>?</c> from the setup-side return type
1568+ /// (<c>IReturnMethodSetup<T></c> instead of <c>IReturnMethodSetup<T?></c>) and from
1569+ /// the matching <c>ReturnMethodSetup<T></c> construction. NRT annotations are erased at
1570+ /// runtime, so the underlying setup object is identical and the fluent API still composes.
1571+ /// The user-facing mock body keeps <c>T?</c> because the constraint is visible there.
1572+ /// </summary>
1573+ private static bool ShouldStripNullableGenericReturnAnnotation ( Method method )
1574+ {
1575+ if ( method . GenericParameters is null || method . GenericParameters . Value . Count == 0 )
1576+ {
1577+ return false ;
1578+ }
1579+
1580+ string fullname = method . ReturnType . Fullname ;
1581+ if ( fullname . Length < 2 || fullname [ fullname . Length - 1 ] != '?' )
1582+ {
1583+ return false ;
1584+ }
1585+
1586+ string raw = fullname . Substring ( 0 , fullname . Length - 1 ) ;
1587+ foreach ( GenericParameter gp in method . GenericParameters . Value )
1588+ {
1589+ if ( gp . Name == raw )
1590+ {
1591+ return ! gp . IsStruct && ! gp . IsUnmanaged ;
1592+ }
1593+ }
1594+
1595+ return false ;
1596+ }
1597+
1598+ /// <summary>
1599+ /// Emits the method's return type as it should appear inside the setup-side surface
1600+ /// (the <c>IReturnMethodSetup<...></c> wrapper on the setup interface, the explicit
1601+ /// impl, and the <c>new ReturnMethodSetup<...></c> construction). Strips a trailing
1602+ /// <c>?</c> when <see cref="ShouldStripNullableGenericReturnAnnotation" /> applies.
1603+ /// </summary>
1604+ private static void AppendSetupReturnType ( StringBuilder sb , Method method )
1605+ {
1606+ if ( ShouldStripNullableGenericReturnAnnotation ( method ) )
1607+ {
1608+ string fullname = method . ReturnType . Fullname ;
1609+ sb . Append ( fullname , 0 , fullname . Length - 1 ) ;
1610+ return ;
1611+ }
1612+
1613+ sb . AppendTypeOrWrapper ( method . ReturnType ) ;
1614+ }
1615+
15581616#pragma warning disable S107 // Methods should not have too many parameters
15591617 private static void ImplementMockForInterface ( StringBuilder sb , string mockRegistryName , string name ,
15601618 bool hasEvents , bool hasProtectedMembers , bool hasProtectedEvents , bool hasStaticMembers , bool hasStaticEvents )
@@ -3913,7 +3971,8 @@ private static void AppendMethodSetupDefinition(StringBuilder sb, Class @class,
39133971 : "\t \t global::Mockolate.Setup.IReturnMethodSetup" ) ;
39143972 }
39153973
3916- sb . Append ( '<' ) . AppendTypeOrWrapper ( method . ReturnType ) ;
3974+ sb . Append ( '<' ) ;
3975+ AppendSetupReturnType ( sb , method ) ;
39173976 foreach ( MethodParameter parameter in method . Parameters )
39183977 {
39193978 sb . Append ( ", " ) . AppendTypeOrWrapper ( parameter . Type ) ;
@@ -4208,7 +4267,8 @@ private static void AppendMethodSetupImplementation(StringBuilder sb, Method met
42084267 : "\t \t global::Mockolate.Setup.IReturnMethodSetup" ) ;
42094268 }
42104269
4211- sb . Append ( '<' ) . AppendTypeOrWrapper ( method . ReturnType ) ;
4270+ sb . Append ( '<' ) ;
4271+ AppendSetupReturnType ( sb , method ) ;
42124272 foreach ( MethodParameter parameter in method . Parameters )
42134273 {
42144274 sb . Append ( ", " ) . AppendTypeOrWrapper ( parameter . Type ) ;
@@ -4299,8 +4359,8 @@ private static void AppendMethodSetupImplementation(StringBuilder sb, Method met
42994359 string methodSetupVar = Helpers . GetUniqueLocalVariableName ( "methodSetup" , method . Parameters ) ;
43004360 if ( method . ReturnType != Type . Void )
43014361 {
4302- sb . Append ( "\t \t \t var " ) . Append ( methodSetupVar ) . Append ( " = new global::Mockolate.Setup.ReturnMethodSetup<" )
4303- . AppendTypeOrWrapper ( method . ReturnType ) ;
4362+ sb . Append ( "\t \t \t var " ) . Append ( methodSetupVar ) . Append ( " = new global::Mockolate.Setup.ReturnMethodSetup<" ) ;
4363+ AppendSetupReturnType ( sb , method ) ;
43044364
43054365 foreach ( MethodParameter parameter in method . Parameters )
43064366 {
0 commit comments