Skip to content

Commit e1ec425

Browse files
authored
Merge pull request #135 from CommunityToolkit/dev/dynamically-accessed-members-attribute
Add [DynamicallyAccessedMembers] annotations
2 parents 7daf7bf + 5fb9f70 commit e1ec425

7 files changed

+159
-54
lines changed

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,56 @@ public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray<
108108
/// <summary>
109109
/// Gets the head <see cref="CompilationUnitSyntax"/> instance.
110110
/// </summary>
111+
/// <param name="isDynamicallyAccessedMembersAttributeAvailable">Indicates whether <c>[DynamicallyAccessedMembers]</c> should be generated.</param>
111112
/// <returns>The head <see cref="CompilationUnitSyntax"/> instance with the type attributes.</returns>
112-
public static CompilationUnitSyntax GetSyntax()
113+
public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersAttributeAvailable)
113114
{
115+
int numberOfAttributes = 5 + (isDynamicallyAccessedMembersAttributeAvailable ? 1 : 0);
116+
ImmutableArray<AttributeListSyntax>.Builder attributes = ImmutableArray.CreateBuilder<AttributeListSyntax>(numberOfAttributes);
117+
118+
// Prepare the base attributes with are always present:
119+
//
120+
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
121+
// [global::System.Diagnostics.DebuggerNonUserCode]
122+
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
123+
// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
124+
// [global::System.Obsolete("This type is not intended to be used directly by user code")]
125+
attributes.Add(
126+
AttributeList(SingletonSeparatedList(
127+
Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments(
128+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).FullName))),
129+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).Assembly.GetName().Version.ToString())))))));
130+
attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))));
131+
attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))));
132+
attributes.Add(
133+
AttributeList(SingletonSeparatedList(
134+
Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
135+
AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))));
136+
attributes.Add(
137+
AttributeList(SingletonSeparatedList(
138+
Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
139+
AttributeArgument(LiteralExpression(
140+
SyntaxKind.StringLiteralExpression,
141+
Literal("This type is not intended to be used directly by user code")))))));
142+
143+
if (isDynamicallyAccessedMembersAttributeAvailable)
144+
{
145+
// Conditionally add the attribute to inform trimming, if the type is available:
146+
//
147+
// [global::System.CodeDom.Compiler.DynamicallyAccessedMembersAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)]
148+
attributes.Add(
149+
AttributeList(SingletonSeparatedList(
150+
Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute")).AddArgumentListArguments(
151+
AttributeArgument(ParseExpression("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods"))))));
152+
}
153+
114154
// This code produces a compilation unit as follows:
115155
//
116156
// // <auto-generated/>
117157
// #pragma warning disable
118158
// namespace CommunityToolkit.Mvvm.ComponentModel.__Internals
119159
// {
120-
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
121-
// [global::System.Diagnostics.DebuggerNonUserCode]
122-
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
123-
// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
124-
// [global::System.Obsolete("This type is not intended to be used directly by user code")]
160+
// <ATTRIBUTES>
125161
// internal static partial class __ObservableValidatorExtensions
126162
// {
127163
// }
@@ -135,21 +171,7 @@ public static CompilationUnitSyntax GetSyntax()
135171
Token(SyntaxKind.InternalKeyword),
136172
Token(SyntaxKind.StaticKeyword),
137173
Token(SyntaxKind.PartialKeyword))
138-
.AddAttributeLists(
139-
AttributeList(SingletonSeparatedList(
140-
Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments(
141-
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).FullName))),
142-
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).Assembly.GetName().Version.ToString())))))),
143-
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))),
144-
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))),
145-
AttributeList(SingletonSeparatedList(
146-
Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
147-
AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))),
148-
AttributeList(SingletonSeparatedList(
149-
Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
150-
AttributeArgument(LiteralExpression(
151-
SyntaxKind.StringLiteralExpression,
152-
Literal("This type is not intended to be used directly by user code")))))))))
174+
.AddAttributeLists(attributes.MoveToImmutable().ToArray())))
153175
.NormalizeWhitespace();
154176
}
155177

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,19 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
4242
.Collect()
4343
.Select(static (item, _) => item.Length > 0);
4444

