Skip to content

Commit

Permalink
Merge pull request #135 from CommunityToolkit/dev/dynamically-accesse…
Browse files Browse the repository at this point in the history
…d-members-attribute

Add [DynamicallyAccessedMembers] annotations
  • Loading branch information
Sergio0694 authored Mar 9, 2022
2 parents 7daf7bf + 5fb9f70 commit e1ec425
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,56 @@ public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray<
/// <summary>
/// Gets the head <see cref="CompilationUnitSyntax"/> instance.
/// </summary>
/// <param name="isDynamicallyAccessedMembersAttributeAvailable">Indicates whether <c>[DynamicallyAccessedMembers]</c> should be generated.</param>
/// <returns>The head <see cref="CompilationUnitSyntax"/> instance with the type attributes.</returns>
public static CompilationUnitSyntax GetSyntax()
public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersAttributeAvailable)
{
int numberOfAttributes = 5 + (isDynamicallyAccessedMembersAttributeAvailable ? 1 : 0);
ImmutableArray<AttributeListSyntax>.Builder attributes = ImmutableArray.CreateBuilder<AttributeListSyntax>(numberOfAttributes);

// Prepare the base attributes with are always present:
//
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
// [global::System.Diagnostics.DebuggerNonUserCode]
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
// [global::System.Obsolete("This type is not intended to be used directly by user code")]
attributes.Add(
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments(
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).FullName))),
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).Assembly.GetName().Version.ToString())))))));
attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))));
attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))));
attributes.Add(
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))));
attributes.Add(
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
AttributeArgument(LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("This type is not intended to be used directly by user code")))))));

if (isDynamicallyAccessedMembersAttributeAvailable)
{
// Conditionally add the attribute to inform trimming, if the type is available:
//
// [global::System.CodeDom.Compiler.DynamicallyAccessedMembersAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)]
attributes.Add(
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute")).AddArgumentListArguments(
AttributeArgument(ParseExpression("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods"))))));
}

// This code produces a compilation unit as follows:
//
// // <auto-generated/>
// #pragma warning disable
// namespace CommunityToolkit.Mvvm.ComponentModel.__Internals
// {
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
// [global::System.Diagnostics.DebuggerNonUserCode]
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
// [global::System.Obsolete("This type is not intended to be used directly by user code")]
// <ATTRIBUTES>
// internal static partial class __ObservableValidatorExtensions
// {
// }
Expand All @@ -135,21 +171,7 @@ public static CompilationUnitSyntax GetSyntax()
Token(SyntaxKind.InternalKeyword),
Token(SyntaxKind.StaticKeyword),
Token(SyntaxKind.PartialKeyword))
.AddAttributeLists(
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments(
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).FullName))),
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).Assembly.GetName().Version.ToString())))))),
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))),
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))),
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))),
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
AttributeArgument(LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("This type is not intended to be used directly by user code")))))))))
.AddAttributeLists(attributes.MoveToImmutable().ToArray())))
.NormalizeWhitespace();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,19 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.Collect()
.Select(static (item, _) => item.Length > 0);

// Check whether [DynamicallyAccessedMembers] is available
IncrementalValueProvider<bool> isDynamicallyAccessedMembersAttributeAvailable =
context.CompilationProvider
.Select(static (item, _) => item.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute"));

// Gather the conditional flag and attribute availability
IncrementalValueProvider<(bool IsHeaderFileNeeded, bool IsDynamicallyAccessedMembersAttributeAvailable)> headerFileInfo =
isHeaderFileNeeded.Combine(isDynamicallyAccessedMembersAttributeAvailable);

// Generate the header file with the attributes
context.RegisterConditionalImplementationSourceOutput(isHeaderFileNeeded, static context =>
context.RegisterConditionalImplementationSourceOutput(headerFileInfo, static (context, item) =>
{
CompilationUnitSyntax compilationUnit = Execute.GetSyntax();
CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item);
context.AddSource(
hintName: "__ObservableValidatorExtensions.cs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,22 @@ public static void FilterWithLanguageVersion<T>(
}

/// <summary>
/// Conditionally invokes <see cref="IncrementalGeneratorInitializationContext.RegisterImplementationSourceOutput{TSource}(IncrementalValueProvider{TSource}, System.Action{SourceProductionContext, TSource})"/>
/// if the value produced by the input <see cref="IncrementalValueProvider{TValue}"/> is <see langword="true"/>.
/// Conditionally invokes <see cref="IncrementalGeneratorInitializationContext.RegisterImplementationSourceOutput{TSource}(IncrementalValueProvider{TSource}, Action{SourceProductionContext, TSource})"/>
/// if the value produced by the input <see cref="IncrementalValueProvider{TValue}"/> is <see langword="true"/>, and also supplying a given input state.
/// </summary>
/// <param name="context">The input <see cref="IncrementalGeneratorInitializationContext"/> value being used.</param>
/// <param name="source">The source <see cref="IncrementalValueProvider{TValues}"/> instance.</param>
/// <param name="action">The conditional <see cref="Action{T}"/> to invoke.</param>
public static void RegisterConditionalImplementationSourceOutput(
public static void RegisterConditionalImplementationSourceOutput<T>(
this IncrementalGeneratorInitializationContext context,
IncrementalValueProvider<bool> source,
Action<SourceProductionContext> action)
IncrementalValueProvider<(bool Condition, T State)> source,
Action<SourceProductionContext, T> action)
{
context.RegisterImplementationSourceOutput(source, (context, flag) =>
context.RegisterImplementationSourceOutput(source, (context, item) =>
{
if (flag)
if (item.Condition)
{
action(context);
action(context, item.State);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,56 @@ public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray<
/// <summary>
/// Gets the head <see cref="CompilationUnitSyntax"/> instance.
/// </summary>
/// <param name="isDynamicallyAccessedMembersAttributeAvailable">Indicates whether <c>[DynamicallyAccessedMembers]</c> should be generated.</param>
/// <returns>The head <see cref="CompilationUnitSyntax"/> instance with the type attributes.</returns>
public static CompilationUnitSyntax GetSyntax()
public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersAttributeAvailable)
{
int numberOfAttributes = 5 + (isDynamicallyAccessedMembersAttributeAvailable ? 1 : 0);
ImmutableArray<AttributeListSyntax>.Builder attributes = ImmutableArray.CreateBuilder<AttributeListSyntax>(numberOfAttributes);

// Prepare the base attributes with are always present:
//
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
// [global::System.Diagnostics.DebuggerNonUserCode]
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
// [global::System.Obsolete("This type is not intended to be used directly by user code")]
attributes.Add(
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments(
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).FullName))),
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).Assembly.GetName().Version.ToString())))))));
attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))));
attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))));
attributes.Add(
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))));
attributes.Add(
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
AttributeArgument(LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("This type is not intended to be used directly by user code")))))));

