Skip to content

Commit a7e0f2c

Browse files
committed
Enable trimming support in the MVVM Toolkit
Also added linker annotations where needed
1 parent 14c6dc3 commit a7e0f2c

File tree

6 files changed

+94
-0
lines changed

6 files changed

+94
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#if !NET6_0_OR_GREATER
6+
7+
namespace System.Diagnostics.CodeAnalysis;
8+
9+
/// <summary>
10+
/// Indicates that the specified method requires dynamic access to code that is not referenced statically.
11+
/// </summary>
12+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)]
13+
[Conditional("DEBUG")]
14+
internal sealed class RequiresUnreferencedCodeAttribute : Attribute
15+
{
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="RequiresUnreferencedCodeAttribute"/> class.
18+
/// </summary>
19+
/// <param name="message">A message that contains information about the usage of unreferenced code.</param>
20+
public RequiresUnreferencedCodeAttribute(string message)
21+
{
22+
Message = message;
23+
}
24+
25+
/// <summary>
26+
/// Gets a message that contains information about the usage of unreferenced code.
27+
/// </summary>
28+
public string Message { get; }
29+
30+
/// <summary>
31+
/// Gets or sets an optional URL that contains more information about the method,
32+
/// why it requires unreferenced code, and what options a consumer has to deal with it.
33+
/// </summary>
34+
public string? Url { get; set; }
35+
}
36+
37+
#endif

CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
3434
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
3535
</ItemGroup>
36+
37+
<!-- Enable trimming support on .NET 6 -->
38+
<PropertyGroup Condition="'$(TargetFramework)' == 'net6.0'">
39+
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
40+
<IsTrimmable>true</IsTrimmable>
41+
</PropertyGroup>
3642

3743
<!-- Source generator project reference for packing -->
3844
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">

CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ protected ObservableRecipient(IMessenger messenger)
5757
public bool IsActive
5858
{
5959
get => this.isActive;
60+
61+
[RequiresUnreferencedCode(
62+
"When this property is set to true, the OnActivated() method will be invoked, which will register all necessary message handlers for this recipient. " +
63+
"This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
64+
"If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
65+
"path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type. " +
66+
"Alternatively, OnActivated() can be manually overwritten, and registration can be done individually for each required message for this recipient.")]
6067
set
6168
{
6269
if (SetProperty(ref this.isActive, value, true))
@@ -84,6 +91,11 @@ public bool IsActive
8491
/// If you need more fine tuned control, want to register messages individually or just prefer
8592
/// the lambda-style syntax for message registration, override this method and register manually.
8693
/// </remarks>
94+
[RequiresUnreferencedCode(
95+
"This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
96+
"If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
97+
"path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type. " +
98+
"Alternatively, OnActivated() can be manually overwritten, and registration can be done individually for each required message for this recipient.")]
8799
protected virtual void OnActivated()
88100
{
89101
Messenger.RegisterAll(this);

CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public abstract class ObservableValidator : ObservableObject, INotifyDataErrorIn
6969
/// be used to validate all properties, which will reference the current instance
7070
/// and no additional services or validation properties and settings.
7171
/// </summary>
72+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
7273
protected ObservableValidator()
7374
{
7475
this.validationContext = new ValidationContext(this);
@@ -80,6 +81,7 @@ protected ObservableValidator()
8081
/// be used to validate all properties, which will reference the current instance.
8182
/// </summary>
8283
/// <param name="items">A set of key/value pairs to make available to consumers.</param>
84+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
8385
protected ObservableValidator(IDictionary<object, object?>? items)
8486
{
8587
this.validationContext = new ValidationContext(this, items);
@@ -92,6 +94,7 @@ protected ObservableValidator(IDictionary<object, object?>? items)
9294
/// </summary>
9395
/// <param name="serviceProvider">An <see cref="IServiceProvider"/> instance to make available during validation.</param>
9496
/// <param name="items">A set of key/value pairs to make available to consumers.</param>
97+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
9598
protected ObservableValidator(IServiceProvider? serviceProvider, IDictionary<object, object?>? items)
9699
{
97100
this.validationContext = new ValidationContext(this, serviceProvider, items);
@@ -141,6 +144,7 @@ protected ObservableValidator(ValidationContext validationContext)
141144
/// are not raised if the current and new value for the target property are the same.
142145
/// </remarks>
143146
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="propertyName"/> is <see langword="null"/>.</exception>
147+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
144148
protected bool SetProperty<T>([NotNullIfNotNull("newValue")] ref T field, T newValue, bool validate, [CallerMemberName] string propertyName = null!)
145149
{
146150
ArgumentNullException.ThrowIfNull(propertyName);
@@ -169,6 +173,7 @@ protected bool SetProperty<T>([NotNullIfNotNull("newValue")] ref T field, T newV
169173
/// <param name="propertyName">(optional) The name of the property that changed.</param>
170174
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
171175
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
176+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
172177
protected bool SetProperty<T>([NotNullIfNotNull("newValue")] ref T field, T newValue, IEqualityComparer<T> comparer, bool validate, [CallerMemberName] string propertyName = null!)
173178
{
174179
ArgumentNullException.ThrowIfNull(comparer);
@@ -205,6 +210,7 @@ protected bool SetProperty<T>([NotNullIfNotNull("newValue")] ref T field, T newV
205210
/// are not raised if the current and new value for the target property are the same.
206211
/// </remarks>
207212
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
213+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
208214
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool validate, [CallerMemberName] string propertyName = null!)
209215
{
210216
ArgumentNullException.ThrowIfNull(callback);
@@ -235,6 +241,7 @@ protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool v
235241
/// <param name="propertyName">(optional) The name of the property that changed.</param>
236242
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
237243
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
244+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
238245
protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, bool validate, [CallerMemberName] string propertyName = null!)
239246
{
240247
ArgumentNullException.ThrowIfNull(comparer);
@@ -269,6 +276,7 @@ protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> compa
269276
/// <param name="propertyName">(optional) The name of the property that changed.</param>
270277
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
271278
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="model"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
279+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
272280
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string propertyName = null!)
273281
where TModel : class
274282
{
@@ -306,6 +314,7 @@ protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Acti
306314
/// <param name="propertyName">(optional) The name of the property that changed.</param>
307315
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
308316
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/>, <paramref name="model"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
317+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
309318
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string propertyName = null!)
310319
where TModel : class
311320
{
@@ -335,6 +344,7 @@ protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<
335344
/// <param name="propertyName">(optional) The name of the property that changed.</param>
336345
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
337346
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="propertyName"/> is <see langword="null"/>.</exception>
347+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
338348
protected bool TrySetProperty<T>(ref T field, T newValue, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
339349
{
340350
ArgumentNullException.ThrowIfNull(propertyName);
@@ -355,6 +365,7 @@ protected bool TrySetProperty<T>(ref T field, T newValue, out IReadOnlyCollectio
355365
/// <param name="propertyName">(optional) The name of the property that changed.</param>
356366
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
357367
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
368+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
358369
protected bool TrySetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
359370
{
360371
ArgumentNullException.ThrowIfNull(comparer);
@@ -376,6 +387,7 @@ protected bool TrySetProperty<T>(ref T field, T newValue, IEqualityComparer<T> c
376387
/// <param name="propertyName">(optional) The name of the property that changed.</param>
377388
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
378389
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
390+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
379391
protected bool TrySetProperty<T>(T oldValue, T newValue, Action<T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
380392
{
381393
ArgumentNullException.ThrowIfNull(callback);
@@ -398,6 +410,7 @@ protected bool TrySetProperty<T>(T oldValue, T newValue, Action<T> callback, out
398410
/// <param name="propertyName">(optional) The name of the property that changed.</param>
399411
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
400412
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
413+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
401414
protected bool TrySetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
402415
{
403416
ArgumentNullException.ThrowIfNull(comparer);
@@ -422,6 +435,7 @@ protected bool TrySetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> co
422435
/// <param name="propertyName">(optional) The name of the property that changed.</param>
423436
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
424437
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="model"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
438+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
425439
protected bool TrySetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
426440
where TModel : class
427441
{
@@ -448,6 +462,7 @@ protected bool TrySetProperty<TModel, T>(T oldValue, T newValue, TModel model, A
448462
/// <param name="propertyName">(optional) The name of the property that changed.</param>
449463
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
450464
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="comparer"/>, <paramref name="model"/>, <paramref name="callback"/> or <paramref name="propertyName"/> are <see langword="null"/>.</exception>
465+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
451466
protected bool TrySetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string propertyName = null!)
452467
where TModel : class
453468
{
@@ -523,8 +538,14 @@ IEnumerable<ValidationResult> GetAllErrors()
523538
/// members in the current instance will be ignored. None of the processed properties
524539
/// will be modified - they will only be used to retrieve their values and validate them.
525540
/// </remarks>
541+
[RequiresUnreferencedCode(
542+
"This method requires the generated CommunityToolkit.Mvvm.ComponentModel.__Internals.__ObservableValidatorExtensions type not to be removed to use the fast path. " +
543+
"If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
544+
"path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type. " +
545+
"Additionally, due to the usage of validation APIs, the type of the current instance cannot be statically discovered.")]
526546
protected void ValidateAllProperties()
527547
{
548+
#pragma warning disable IL2026
528549
// Fast path that tries to create a delegate from a generated type-specific method. This
529550
// is used to make this method more AOT-friendly and faster, as there is no dynamic code.
530551
static Action<object> GetValidationAction(Type type)
@@ -537,6 +558,7 @@ static Action<object> GetValidationAction(Type type)
537558

538559
return GetValidationActionFallback(type);
539560
}
561+
#pragma warning restore IL2026
540562

541563
// Fallback method to create the delegate with a compiled LINQ expression
542564
static Action<object> GetValidationActionFallback(Type type)
@@ -603,6 +625,7 @@ from property in validatableProperties
603625
/// <param name="value">The value to test for the specified property.</param>
604626
/// <param name="propertyName">The name of the property to validate.</param>
605627
/// <exception cref="ArgumentNullException">Thrown when <paramref name="propertyName"/> is <see langword="null"/>.</exception>
628+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
606629
protected internal void ValidateProperty(object? value, [CallerMemberName] string propertyName = null!)
607630
{
608631
ArgumentNullException.ThrowIfNull(propertyName);
@@ -679,6 +702,7 @@ protected internal void ValidateProperty(object? value, [CallerMemberName] strin
679702
/// <param name="value">The value to test for the specified property.</param>
680703
/// <param name="propertyName">The name of the property to validate.</param>
681704
/// <param name="errors">The resulting validation errors, if any.</param>
705+
[RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
682706
private bool TryValidateProperty(object? value, string propertyName, out IReadOnlyCollection<ValidationResult> errors)
683707
{
684708
// Add the cached errors list for later use.

CommunityToolkit.Mvvm/ComponentModel/__Internals/__ObservableValidatorHelper.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public static class __ObservableValidatorHelper
2525
[Obsolete("This method is not intended to be called directly by user code")]
2626
public static void ValidateProperty(ObservableValidator instance, object? value, string propertyName)
2727
{
28+
#pragma warning disable IL2026
2829
instance.ValidateProperty(value, propertyName);
30+
#pragma warning restore IL2026
2931
}
3032
}

0 commit comments

Comments
 (0)