45+
// Check whether [DynamicallyAccessedMembers] is available
46+
IncrementalValueProvider<bool> isDynamicallyAccessedMembersAttributeAvailable =
47+
context.CompilationProvider
48+
.Select(static (item, _) => item.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute"));
49+
50+
// Gather the conditional flag and attribute availability
51+
IncrementalValueProvider<(bool IsHeaderFileNeeded, bool IsDynamicallyAccessedMembersAttributeAvailable)> headerFileInfo =
52+
isHeaderFileNeeded.Combine(isDynamicallyAccessedMembersAttributeAvailable);
53+
4554
// Generate the header file with the attributes
46-
context.RegisterConditionalImplementationSourceOutput(isHeaderFileNeeded, static context =>
55+
context.RegisterConditionalImplementationSourceOutput(headerFileInfo, static (context, item) =>
4756
{
48-
CompilationUnitSyntax compilationUnit = Execute.GetSyntax();
57+
CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item);
4958

5059
context.AddSource(
5160
hintName: "__ObservableValidatorExtensions.cs",

CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,22 +65,22 @@ public static void FilterWithLanguageVersion<T>(
6565
}
6666

6767
/// <summary>
68-
/// Conditionally invokes <see cref="IncrementalGeneratorInitializationContext.RegisterImplementationSourceOutput{TSource}(IncrementalValueProvider{TSource}, System.Action{SourceProductionContext, TSource})"/>
69-
/// if the value produced by the input <see cref="IncrementalValueProvider{TValue}"/> is <see langword="true"/>.
68+
/// Conditionally invokes <see cref="IncrementalGeneratorInitializationContext.RegisterImplementationSourceOutput{TSource}(IncrementalValueProvider{TSource}, Action{SourceProductionContext, TSource})"/>
69+
/// if the value produced by the input <see cref="IncrementalValueProvider{TValue}"/> is <see langword="true"/>, and also supplying a given input state.
7070
/// </summary>
7171
/// <param name="context">The input <see cref="IncrementalGeneratorInitializationContext"/> value being used.</param>
7272
/// <param name="source">The source <see cref="IncrementalValueProvider{TValues}"/> instance.</param>
7373
/// <param name="action">The conditional <see cref="Action{T}"/> to invoke.</param>
74-
public static void RegisterConditionalImplementationSourceOutput(
74+
public static void RegisterConditionalImplementationSourceOutput<T>(
7575
this IncrementalGeneratorInitializationContext context,
76-
IncrementalValueProvider<bool> source,
77-
Action<SourceProductionContext> action)
76+
IncrementalValueProvider<(bool Condition, T State)> source,
77+
Action<SourceProductionContext, T> action)
7878
{
79-
context.RegisterImplementationSourceOutput(source, (context, flag) =>
79+
context.RegisterImplementationSourceOutput(source, (context, item) =>
8080
{
81-
if (flag)
81+
if (item.Condition)
8282
{
83-
action(context);
83+
action(context, item.State);
8484
}
8585
});
8686
}

CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,56 @@ public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray<
6666
/// <summary>
6767
/// Gets the head <see cref="CompilationUnitSyntax"/> instance.
6868
/// </summary>
69+
/// <param name="isDynamicallyAccessedMembersAttributeAvailable">Indicates whether <c>[DynamicallyAccessedMembers]</c> should be generated.</param>
6970
/// <returns>The head <see cref="CompilationUnitSyntax"/> instance with the type attributes.</returns>
70-
public static CompilationUnitSyntax GetSyntax()
71+
public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersAttributeAvailable)
7172
{
73+
int numberOfAttributes = 5 + (isDynamicallyAccessedMembersAttributeAvailable ? 1 : 0);
74+
ImmutableArray<AttributeListSyntax>.Builder attributes = ImmutableArray.CreateBuilder<AttributeListSyntax>(numberOfAttributes);
75+
76+
// Prepare the base attributes with are always present:
77+
//
78+
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
79+
// [global::System.Diagnostics.DebuggerNonUserCode]
80+
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
81+
// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
82+
// [global::System.Obsolete("This type is not intended to be used directly by user code")]
83+
attributes.Add(
84+
AttributeList(SingletonSeparatedList(
85+
Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments(
86+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).FullName))),
87+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).Assembly.GetName().Version.ToString())))))));
88+
attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))));
89+
attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))));
90+
attributes.Add(
91+
AttributeList(SingletonSeparatedList(
92+
Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
93+
AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))));
94+
attributes.Add(
95+
AttributeList(SingletonSeparatedList(
96+
Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
97+
AttributeArgument(LiteralExpression(
98+
SyntaxKind.StringLiteralExpression,
99+
Literal("This type is not intended to be used directly by user code")))))));
100+
101+
if (isDynamicallyAccessedMembersAttributeAvailable)
102+
{
103+
// Conditionally add the attribute to inform trimming, if the type is available:
104+
//
105+
// [global::System.CodeDom.Compiler.DynamicallyAccessedMembersAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)]
106+
attributes.Add(
107+
AttributeList(SingletonSeparatedList(
108+
Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute")).AddArgumentListArguments(
109+
AttributeArgument(ParseExpression("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods"))))));
110+
}
111+
72112
// This code produces a compilation unit as follows:
73113
//
74114
// // <auto-generated/>
75115
// #pragma warning disable
76116
// namespace CommunityToolkit.Mvvm.Messaging.__Internals
77117
// {
78-
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
79-
// [global::System.Diagnostics.DebuggerNonUserCode]
80-
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
81-
// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
82-
// [global::System.Obsolete("This type is not intended to be used directly by user code")]
118+
// <ATTRIBUTES>
83119
// internal static partial class __IMessengerExtensions
84120
// {
85121
// }
@@ -93,21 +129,7 @@ public static CompilationUnitSyntax GetSyntax()
93129
Token(SyntaxKind.InternalKeyword),
94130
Token(SyntaxKind.StaticKeyword),
95131
Token(SyntaxKind.PartialKeyword))
96-
.AddAttributeLists(
97-
AttributeList(SingletonSeparatedList(
98-
Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments(
99-
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).FullName))),
100-
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).Assembly.GetName().Version.ToString())))))),
101-
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))),
102-
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))),
103-
AttributeList(SingletonSeparatedList(
104-
Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
105-
AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))),
106-
AttributeList(SingletonSeparatedList(
107-
Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
108-
AttributeArgument(LiteralExpression(
109-
SyntaxKind.StringLiteralExpression,
110-
Literal("This type is not intended to be used directly by user code")))))))))
132+
.AddAttributeLists(attributes.MoveToImmutable().ToArray())))
111133
.NormalizeWhitespace();
112134
}
113135

CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,19 @@ item.Symbol.DeclaringSyntaxReferences[0] is SyntaxReference syntaxReference &&
5757
.Collect()
5858
.Select(static (item, _) => item.Length > 0);
5959

60+
// Check whether [DynamicallyAccessedMembers] is available
61+
IncrementalValueProvider<bool> isDynamicallyAccessedMembersAttributeAvailable =
62+
context.CompilationProvider
63+
.Select(static (item, _) => item.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute"));
64+
65+
// Gather the conditional flag and attribute availability
66+
IncrementalValueProvider<(bool IsHeaderFileNeeded, bool IsDynamicallyAccessedMembersAttributeAvailable)> headerFileInfo =
67+
isHeaderFileNeeded.Combine(isDynamicallyAccessedMembersAttributeAvailable);
68+
6069
// Generate the header file with the attributes
61-
context.RegisterConditionalImplementationSourceOutput(isHeaderFileNeeded, static context =>
70+
context.RegisterConditionalImplementationSourceOutput(headerFileInfo, static (context, item) =>
6271
{
63-
CompilationUnitSyntax compilationUnit = Execute.GetSyntax();
72+
CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item);
6473

6574
context.AddSource(
6675
hintName: "__IMessengerExtensions.cs",

tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipient.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
#if !NET6_0_OR_GREATER
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
#endif
10+
using System.Reflection;
611
using CommunityToolkit.Mvvm.ComponentModel;
712
using CommunityToolkit.Mvvm.Messaging;
813
using CommunityToolkit.Mvvm.Messaging.Messages;
914
using Microsoft.VisualStudio.TestTools.UnitTesting;
1015

16+
#pragma warning disable CS0618
17+
1118
namespace CommunityToolkit.Mvvm.UnitTests;
1219

1320
[TestClass]
@@ -85,6 +92,23 @@ public void Test_ObservableRecipient_Broadcast(Type type)
8592
Assert.AreEqual(message.PropertyName, nameof(SomeRecipient<int>.Data));
8693
}
8794

95+
[TestMethod]
96+
public void Test_IRecipient_VerifyTrimmingAnnotation()
97+
{
98+
#if NET6_0_OR_GREATER
99+
System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute? attribute =
100+
typeof(Messaging.__Internals.__IMessengerExtensions)
101+
.GetCustomAttribute<System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute>();
102+
103+
Assert.IsNotNull(attribute);
104+
Assert.AreEqual(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods, attribute.MemberTypes);
105+
#else
106+
IEnumerable<Attribute> attributes = typeof(Messaging.__Internals.__IMessengerExtensions).GetCustomAttributes();
107+
108+
Assert.IsFalse(attributes.Any(static a => a.GetType().Name is "DynamicallyAccessedMembersAttribute"));
109+
#endif
110+
}
111+
88112
public class SomeRecipient<T> : ObservableRecipient
89113
{
90114
public SomeRecipient()

0 commit comments

Comments
 (0)