@@ -113,6 +113,7 @@ public static bool TryGetInfo(
113
113
bool hasAnyValidationAttributes = false ;
114
114
bool isOldPropertyValueDirectlyReferenced = IsOldPropertyValueDirectlyReferenced ( fieldSymbol , propertyName ) ;
115
115
bool isReferenceType = fieldSymbol . Type . IsReferenceType ;
116
+ bool includeMemberNotNullOnSetAccessor = GetIncludeMemberNotNullOnSetAccessor ( fieldSymbol , semanticModel ) ;
116
117
117
118
// Track the property changing event for the property, if the type supports it
118
119
if ( shouldInvokeOnPropertyChanging )
@@ -262,6 +263,7 @@ public static bool TryGetInfo(
262
263
notifyDataErrorInfo ,
263
264
isOldPropertyValueDirectlyReferenced ,
264
265
isReferenceType ,
266
+ includeMemberNotNullOnSetAccessor ,
265
267
forwardedAttributes . ToImmutable ( ) ) ;
266
268
267
269
diagnostics = builder . ToImmutable ( ) ;
@@ -668,6 +670,36 @@ private static bool IsOldPropertyValueDirectlyReferenced(IFieldSymbol fieldSymbo
668
670
return false ;
669
671
}
670
672
673
+ /// <summary>
674
+ /// Checks whether <see cref="MemberNotNullAttribute"/> should be used on the setter.
675
+ /// </summary>
676
+ /// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
677
+ /// <param name="semanticModel">The <see cref="SemanticModel"/> instance for the current run.</param>
678
+ /// <returns>Whether <see cref="MemberNotNullAttribute"/> should be used on the setter.</returns>
679
+ private static bool GetIncludeMemberNotNullOnSetAccessor ( IFieldSymbol fieldSymbol , SemanticModel semanticModel )
680
+ {
681
+ // This is used to avoid nullability warnings when setting the property from a constructor, in case the field
682
+ // was marked as not nullable. Nullability annotations are assumed to always be enabled to make the logic simpler.
683
+ // Consider this example:
684
+ //
685
+ // partial class MyViewModel : ObservableObject
686
+ // {
687
+ // public MyViewModel()
688
+ // {
689
+ // Name = "Bob";
690
+ // }
691
+ //
692
+ // [ObservableProperty]
693
+ // private string name;
694
+ // }
695
+ //
696
+ // The [MemberNotNull] attribute is needed on the setter for the generated Name property so that when Name
697
+ // is set, the compiler can determine that the name backing field is also being set (to a non null value).
698
+ return
699
+ fieldSymbol . Type is { NullableAnnotation : not NullableAnnotation . Annotated , IsReferenceType : true } &&
700
+ semanticModel . Compilation . HasAccessibleTypeWithMetadataName ( "System.Diagnostics.CodeAnalysis.MemberNotNullAttribute" ) ;
701
+ }
702
+
671
703
/// <summary>
672
704
/// Gets a <see cref="CompilationUnitSyntax"/> instance with the cached args for property changing notifications.
673
705
/// </summary>
@@ -880,6 +912,27 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
880
912
. Select ( static a => AttributeList ( SingletonSeparatedList ( a . GetSyntax ( ) ) ) )
881
913
. ToImmutableArray ( ) ;
882
914
915
+ // Prepare the setter for the generated property:
916
+ //
917
+ // set
918
+ // {
919
+ // <BODY>
920
+ // }
921
+ AccessorDeclarationSyntax setAccessor = AccessorDeclaration ( SyntaxKind . SetAccessorDeclaration ) . WithBody ( Block ( setterIfStatement ) ) ;
922
+
923
+ // Add the [MemberNotNull] attribute if needed:
924
+ //
925
+ // [MemberNotNull("<FIELD_NAME>")]
926
+ // <SET_ACCESSOR>
927
+ if ( propertyInfo . IncludeMemberNotNullOnSetAccessor )
928
+ {
929
+ setAccessor = setAccessor . AddAttributeLists (
930
+ AttributeList ( SingletonSeparatedList (
931
+ Attribute ( IdentifierName ( "global::System.Diagnostics.CodeAnalysis.MemberNotNull" ) )
932
+ . AddArgumentListArguments (
933
+ AttributeArgument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( propertyInfo . FieldName ) ) ) ) ) ) ) ;
934
+ }
935
+
883
936
// Construct the generated property as follows:
884
937
//
885
938
// /// <inheritdoc cref="<FIELD_NAME>"/>
@@ -889,10 +942,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
889
942
// public <FIELD_TYPE><NULLABLE_ANNOTATION?> <PROPERTY_NAME>
890
943
// {
891
944
// get => <FIELD_NAME>;
892
- // set
893
- // {
894
- // <BODY>
895
- // }
945
+ // <SET_ACCESSOR>
896
946
// }
897
947
return
898
948
PropertyDeclaration ( propertyType , Identifier ( propertyInfo . PropertyName ) )
@@ -910,8 +960,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
910
960
AccessorDeclaration ( SyntaxKind . GetAccessorDeclaration )
911
961
. WithExpressionBody ( ArrowExpressionClause ( IdentifierName ( propertyInfo . FieldName ) ) )
912
962
. WithSemicolonToken ( Token ( SyntaxKind . SemicolonToken ) ) ,
913
- AccessorDeclaration ( SyntaxKind . SetAccessorDeclaration )
914
- . WithBody ( Block ( setterIfStatement ) ) ) ;
963
+ setAccessor ) ;
915
964
}
916
965
917
966
/// <summary>
0 commit comments