diff --git a/CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs b/CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs index 24484ee96..a39af6343 100644 --- a/CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs +++ b/CommunityToolkit.Mvvm/ComponentModel/ObservableValidator.cs @@ -545,9 +545,9 @@ IEnumerable GetAllErrors() "Additionally, due to the usage of validation APIs, the type of the current instance cannot be statically discovered.")] protected void ValidateAllProperties() { -#pragma warning disable IL2026 // Fast path that tries to create a delegate from a generated type-specific method. This // is used to make this method more AOT-friendly and faster, as there is no dynamic code. + [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")] static Action GetValidationAction(Type type) { if (type.Assembly.GetType("CommunityToolkit.Mvvm.ComponentModel.__Internals.__ObservableValidatorExtensions") is Type extensionsType && @@ -558,7 +558,6 @@ static Action GetValidationAction(Type type) return GetValidationActionFallback(type); } -#pragma warning restore IL2026 // Fallback method to create the delegate with a compiled LINQ expression static Action GetValidationActionFallback(Type type) @@ -615,7 +614,9 @@ from property in validatableProperties // Get or compute the cached list of properties to validate. Here we're using a static lambda to ensure the // delegate is cached by the C# compiler, see the related issue at https://github.com/dotnet/roslyn/issues/5835. - EntityValidatorMap.GetValue(GetType(), static t => GetValidationAction(t))(this); + EntityValidatorMap.GetValue( + GetType(), + [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")] static (t) => GetValidationAction(t))(this); } /// diff --git a/CommunityToolkit.Mvvm/ComponentModel/__Internals/__ObservableValidatorHelper.cs b/CommunityToolkit.Mvvm/ComponentModel/__Internals/__ObservableValidatorHelper.cs index c4398436c..cddca2c73 100644 --- a/CommunityToolkit.Mvvm/ComponentModel/__Internals/__ObservableValidatorHelper.cs +++ b/CommunityToolkit.Mvvm/ComponentModel/__Internals/__ObservableValidatorHelper.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; namespace CommunityToolkit.Mvvm.ComponentModel.__Internals; @@ -23,10 +24,12 @@ public static class __ObservableValidatorHelper /// The name of the property to validate. [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("This method is not intended to be called directly by user code")] + [UnconditionalSuppressMessage( + "ReflectionAnalysis", + "IL2026:RequiresUnreferencedCode", + Justification = "This helper is called by generated code from public APIs that have the proper annotations already (and we don't want generated code to produce warnings that developers cannot fix).")] public static void ValidateProperty(ObservableValidator instance, object? value, string propertyName) { -#pragma warning disable IL2026 instance.ValidateProperty(value, propertyName); -#pragma warning restore IL2026 } } diff --git a/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs b/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs index 406e26926..2163efd86 100644 --- a/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs +++ b/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs @@ -95,10 +95,10 @@ public static void RegisterAll(this IMessenger messenger, object recipient) ArgumentNullException.ThrowIfNull(messenger); ArgumentNullException.ThrowIfNull(recipient); -#pragma warning disable IL2026 // We use this method as a callback for the conditional weak table, which will handle // thread-safety for us. This first callback will try to find a generated method for the // target recipient type, and just invoke it to get the delegate to cache and use later. + [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")] static Action? LoadRegistrationMethodsForType(Type recipientType) { if (recipientType.Assembly.GetType("CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions") is Type extensionsType && @@ -109,12 +109,11 @@ public static void RegisterAll(this IMessenger messenger, object recipient) return null; } -#pragma warning restore IL2026 // Try to get the cached delegate, if the generator has run correctly Action? registrationAction = DiscoveredRecipients.RegistrationMethods.GetValue( recipient.GetType(), - static t => LoadRegistrationMethodsForType(t)); + [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")] static (t) => LoadRegistrationMethodsForType(t)); if (registrationAction is not null) { @@ -152,11 +151,11 @@ public static void RegisterAll(this IMessenger messenger, object recipie ArgumentNullException.ThrowIfNull(recipient); ArgumentNullException.For.ThrowIfNull(token); -#pragma warning disable IL2026 // We use this method as a callback for the conditional weak table, which will handle // thread-safety for us. This first callback will try to find a generated method for the // target recipient type, and just invoke it to get the delegate to cache and use later. // In this case we also need to create a generic instantiation of the target method first. + [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")] static Action LoadRegistrationMethodsForType(Type recipientType) { if (recipientType.Assembly.GetType("CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions") is Type extensionsType && @@ -224,7 +223,6 @@ from registrationMethod in registrationMethods return Expression.Lambda>(body, arg0, arg1, arg2).Compile(); } -#pragma warning restore IL2026 // Get or compute the registration method for the current recipient type. // As in CommunityToolkit.Diagnostics.TypeExtensions.ToTypeString, we use a lambda @@ -233,7 +231,7 @@ from registrationMethod in registrationMethods // For more info on this, see the related issue at https://github.com/dotnet/roslyn/issues/5835. Action registrationAction = DiscoveredRecipients.RegistrationMethods.GetValue( recipient.GetType(), - static t => LoadRegistrationMethodsForType(t)); + [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")] static (t) => LoadRegistrationMethodsForType(t)); // Invoke the cached delegate to actually execute the message registration registrationAction(messenger, recipient, token); diff --git a/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/UnconditionalSuppressMessageAttribute.cs b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/UnconditionalSuppressMessageAttribute.cs new file mode 100644 index 000000000..15ac11371 --- /dev/null +++ b/CommunityToolkit.Mvvm/Properties/Polyfills/Attributes/UnconditionalSuppressMessageAttribute.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if !NET6_0_OR_GREATER + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a +/// single code artifact. +/// +/// +/// is different than +/// in that it doesn't have a +/// . So it is always preserved in the compiled assembly. +/// +[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] +[Conditional("DEBUG")] +internal sealed class UnconditionalSuppressMessageAttribute : Attribute +{ + /// + /// Initializes a new instance of the + /// class, specifying the category of the tool and the identifier for an analysis rule. + /// + /// The category for the attribute. + /// The identifier of the analysis rule the attribute applies to. + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + + /// + /// Gets the category identifying the classification of the attribute. + /// + /// + /// The property describes the tool or tool analysis category + /// for which a message suppression attribute applies. + /// + public string Category { get; } + + /// + /// Gets the identifier of the analysis tool rule to be suppressed. + /// + /// + /// Concatenated together, the and + /// properties form a unique check identifier. + /// + public string CheckId { get; } + + /// + /// Gets or sets the scope of the code that is relevant for the attribute. + /// + /// + /// The Scope property is an optional argument that specifies the metadata scope for which + /// the attribute is relevant. + /// + public string? Scope { get; set; } + + /// + /// Gets or sets a fully qualified path that represents the target of the attribute. + /// + /// + /// The property is an optional argument identifying the analysis target + /// of the attribute. An example value is "System.IO.Stream.ctor():System.Void". + /// Because it is fully qualified, it can be long, particularly for targets such as parameters. + /// The analysis tool user interface should be capable of automatically formatting the parameter. + /// + public string? Target { get; set; } + + /// + /// Gets or sets an optional argument expanding on exclusion criteria. + /// + /// + /// The property is an optional argument that specifies additional + /// exclusion where the literal metadata target is not sufficiently precise. For example, + /// the cannot be applied within a method, + /// and it may be desirable to suppress a violation against a statement in the method that will + /// give a rule violation, but not against all statements in the method. + /// + public string? MessageId { get; set; } + + /// + /// Gets or sets the justification for suppressing the code analysis message. + /// + public string? Justification { get; set; } +} + +#endif \ No newline at end of file