if (isDynamicallyAccessedMembersAttributeAvailable)
{
// Conditionally add the attribute to inform trimming, if the type is available:
//
// [global::System.CodeDom.Compiler.DynamicallyAccessedMembersAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)]
attributes.Add(
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute")).AddArgumentListArguments(
AttributeArgument(ParseExpression("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods"))))));
}

// This code produces a compilation unit as follows:
//
// // <auto-generated/>
// #pragma warning disable
// namespace CommunityToolkit.Mvvm.Messaging.__Internals
// {
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
// [global::System.Diagnostics.DebuggerNonUserCode]
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
// [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
// [global::System.Obsolete("This type is not intended to be used directly by user code")]
// <ATTRIBUTES>
// internal static partial class __IMessengerExtensions
// {
// }
Expand All @@ -93,21 +129,7 @@ public static CompilationUnitSyntax GetSyntax()
Token(SyntaxKind.InternalKeyword),
Token(SyntaxKind.StaticKeyword),
Token(SyntaxKind.PartialKeyword))
.AddAttributeLists(
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments(
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).FullName))),
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).Assembly.GetName().Version.ToString())))))),
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))),
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))),
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))),
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
AttributeArgument(LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("This type is not intended to be used directly by user code")))))))))
.AddAttributeLists(attributes.MoveToImmutable().ToArray())))
.NormalizeWhitespace();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,19 @@ item.Symbol.DeclaringSyntaxReferences[0] is SyntaxReference syntaxReference &&
.Collect()
.Select(static (item, _) => item.Length > 0);

// Check whether [DynamicallyAccessedMembers] is available
IncrementalValueProvider<bool> isDynamicallyAccessedMembersAttributeAvailable =
context.CompilationProvider
.Select(static (item, _) => item.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute"));

// Gather the conditional flag and attribute availability
IncrementalValueProvider<(bool IsHeaderFileNeeded, bool IsDynamicallyAccessedMembersAttributeAvailable)> headerFileInfo =
isHeaderFileNeeded.Combine(isDynamicallyAccessedMembersAttributeAvailable);

// Generate the header file with the attributes
context.RegisterConditionalImplementationSourceOutput(isHeaderFileNeeded, static context =>
context.RegisterConditionalImplementationSourceOutput(headerFileInfo, static (context, item) =>
{
CompilationUnitSyntax compilationUnit = Execute.GetSyntax();
CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item);
context.AddSource(
hintName: "__IMessengerExtensions.cs",
Expand Down
24 changes: 24 additions & 0 deletions tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
// See the LICENSE file in the project root for more information.

using System;
#if !NET6_0_OR_GREATER
using System.Collections.Generic;
using System.Linq;
#endif
using System.Reflection;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.VisualStudio.TestTools.UnitTesting;

#pragma warning disable CS0618

namespace CommunityToolkit.Mvvm.UnitTests;

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

[TestMethod]
public void Test_IRecipient_VerifyTrimmingAnnotation()
{
#if NET6_0_OR_GREATER
System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute? attribute =
typeof(Messaging.__Internals.__IMessengerExtensions)
.GetCustomAttribute<System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute>();

Assert.IsNotNull(attribute);
Assert.AreEqual(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods, attribute.MemberTypes);
#else
IEnumerable<Attribute> attributes = typeof(Messaging.__Internals.__IMessengerExtensions).GetCustomAttributes();

Assert.IsFalse(attributes.Any(static a => a.GetType().Name is "DynamicallyAccessedMembersAttribute"));
#endif
}

public class SomeRecipient<T> : ObservableRecipient
{
public SomeRecipient()
Expand Down
Loading

0 comments on commit e1ec425

Please sign in to comment.