diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs
index 2bf62d0de..8c3bca4da 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs
@@ -20,6 +20,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
/// Whether the old property value is being directly referenced.
/// Indicates whether the property is of a reference type or an unconstrained type parameter.
/// Indicates whether to include nullability annotations on the setter.
+/// Indicates whether to annotate the setter as requiring unreferenced code.
/// The sequence of forwarded attributes for the generated property.
internal sealed record PropertyInfo(
string TypeNameWithNullabilityAnnotations,
@@ -33,4 +34,5 @@ internal sealed record PropertyInfo(
bool IsOldPropertyValueDirectlyReferenced,
bool IsReferenceTypeOrUnconstraindTypeParameter,
bool IncludeMemberNotNullOnSetAccessor,
+ bool IncludeRequiresUnreferencedCodeOnSetAccessor,
EquatableArray ForwardedAttributes);
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
index 6aefd581a..d029295bd 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
@@ -297,6 +297,13 @@ public static bool TryGetInfo(
token.ThrowIfCancellationRequested();
+ // We should generate [RequiresUnreferencedCode] on the setter if [NotifyDataErrorInfo] was used and the attribute is available
+ bool includeRequiresUnreferencedCodeOnSetAccessor =
+ notifyDataErrorInfo &&
+ semanticModel.Compilation.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute");
+
+ token.ThrowIfCancellationRequested();
+
// Prepare the effective property changing/changed names. For the property changing names,
// there are two possible cases: if the mode is disabled, then there are no names to report
// at all. If the mode is enabled, then the list is just the same as for property changed.
@@ -321,6 +328,7 @@ public static bool TryGetInfo(
isOldPropertyValueDirectlyReferenced,
isReferenceTypeOrUnconstraindTypeParameter,
includeMemberNotNullOnSetAccessor,
+ includeRequiresUnreferencedCodeOnSetAccessor,
forwardedAttributes.ToImmutable());
diagnostics = builder.ToImmutable();
@@ -1044,6 +1052,19 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(propertyInfo.FieldName)))))));
}
+ // Add the [RequiresUnreferencedCode] attribute if needed:
+ //
+ // [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
+ //
+ if (propertyInfo.IncludeRequiresUnreferencedCodeOnSetAccessor)
+ {
+ setAccessor = setAccessor.AddAttributeLists(
+ AttributeList(SingletonSeparatedList(
+ Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode"))
+ .AddArgumentListArguments(
+ AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("The type of the current instance cannot be statically discovered.")))))));
+ }
+
// Construct the generated property as follows:
//
// ///
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
index f98b7e917..c3e48fba6 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
@@ -2733,6 +2733,291 @@ internal static class __KnownINotifyPropertyChangedArgs
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result), ("__KnownINotifyPropertyChangingArgs.g.cs", null), ("__KnownINotifyPropertyChangedArgs.g.cs", changedArgs));
}
+ [TestMethod]
+ public void ObservableProperty_NotifyDataErrorInfo_EmitsTrimAnnotationsWhenNeeded()
+ {
+ string source = """
+ using System.ComponentModel.DataAnnotations;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableValidator
+ {
+ [ObservableProperty]
+ [NotifyDataErrorInfo]
+ [Required]
+ 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", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::System.ComponentModel.DataAnnotations.RequiredAttribute()]
+ public string? Name
+ {
+ get => name;
+ [global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
+ 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;
+ ValidateProperty(value, "Name");
+ 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", )]
+ 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", )]
+ 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", )]
+ 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", )]
+ 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", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::System.ComponentModel.DataAnnotations.RequiredAttribute()]
+ 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;
+ ValidateProperty(value, "Name");
+ 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", )]
+ 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", )]
+ 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", )]
+ 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", )]
+ partial void OnNameChanged(string? oldValue, string? newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservableProperty_NotifyDataErrorInfo_EmitsTrimAnnotationsWhenNeeded_AppendToNullableAttribute()
+ {
+ string source = """
+ using System.ComponentModel.DataAnnotations;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableValidator
+ {
+ [ObservableProperty]
+ [NotifyDataErrorInfo]
+ [Required]
+ 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", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::System.ComponentModel.DataAnnotations.RequiredAttribute()]
+ public string Name
+ {
+ get => name;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")]
+ [global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
+ 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;
+ ValidateProperty(value, "Name");
+ 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", )]
+ 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", )]
+ 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", )]
+ 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", )]
+ 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", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::System.ComponentModel.DataAnnotations.RequiredAttribute()]
+ 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;
+ ValidateProperty(value, "Name");
+ 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", )]
+ 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", )]
+ 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", )]
+ 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", )]
+ partial void OnNameChanged(string? oldValue, string newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+
///
/// Generates the requested sources
///