@@ -421,4 +421,129 @@ partial class Derived : Base {
421421
422422 Assert . AreEqual ( 1 , result . Diagnostics . Count ( d => d . Id == "EXP0035" ) ) ;
423423 }
424+
425+ [ TestMethod ]
426+ public Task GenericReturnTypeWithInnerNullableReference_EmitsCorrectBackingFieldAnnotation ( )
427+ {
428+ // Outer non-nullable, inner ref-nullable — backing field must keep the inner ? to match the property.
429+ var compilation = CreateCompilation (
430+ """
431+ #nullable enable
432+ using ExpressiveSharp.Mapping;
433+
434+ namespace Foo {
435+ public partial class BugA {
436+ [ExpressiveProperty("Items")]
437+ private IEnumerable<Item?> ItemsExpr => new Item?[] { null };
438+ }
439+ public class Item;
440+ }
441+ """ ) ;
442+ var result = RunExpressiveGenerator ( compilation ) ;
443+
444+ Assert . AreEqual ( 0 , result . Diagnostics . Length ,
445+ "Unexpected diagnostics: " + string . Join ( ", " , result . Diagnostics . Select ( d => d . Id + ": " + d . GetMessage ( ) ) ) ) ;
446+ Assert . AreEqual ( 2 , result . GeneratedTrees . Length ) ;
447+
448+ var generated = string . Join ( "\n " , result . GeneratedTrees . Select ( t => t . ToString ( ) ) ) ;
449+ StringAssert . Contains ( generated , "IEnumerable<global::Foo.Item?>? _items" ,
450+ "Backing field must preserve the inner ? on Item, otherwise the synthesized ?? trips CS8619." ) ;
451+
452+ return Verifier . Verify ( string . Join ( "\n \n // ===\n \n " ,
453+ result . GeneratedTrees . Select ( t => t . ToString ( ) ) ) ) ;
454+ }
455+
456+ [ TestMethod ]
457+ public void GenericMethodWithClosedInnerTypeArg_PinsByExactEquality ( )
458+ {
459+ // Inner type arg is concrete (List<int>), exercising the else branch in EnsureMethodInfo.
460+ var compilation = CreateCompilation (
461+ """
462+ #nullable enable
463+ using ExpressiveSharp.Mapping;
464+
465+ namespace Foo {
466+ public static class ListExt {
467+ public static T Pick<T>(this IEnumerable<T> src, List<int> filter) => default!;
468+ }
469+ public partial class ClosedInnerArg {
470+ public List<int> Filter { get; init; } = null!;
471+
472+ [ExpressiveProperty("Head")]
473+ private string HeadExpr => new[] { "x" }.Pick(Filter);
474+ }
475+ }
476+ """ ) ;
477+ var result = RunExpressiveGenerator ( compilation ) ;
478+
479+ Assert . AreEqual ( 0 , result . Diagnostics . Length ,
480+ "Unexpected diagnostics: " + string . Join ( ", " , result . Diagnostics . Select ( d => d . Id + ": " + d . GetMessage ( ) ) ) ) ;
481+
482+ var generated = string . Join ( "\n " , result . GeneratedTrees . Select ( t => t . ToString ( ) ) ) ;
483+ StringAssert . Contains ( generated , "GetGenericArguments()[0] == typeof(int)" ,
484+ "Closed inner type arg must be pinned by exact equality." ) ;
485+ }
486+
487+ [ TestMethod ]
488+ public void GenericMethodWithArrayOfTypeParam_GeneratesValidCode ( )
489+ {
490+ // Inner type arg is T[] (IArrayTypeSymbol), exercising the array recursion in ContainsTypeParameter.
491+ var compilation = CreateCompilation (
492+ """
493+ #nullable enable
494+ using ExpressiveSharp.Mapping;
495+
496+ namespace Foo {
497+ public static class ArrExt {
498+ public static int Total<T>(this IEnumerable<T[]> source) => 0;
499+ }
500+ public partial class ArrayOfTypeParam {
501+ public IEnumerable<int[]> Source { get; init; } = null!;
502+
503+ [ExpressiveProperty("Sum")]
504+ private int SumExpr => Source.Total();
505+ }
506+ }
507+ """ ) ;
508+ var result = RunExpressiveGenerator ( compilation ) ;
509+
510+ Assert . AreEqual ( 0 , result . Diagnostics . Length ,
511+ "Unexpected diagnostics: " + string . Join ( ", " , result . Diagnostics . Select ( d => d . Id + ": " + d . GetMessage ( ) ) ) ) ;
512+
513+ var generated = string . Join ( "\n " , result . GeneratedTrees . Select ( t => t . ToString ( ) ) ) ;
514+ Assert . IsFalse ( generated . Contains ( "typeof(T)" ) || generated . Contains ( "typeof(T[" ) ,
515+ "Array-of-type-parameter slot must not leak the open type parameter into typeof()." ) ;
516+ }
517+
518+ [ TestMethod ]
519+ public Task BodyUsingQueryableWhereOrderBy_EmitsValidExpressionTree ( )
520+ {
521+ // Queryable.Where's predicate-shaped parameter must not leak the open type parameter (TSource → CS0246).
522+ var compilation = CreateCompilation (
523+ """
524+ #nullable enable
525+ using ExpressiveSharp.Mapping;
526+
527+ namespace Foo {
528+ public partial class BugB {
529+ public IQueryable<int> Source { get; init; } = null!;
530+
531+ [ExpressiveProperty("Sorted")]
532+ private IEnumerable<int> SortedExpr => Source.Where(x => x > 0).OrderBy(x => x);
533+ }
534+ }
535+ """ ) ;
536+ var result = RunExpressiveGenerator ( compilation ) ;
537+
538+ Assert . AreEqual ( 0 , result . Diagnostics . Length ,
539+ "Unexpected diagnostics: " + string . Join ( ", " , result . Diagnostics . Select ( d => d . Id + ": " + d . GetMessage ( ) ) ) ) ;
540+ Assert . AreEqual ( 2 , result . GeneratedTrees . Length ) ;
541+
542+ var generated = string . Join ( "\n " , result . GeneratedTrees . Select ( t => t . ToString ( ) ) ) ;
543+ Assert . IsFalse ( generated . Contains ( "TSource" ) ,
544+ "Generated code must not reference the open type parameter TSource." ) ;
545+
546+ return Verifier . Verify ( string . Join ( "\n \n // ===\n \n " ,
547+ result . GeneratedTrees . Select ( t => t . ToString ( ) ) ) ) ;
548+ }
424549}
0 commit comments