Skip to content

Commit af03bdf

Browse files
committed
Remove trimming annoations for [ObservableRecipient] on older runtimes
1 parent afc7232 commit af03bdf

File tree

3 files changed

+90
-6
lines changed

3 files changed

+90
-6
lines changed

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ObservableRecipientInfo.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
1111
/// <param name="HasExplicitConstructors">Whether or not the target type has explicit constructors.</param>
1212
/// <param name="IsAbstract">Whether or not the target type is abstract.</param>
1313
/// <param name="IsObservableValidator">Whether or not the target type inherits from <c>ObservableValidator</c>.</param>
14+
/// <param name="IsRequiresUnreferencedCodeAttributeAvailable">Whether or not the <c>RequiresUnreferencedCodeAttribute</c> type is available.</param>
1415
public sealed record ObservableRecipientInfo(
1516
string TypeName,
1617
bool HasExplicitConstructors,
1718
bool IsAbstract,
18-
bool IsObservableValidator);
19+
bool IsObservableValidator,
20+
bool IsRequiresUnreferencedCodeAttributeAvailable);

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableRecipientGenerator.cs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public ObservableRecipientGenerator()
3434
IncrementalGeneratorInitializationContext context,
3535
IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> source)
3636
{
37-
static ObservableRecipientInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
37+
static ObservableRecipientInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, bool isRequiresUnreferencedCodeAttributeAvailable)
3838
{
3939
string typeName = typeSymbol.Name;
4040
bool hasExplicitConstructors = !(typeSymbol.InstanceConstructors.Length == 1 && typeSymbol.InstanceConstructors[0] is { Parameters.IsEmpty: true, IsImplicitlyDeclared: true });
@@ -45,10 +45,19 @@ static ObservableRecipientInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeDat
4545
typeName,
4646
hasExplicitConstructors,
4747
isAbstract,
48-
isObservableValidator);
48+
isObservableValidator,
49+
isRequiresUnreferencedCodeAttributeAvailable);
4950
}
5051

51-
return source.Select(static (item, _) => (item.Symbol, GetInfo(item.Symbol, item.AttributeData)));
52+
// Check whether [RequiresUnreferencedCode] is available
53+
IncrementalValueProvider<bool> isRequiresUnreferencedCodeAttributeAvailable =
54+
context.CompilationProvider
55+
.Select(static (item, _) => item.GetTypeByMetadataName("System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute") is not null);
56+
57+
return
58+
source
59+
.Combine(isRequiresUnreferencedCodeAttributeAvailable)
60+
.Select(static (item, _) => (item.Left.Symbol, GetInfo(item.Left.Symbol, item.Left.AttributeData, item.Right)));
5261
}
5362

5463
/// <inheritdoc/>
@@ -110,14 +119,31 @@ protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers
110119
}
111120
}
112121

122+
MemberDeclarationSyntax RemoveRequiresUnreferencedCodeAttributeIfNeeded(MemberDeclarationSyntax member)
123+
{
124+
if (!info.IsRequiresUnreferencedCodeAttributeAvailable &&
125+
member is PropertyDeclarationSyntax { Identifier.ValueText: "IsActive" } or MethodDeclarationSyntax { Identifier.ValueText: "OnActivated" })
126+
{
127+
SyntaxNode attributeNode =
128+
member
129+
.DescendantNodes()
130+
.OfType<AttributeListSyntax>()
131+
.First(node => ((IdentifierNameSyntax)((QualifiedNameSyntax)node.Attributes[0].Name).Right).Identifier.ValueText == "RequiresUnreferencedCode");
132+
133+
return member.RemoveNode(attributeNode, SyntaxRemoveOptions.KeepExteriorTrivia)!;
134+
}
135+
136+
return member;
137+
}
138+
113139
// Skip the SetProperty overloads if the target type inherits from ObservableValidator, to avoid conflicts
114140
if (info.IsObservableValidator)
115141
{
116142
foreach (MemberDeclarationSyntax member in memberDeclarations.Where(static member => member is not ConstructorDeclarationSyntax))
117143
{
118144
if (member is not MethodDeclarationSyntax { Identifier.ValueText: "SetProperty" })
119145
{
120-
builder.Add(member);
146+
builder.Add(RemoveRequiresUnreferencedCodeAttributeIfNeeded(member));
121147
}
122148
}
123149

@@ -127,7 +153,7 @@ protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers
127153
// If the target type has at least one custom constructor, only generate methods
128154
foreach (MemberDeclarationSyntax member in memberDeclarations.Where(static member => member is not ConstructorDeclarationSyntax))
129155
{
130-
builder.Add(member);
156+
builder.Add(RemoveRequiresUnreferencedCodeAttributeIfNeeded(member));
131157
}
132158

133159
return builder.ToImmutable();

tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipientAttribute.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Collections.Generic;
67
using System.ComponentModel;
78
using System.ComponentModel.DataAnnotations;
@@ -97,4 +98,59 @@ public void Test_ObservableRecipientAttribute_NonAbstractConstructors()
9798
public partial class NonAbstractPerson : ObservableObject
9899
{
99100
}
101+
102+
[TestMethod]
103+
public void Test_ObservableRecipientAttribute_TrimmingAnnoations_IsActive()
104+
{
105+
bool shouldHaveTrimmingAnnotations =
106+
#if NET6_0_OR_GREATER
107+
true;
108+
#else
109+
false;
110+
#endif
111+
112+
MethodInfo isActivePropertySetter = typeof(TestRecipient).GetProperty(nameof(TestRecipient.IsActive))!.SetMethod!;
113+
114+
if (shouldHaveTrimmingAnnotations)
115+
{
116+
Attribute[] attributes = isActivePropertySetter.GetCustomAttributes().ToArray();
117+
118+
Assert.AreEqual(1, attributes.Length);
119+
Assert.AreEqual("System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute", attributes[0].GetType().ToString());
120+
}
121+
else
122+
{
123+
Attribute[] attributes = isActivePropertySetter.GetCustomAttributes().ToArray();
124+
125+
Assert.AreEqual(0, attributes.Length);
126+
}
127+
}
128+
129+
[TestMethod]
130+
public void Test_ObservableRecipientAttribute_TrimmingAnnoations_OnActivated()
131+
{
132+
bool shouldHaveTrimmingAnnotations =
133+
#if NET6_0_OR_GREATER
134+
true;
135+
#else
136+
false;
137+
#endif
138+
139+
MethodInfo onActivatedMethod = typeof(TestRecipient).GetMethod("OnActivated", BindingFlags.Instance | BindingFlags.NonPublic)!;
140+
IEnumerable<Attribute> attributes = onActivatedMethod.GetCustomAttributes();
141+
142+
if (shouldHaveTrimmingAnnotations)
143+
{
144+
Assert.IsTrue(attributes.Any(static a => a.GetType().ToString() == "System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute"));
145+
}
146+
else
147+
{
148+
Assert.IsFalse(attributes.Any(static a => a.GetType().ToString() == "System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute"));
149+
}
150+
}
151+
152+
[ObservableRecipient]
153+
public abstract partial class TestRecipient : ObservableObject
154+
{
155+
}
100156
}

0 commit comments

Comments
 (0)