diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs
index 59e5b33fe..2bf62d0de 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs
@@ -18,7 +18,8 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
/// Whether or not the generated property also broadcasts changes.
/// Whether or not the generated property also validates its value.
/// Whether the old property value is being directly referenced.
-/// Indicates whether the property is of a reference type.
+/// Indicates whether the property is of a reference type or an unconstrained type parameter.
+/// Indicates whether to include nullability annotations on the setter.
/// The sequence of forwarded attributes for the generated property.
internal sealed record PropertyInfo(
string TypeNameWithNullabilityAnnotations,
@@ -30,5 +31,6 @@ internal sealed record PropertyInfo(
bool NotifyPropertyChangedRecipients,
bool NotifyDataErrorInfo,
bool IsOldPropertyValueDirectlyReferenced,
- bool IsReferenceType,
+ bool IsReferenceTypeOrUnconstraindTypeParameter,
+ bool IncludeMemberNotNullOnSetAccessor,
EquatableArray ForwardedAttributes);
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
index c6d35b1d8..5def3ccb8 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
@@ -112,7 +112,13 @@ public static bool TryGetInfo(
bool hasOrInheritsClassLevelNotifyDataErrorInfo = false;
bool hasAnyValidationAttributes = false;
bool isOldPropertyValueDirectlyReferenced = IsOldPropertyValueDirectlyReferenced(fieldSymbol, propertyName);
- bool isReferenceType = fieldSymbol.Type.IsReferenceType;
+
+ // Get the nullability info for the property
+ GetNullabilityInfo(
+ fieldSymbol,
+ semanticModel,
+ out bool isReferenceTypeOrUnconstraindTypeParameter,
+ out bool includeMemberNotNullOnSetAccessor);
// Track the property changing event for the property, if the type supports it
if (shouldInvokeOnPropertyChanging)
@@ -261,7 +267,8 @@ public static bool TryGetInfo(
notifyRecipients,
notifyDataErrorInfo,
isOldPropertyValueDirectlyReferenced,
- isReferenceType,
+ isReferenceTypeOrUnconstraindTypeParameter,
+ includeMemberNotNullOnSetAccessor,
forwardedAttributes.ToImmutable());
diagnostics = builder.ToImmutable();
@@ -668,6 +675,49 @@ private static bool IsOldPropertyValueDirectlyReferenced(IFieldSymbol fieldSymbo
return false;
}
+ ///
+ /// Gets the nullability info on the generated property
+ ///
+ /// The input instance to process.
+ /// The instance for the current run.
+ /// Whether the property type supports nullability.
+ /// Whether should be used on the setter.
+ ///
+ private static void GetNullabilityInfo(
+ IFieldSymbol fieldSymbol,
+ SemanticModel semanticModel,
+ out bool isReferenceTypeOrUnconstraindTypeParameter,
+ out bool includeMemberNotNullOnSetAccessor)
+ {
+ // We're using IsValueType here and not IsReferenceType to also cover unconstrained type parameter cases.
+ // This will cover both reference types as well T when the constraints are not struct or unmanaged.
+ // If this is true, it means the field storage can potentially be in a null state (even if not annotated).
+ isReferenceTypeOrUnconstraindTypeParameter = !fieldSymbol.Type.IsValueType;
+
+ // This is used to avoid nullability warnings when setting the property from a constructor, in case the field
+ // was marked as not nullable. Nullability annotations are assumed to always be enabled to make the logic simpler.
+ // Consider this example:
+ //
+ // partial class MyViewModel : ObservableObject
+ // {
+ // public MyViewModel()
+ // {
+ // Name = "Bob";
+ // }
+ //
+ // [ObservableProperty]
+ // private string name;
+ // }
+ //
+ // The [MemberNotNull] attribute is needed on the setter for the generated Name property so that when Name
+ // is set, the compiler can determine that the name backing field is also being set (to a non null value).
+ // Of course, this can only be the case if the field type is also of a type that could be in a null state.
+ includeMemberNotNullOnSetAccessor =
+ isReferenceTypeOrUnconstraindTypeParameter &&
+ fieldSymbol.Type.NullableAnnotation != NullableAnnotation.Annotated &&
+ semanticModel.Compilation.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.MemberNotNullAttribute");
+ }
+
///
/// Gets a instance with the cached args for property changing notifications.
///
@@ -880,6 +930,27 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
.Select(static a => AttributeList(SingletonSeparatedList(a.GetSyntax())))
.ToImmutableArray();
+ // Prepare the setter for the generated property:
+ //
+ // set
+ // {
+ //
+ // }
+ AccessorDeclarationSyntax setAccessor = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithBody(Block(setterIfStatement));
+
+ // Add the [MemberNotNull] attribute if needed:
+ //
+ // [MemberNotNull("")]
+ //
+ if (propertyInfo.IncludeMemberNotNullOnSetAccessor)
+ {
+ setAccessor = setAccessor.AddAttributeLists(
+ AttributeList(SingletonSeparatedList(
+ Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.MemberNotNull"))
+ .AddArgumentListArguments(
+ AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(propertyInfo.FieldName)))))));
+ }
+
// Construct the generated property as follows:
//
// ///
@@ -889,10 +960,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
// public
// {
// get => ;
- // set
- // {
- //
- // }
+ //
// }
return
PropertyDeclaration(propertyType, Identifier(propertyInfo.PropertyName))
@@ -910,8 +978,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithExpressionBody(ArrowExpressionClause(IdentifierName(propertyInfo.FieldName)))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
- AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
- .WithBody(Block(setterIfStatement)));
+ setAccessor);
}
///
@@ -952,7 +1019,7 @@ public static ImmutableArray GetOnPropertyChangeMethods
// happen when the property is first set to some value that is not null (but the backing field would still be so).
// As a cheap way to check whether we need to add nullable, we can simply check whether the type name with nullability
// annotations ends with a '?'. If it doesn't and the type is a reference type, we add it. Otherwise, we keep it.
- TypeSyntax oldValueTypeSyntax = propertyInfo.IsReferenceType switch
+ TypeSyntax oldValueTypeSyntax = propertyInfo.IsReferenceTypeOrUnconstraindTypeParameter switch
{
true when !propertyInfo.TypeNameWithNullabilityAnnotations.EndsWith("?")
=> IdentifierName($"{propertyInfo.TypeNameWithNullabilityAnnotations}?"),
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
index e82b77ff4..aa8aa0d17 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
@@ -19,10 +19,9 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
public class Test_SourceGeneratorsCodegen
{
[TestMethod]
- public void ObservablePropertyWithPartialMethodWithPreviousValuesNotUsed_DoesNotGenerateFieldReadAndMarksOldValueAsNullable()
+ public void ObservablePropertyWithNonNullableReferenceType_EmitsMemberNotNullAttribute()
{
string source = """
- using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
#nullable enable
@@ -32,7 +31,700 @@ namespace MyApp;
partial class MyViewModel : ObservableObject
{
[ObservableProperty]
- private string name = null!;
+ private string name;
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public string Name
+ {
+ get => name;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnNameChanging(string value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnNameChanging(string? oldValue, string newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnNameChanged(string value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnNameChanged(string? oldValue, string newValue);
+ }
+ }
+ """;
+#else
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public string Name
+ {
+ get => name;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnNameChanging(string value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnNameChanging(string? oldValue, string newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnNameChanged(string value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnNameChanged(string? oldValue, string newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithNonNullableValueType_DoesNotEmitMemberNotNullAttribute()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private Guid id;
+ }
+ """;
+
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public global::System.Guid Id
+ {
+ get => id;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(id, value))
+ {
+ OnIdChanging(value);
+ OnIdChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Id);
+ id = value;
+ OnIdChanged(value);
+ OnIdChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Id);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnIdChanging(global::System.Guid value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnIdChanging(global::System.Guid oldValue, global::System.Guid newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnIdChanged(global::System.Guid value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnIdChanged(global::System.Guid oldValue, global::System.Guid newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithNonNullableUnconstrainedGenericType_EmitsMemberNotNullAttribute()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private T content;
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public T Content
+ {
+ get => content;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("content")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value))
+ {
+ OnContentChanging(value);
+ OnContentChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
+ content = value;
+ OnContentChanged(value);
+ OnContentChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? oldValue, T newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? oldValue, T newValue);
+ }
+ }
+ """;
+#else
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public T Content
+ {
+ get => content;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value))
+ {
+ OnContentChanging(value);
+ OnContentChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
+ content = value;
+ OnContentChanged(value);
+ OnContentChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? oldValue, T newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? oldValue, T newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
+
+ // Also test with C# 8 to double check that using a nullable unconstrained type parameter doesn't cause issues, but just a (suppressed) warning
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.CSharp8, ("MyApp.MyViewModel`1.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithNonNullableConstrainedNotNullGenericType_EmitsMemberNotNullAttribute()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ where T : notnull
+ {
+ [ObservableProperty]
+ private T content;
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public T Content
+ {
+ get => content;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("content")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value))
+ {
+ OnContentChanging(value);
+ OnContentChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
+ content = value;
+ OnContentChanged(value);
+ OnContentChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? oldValue, T newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? oldValue, T newValue);
+ }
+ }
+ """;
+#else
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public T Content
+ {
+ get => content;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value))
+ {
+ OnContentChanging(value);
+ OnContentChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
+ content = value;
+ OnContentChanged(value);
+ OnContentChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? oldValue, T newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? oldValue, T newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithNonNullableConstrainedReferenceTypeGenericType_EmitsMemberNotNullAttribute()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ where T : class
+ {
+ [ObservableProperty]
+ private T content;
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public T Content
+ {
+ get => content;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("content")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value))
+ {
+ OnContentChanging(value);
+ OnContentChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
+ content = value;
+ OnContentChanged(value);
+ OnContentChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? oldValue, T newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? oldValue, T newValue);
+ }
+ }
+ """;
+#else
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public T Content
+ {
+ get => content;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value))
+ {
+ OnContentChanging(value);
+ OnContentChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
+ content = value;
+ OnContentChanged(value);
+ OnContentChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? oldValue, T newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? oldValue, T newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithNonNullableConstrainedValueTypeGenericType_DoesNotEmitMemberNotNullAttribute()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ where T : struct
+ {
+ [ObservableProperty]
+ private T content;
+ }
+ """;
+
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public T Content
+ {
+ get => content;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value))
+ {
+ OnContentChanging(value);
+ OnContentChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
+ content = value;
+ OnContentChanged(value);
+ OnContentChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T oldValue, T newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T oldValue, T newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithNullableReferenceType_DoesNotEmitMemberNotNullAttribute()
+ {
+ string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string? name;
}
""";
@@ -47,12 +739,12 @@ partial class MyViewModel
///
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
- public string Name
+ public string? Name
{
get => name;
set
{
- if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
{
OnNameChanging(value);
OnNameChanging(default, value);
@@ -69,24 +761,100 @@ public string Name
/// The new property value being set.
/// This method is invoked right before the value of is changed.
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
- partial void OnNameChanging(string value);
+ partial void OnNameChanging(string? value);
/// Executes the logic for when is changing.
/// The previous property value that is being replaced.
/// The new property value being set.
/// This method is invoked right before the value of is changed.
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
- partial void OnNameChanging(string? oldValue, string newValue);
+ partial void OnNameChanging(string? oldValue, string? newValue);
/// Executes the logic for when just changed.
/// The new property value that was set.
/// This method is invoked right after the value of is changed.
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
- partial void OnNameChanged(string value);
+ partial void OnNameChanged(string? value);
/// Executes the logic for when just changed.
/// The previous property value that was replaced.
/// The new property value that was set.
/// This method is invoked right after the value of is changed.
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
- partial void OnNameChanged(string? oldValue, string newValue);
+ partial void OnNameChanged(string? oldValue, string? newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithNullableValueType_DoesNotEmitMemberNotNullAttribute()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private Guid? id;
+ }
+ """;
+
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public global::System.Guid? Id
+ {
+ get => id;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(id, value))
+ {
+ OnIdChanging(value);
+ OnIdChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Id);
+ id = value;
+ OnIdChanged(value);
+ OnIdChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Id);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnIdChanging(global::System.Guid? value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnIdChanging(global::System.Guid? oldValue, global::System.Guid? newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnIdChanged(global::System.Guid? value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnIdChanged(global::System.Guid? oldValue, global::System.Guid? newValue);
}
}
""";
@@ -94,6 +862,236 @@ public string Name
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
}
+ [TestMethod]
+ public void ObservablePropertyWithNullableUnconstrainedGenericType_DoesNotEmitMemberNotNullAttribute()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private T? content;
+ }
+ """;
+
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public T? Content
+ {
+ get => content;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value))
+ {
+ OnContentChanging(value);
+ OnContentChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
+ content = value;
+ OnContentChanged(value);
+ OnContentChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? oldValue, T? newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? oldValue, T? newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithNullableConstrainedReferenceTypeGenericType_DoesNotEmitMemberNotNullAttribute()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ where T : class
+ {
+ [ObservableProperty]
+ private T? content;
+ }
+ """;
+
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public T? Content
+ {
+ get => content;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value))
+ {
+ OnContentChanging(value);
+ OnContentChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
+ content = value;
+ OnContentChanged(value);
+ OnContentChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? oldValue, T? newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? oldValue, T? newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithNullableConstrainedValueTypeGenericType_DoesNotEmitMemberNotNullAttribute()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ where T : struct
+ {
+ [ObservableProperty]
+ private T? content;
+ }
+ """;
+
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public T? Content
+ {
+ get => content;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value))
+ {
+ OnContentChanging(value);
+ OnContentChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content);
+ content = value;
+ OnContentChanged(value);
+ OnContentChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanging(T? oldValue, T? newValue);
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
+ partial void OnContentChanged(T? oldValue, T? newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
+ }
+
// See https://github.com/CommunityToolkit/dotnet/issues/601
[TestMethod]
public void ObservablePropertyWithForwardedAttributesWithNumberLiterals_PreservesType()
@@ -524,6 +1522,18 @@ public string? A
/// The generators to apply to the input syntax tree.
/// The source files to compare.
private static void VerifyGenerateSources(string source, IIncrementalGenerator[] generators, params (string Filename, string Text)[] results)
+ {
+ VerifyGenerateSources(source, generators, LanguageVersion.CSharp10, results);
+ }
+
+ ///
+ /// Generates the requested sources
+ ///
+ /// The input source to process.
+ /// The generators to apply to the input syntax tree.
+ /// The language version to use.
+ /// The source files to compare.
+ private static void VerifyGenerateSources(string source, IIncrementalGenerator[] generators, LanguageVersion languageVersion, params (string Filename, string Text)[] results)
{
// Ensure CommunityToolkit.Mvvm and System.ComponentModel.DataAnnotations are loaded
Type observableObjectType = typeof(ObservableObject);
@@ -536,7 +1546,7 @@ from assembly in AppDomain.CurrentDomain.GetAssemblies()
let reference = MetadataReference.CreateFromFile(assembly.Location)
select reference;
- SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10));
+ SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(languageVersion));
// Create a syntax tree with the input source
CSharpCompilation compilation = CSharpCompilation.Create(
@@ -555,7 +1565,14 @@ from assembly in AppDomain.CurrentDomain.GetAssemblies()
foreach ((string filename, string text) in results)
{
- SyntaxTree generatedTree = outputCompilation.SyntaxTrees.Single(tree => Path.GetFileName(tree.FilePath) == filename);
+ string filePath = filename;
+
+#if !ROSLYN_4_3_1_OR_GREATER
+ // Adjust the filenames for the legacy Roslyn 4.0
+ filePath = filePath.Replace('`', '_');
+#endif
+
+ SyntaxTree generatedTree = outputCompilation.SyntaxTrees.Single(tree => Path.GetFileName(tree.FilePath) == filePath);
Assert.AreEqual(text, generatedTree.ToString());
}
diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs
index e485793df..c6404a87f 100644
--- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs
+++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs
@@ -6,6 +6,9 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
+#if NET6_0_OR_GREATER
+using System.Diagnostics.CodeAnalysis;
+#endif
using System.Linq;
using System.Reflection;
#if NET6_0_OR_GREATER
@@ -1037,6 +1040,17 @@ public void Test_ObservableProperty_ModelWithObservablePropertyWithUnderscoreAnd
Assert.IsTrue(model.IsReadOnly);
}
+#if NET6_0_OR_GREATER
+ [TestMethod]
+ public void Test_ObservableProperty_MemberNotNullAttributeIsPresent()
+ {
+ MemberNotNullAttribute? attribute = typeof(ModelWithNonNullableObservableProperty).GetProperty(nameof(ModelWithNonNullableObservableProperty.Name))!.SetMethod!.GetCustomAttribute();
+
+ Assert.IsNotNull(attribute);
+ CollectionAssert.AreEqual(new[] { nameof(ModelWithNonNullableObservableProperty.name) }, attribute.Members);
+ }
+#endif
+
public abstract partial class BaseViewModel : ObservableObject
{
public string? Content { get; set; }
@@ -1664,4 +1678,19 @@ private partial class ModelWithObservablePropertyWithUnderscoreAndUppercase : Ob
[ObservableProperty]
private bool _IsReadOnly;
}
+
+#if NET6_0_OR_GREATER
+ // See https://github.com/CommunityToolkit/dotnet/issues/645
+ // This viewmodel is here only to double check no warnings are emitted when the attribute is present
+ public partial class ModelWithNonNullableObservableProperty : ObservableObject
+ {
+ public ModelWithNonNullableObservableProperty()
+ {
+ Name = "Bob";
+ }
+
+ [ObservableProperty]
+ internal string name;
+ }
+#endif
}