@@ -418,6 +418,105 @@ private void Test_AlsoBroadcastChange_Test<T>(Func<IMessenger, T> factory, Actio
418418 Assert . AreEqual ( propertyName , messages [ 0 ] . Message . PropertyName ) ;
419419 }
420420
421+ #if NET6_0_OR_GREATER
422+ // See https://github.com/CommunityToolkit/dotnet/issues/155
423+ [ TestMethod ]
424+ public void Test_ObservableProperty_NullabilityAnnotations_Simple ( )
425+ {
426+ // List<string?>?
427+ NullabilityInfoContext context = new ( ) ;
428+ NullabilityInfo info = context . Create ( typeof ( NullableRepro ) . GetProperty ( nameof ( NullableRepro . NullableList ) ) ! ) ;
429+
430+ Assert . AreEqual ( typeof ( List < string > ) , info . Type ) ;
431+ Assert . AreEqual ( NullabilityState . Nullable , info . ReadState ) ;
432+ Assert . AreEqual ( NullabilityState . Nullable , info . WriteState ) ;
433+ Assert . AreEqual ( 1 , info . GenericTypeArguments . Length ) ;
434+
435+ NullabilityInfo elementInfo = info . GenericTypeArguments [ 0 ] ;
436+
437+ Assert . AreEqual ( typeof ( string ) , elementInfo . Type ) ;
438+ Assert . AreEqual ( NullabilityState . Nullable , elementInfo . ReadState ) ;
439+ Assert . AreEqual ( NullabilityState . Nullable , elementInfo . WriteState ) ;
440+ }
441+
442+ // See https://github.com/CommunityToolkit/dotnet/issues/155
443+ [ TestMethod ]
444+ public void Test_ObservableProperty_NullabilityAnnotations_Complex ( )
445+ {
446+ // Foo<Foo<string?, int>.Bar<object?>?, StrongBox<Foo<int, string?>.Bar<object>?>?>?
447+ NullabilityInfoContext context = new ( ) ;
448+ NullabilityInfo info = context . Create ( typeof ( NullableRepro ) . GetProperty ( nameof ( NullableRepro . NullableMess ) ) ! ) ;
449+
450+ Assert . AreEqual ( typeof ( Foo < Foo < string ? , int > . Bar < object ? > ? , StrongBox < Foo < int , string ? > . Bar < object > ? > ? > ) , info . Type ) ;
451+ Assert . AreEqual ( NullabilityState . Nullable , info . ReadState ) ;
452+ Assert . AreEqual ( NullabilityState . Nullable , info . WriteState ) ;
453+ Assert . AreEqual ( 2 , info . GenericTypeArguments . Length ) ;
454+
455+ NullabilityInfo leftInfo = info . GenericTypeArguments [ 0 ] ;
456+
457+ Assert . AreEqual ( typeof ( Foo < string ? , int > . Bar < object ? > ) , leftInfo . Type ) ;
458+ Assert . AreEqual ( NullabilityState . Nullable , leftInfo . ReadState ) ;
459+ Assert . AreEqual ( NullabilityState . Nullable , leftInfo . WriteState ) ;
460+ Assert . AreEqual ( 3 , leftInfo . GenericTypeArguments . Length ) ;
461+
462+ NullabilityInfo leftInfo0 = leftInfo . GenericTypeArguments [ 0 ] ;
463+
464+ Assert . AreEqual ( typeof ( string ) , leftInfo0 . Type ) ;
465+ Assert . AreEqual ( NullabilityState . Nullable , leftInfo0 . ReadState ) ;
466+ Assert . AreEqual ( NullabilityState . Nullable , leftInfo0 . WriteState ) ;
467+
468+ NullabilityInfo leftInfo1 = leftInfo . GenericTypeArguments [ 1 ] ;
469+
470+ Assert . AreEqual ( typeof ( int ) , leftInfo1 . Type ) ;
471+ Assert . AreEqual ( NullabilityState . NotNull , leftInfo1 . ReadState ) ;
472+ Assert . AreEqual ( NullabilityState . NotNull , leftInfo1 . WriteState ) ;
473+
474+ NullabilityInfo leftInfo2 = leftInfo . GenericTypeArguments [ 2 ] ;
475+
476+ Assert . AreEqual ( typeof ( object ) , leftInfo2 . Type ) ;
477+ Assert . AreEqual ( NullabilityState . Nullable , leftInfo2 . ReadState ) ;
478+ Assert . AreEqual ( NullabilityState . Nullable , leftInfo2 . WriteState ) ;
479+
480+ NullabilityInfo rightInfo = info . GenericTypeArguments [ 1 ] ;
481+
482+ Assert . AreEqual ( typeof ( StrongBox < Foo < int , string ? > . Bar < object > ? > ) , rightInfo . Type ) ;
483+ Assert . AreEqual ( NullabilityState . Nullable , rightInfo . ReadState ) ;
484+ Assert . AreEqual ( NullabilityState . Nullable , rightInfo . WriteState ) ;
485+ Assert . AreEqual ( 1 , rightInfo . GenericTypeArguments . Length ) ;
486+
487+ NullabilityInfo rightInnerInfo = rightInfo . GenericTypeArguments [ 0 ] ;
488+
489+ Assert . AreEqual ( typeof ( Foo < int , string ? > . Bar < object > ) , rightInnerInfo . Type ) ;
490+ Assert . AreEqual ( NullabilityState . Nullable , rightInnerInfo . ReadState ) ;
491+ Assert . AreEqual ( NullabilityState . Nullable , rightInnerInfo . WriteState ) ;
492+ Assert . AreEqual ( 3 , rightInnerInfo . GenericTypeArguments . Length ) ;
493+
494+ NullabilityInfo rightInfo0 = rightInnerInfo . GenericTypeArguments [ 0 ] ;
495+
496+ Assert . AreEqual ( typeof ( int ) , rightInfo0 . Type ) ;
497+ Assert . AreEqual ( NullabilityState . NotNull , rightInfo0 . ReadState ) ;
498+ Assert . AreEqual ( NullabilityState . NotNull , rightInfo0 . WriteState ) ;
499+
500+ NullabilityInfo rightInfo1 = rightInnerInfo . GenericTypeArguments [ 1 ] ;
501+
502+ Assert . AreEqual ( typeof ( string ) , rightInfo1 . Type ) ;
503+ Assert . AreEqual ( NullabilityState . Nullable , rightInfo1 . ReadState ) ;
504+ Assert . AreEqual ( NullabilityState . Nullable , rightInfo1 . WriteState ) ;
505+
506+ NullabilityInfo rightInfo2 = rightInnerInfo . GenericTypeArguments [ 2 ] ;
507+
508+ Assert . AreEqual ( typeof ( object ) , rightInfo2 . Type ) ;
509+ //Assert.AreEqual(NullabilityState.NotNull, rightInfo2.ReadState);
510+ //Assert.AreEqual(NullabilityState.NotNull, rightInfo2.WriteState);
511+
512+ // The commented out lines are to work around a weird behavior of the NullabilityInfo API there.
513+ // Arguably we're pushing them a bit far here, but it's fine. Even with those cases commented out,
514+ // the test is already more than enough, plus we can also double check the behavior by looking at
515+ // the generated code. Thoe lines can be uncommented once the behavior is either clarified, or if
516+ // it happens to be a bug which is then fixed in a future version of .NET, once we upgrade as well.
517+ }
518+ #endif
519+
421520 public partial class SampleModel : ObservableObject
422521 {
423522 /// <summary>
@@ -705,4 +804,22 @@ public BroadcastingViewModelWithInheritedAttribute(IMessenger messenger)
705804 [ AlsoBroadcastChange ]
706805 private string ? name2 ;
707806 }
807+
808+ #if NET6_0_OR_GREATER
809+ private partial class NullableRepro : ObservableObject
810+ {
811+ [ ObservableProperty ]
812+ private List < string ? > ? nullableList ;
813+
814+ [ ObservableProperty ]
815+ private Foo < Foo < string ? , int > . Bar < object ? > ? , StrongBox < Foo < int , string ? > . Bar < object > ? > ? > ? nullableMess ;
816+ }
817+
818+ private class Foo < T1 , T2 >
819+ {
820+ public class Bar < T >
821+ {
822+ }
823+ }
824+ #endif
708825}
0 commit comments