From 86e8f6a1eccebb0b15c8918595e675b73eaa7d91 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Fri, 19 Apr 2024 16:43:37 -0700 Subject: [PATCH] Conditionally-included polyfills for language feature support in netstandard2.0 --- .../CompilerFeatureRequiredAttribute.cs | 33 +++ .../Compatibility/IEqualityOperators.cs | 11 + .../Compatibility/IntrinsicAttribute.cs | 6 + .../Compatibility/IsExternalInit.cs | 18 ++ .../Compatibility/NullableAttributes.cs | 208 ++++++++++++++++++ .../Compatibility/NumericExtensions.cs | 43 ++++ .../Compatibility/RequiredMemberAttribute.cs | 12 + .../SetsRequiredMembersAttribute.cs | 12 + .../Compatibility/SkipLocalsInitAttribute.cs | 14 ++ 9 files changed, 357 insertions(+) create mode 100644 Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/CompilerFeatureRequiredAttribute.cs create mode 100644 Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IEqualityOperators.cs create mode 100644 Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IntrinsicAttribute.cs create mode 100644 Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IsExternalInit.cs create mode 100644 Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/NullableAttributes.cs create mode 100644 Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/NumericExtensions.cs create mode 100644 Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/RequiredMemberAttribute.cs create mode 100644 Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/SetsRequiredMembersAttribute.cs create mode 100644 Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/SkipLocalsInitAttribute.cs diff --git a/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/CompilerFeatureRequiredAttribute.cs b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/CompilerFeatureRequiredAttribute.cs new file mode 100644 index 000000000..cefddff94 --- /dev/null +++ b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/CompilerFeatureRequiredAttribute.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices; + +/// +/// Indicates that compiler support for a particular feature is required for the location where this attribute is +/// applied. +/// +[AttributeUsage (AttributeTargets.All, AllowMultiple = true, Inherited = false)] +internal sealed class CompilerFeatureRequiredAttribute(string featureName) : Attribute +{ + /// + /// The used for the ref structs C# feature. + /// + public const string RefStructs = nameof (RefStructs); + + /// + /// The used for the required members C# feature. + /// + public const string RequiredMembers = nameof (RequiredMembers); + + /// + /// The name of the compiler feature. + /// + public string FeatureName { get; } = featureName; + /// + /// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not + /// understand . + /// + public bool IsOptional { get; init; } +} \ No newline at end of file diff --git a/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IEqualityOperators.cs b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IEqualityOperators.cs new file mode 100644 index 000000000..63493a738 --- /dev/null +++ b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IEqualityOperators.cs @@ -0,0 +1,11 @@ +// ReSharper disable once CheckNamespace +namespace System.Numerics; +/// +/// Included for compatibility with .net7+, but has no members. +/// Thus it cannot be explicitly used in generator code. +/// Use it for static analysis only. +/// +/// The left operand type. +/// The right operand type. +/// The return type. +internal interface IEqualityOperators; \ No newline at end of file diff --git a/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IntrinsicAttribute.cs b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IntrinsicAttribute.cs new file mode 100644 index 000000000..06cd5b3d5 --- /dev/null +++ b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IntrinsicAttribute.cs @@ -0,0 +1,6 @@ +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Field, Inherited = false)] +public sealed class IntrinsicAttribute : Attribute +{ +} diff --git a/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IsExternalInit.cs b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IsExternalInit.cs new file mode 100644 index 000000000..4976ee705 --- /dev/null +++ b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/IsExternalInit.cs @@ -0,0 +1,18 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +// ReSharper disable CheckNamespace +namespace System.Runtime.CompilerServices; + +/// +/// Reserved to be used by the compiler for tracking metadata. +/// This class should not be used by developers in source code. +/// +/// +/// Copied from .net source code, for support of init property accessors in netstandard2.0. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[EditorBrowsable (EditorBrowsableState.Never)] +public static class IsExternalInit; diff --git a/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/NullableAttributes.cs b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/NullableAttributes.cs new file mode 100644 index 000000000..11ef7e4cf --- /dev/null +++ b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/NullableAttributes.cs @@ -0,0 +1,208 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +// This file is further modified from the original, for this project, +// to comply with project style. +// No changes are made which affect compatibility with the same types from +// APIs later than netstandard2.0, nor will this file be included in compilations +// targeted at later APIs. +// +// Originally rom https://github.com/dotnet/runtime/blob/ef72b95937703e485fdbbb75f3251fedfd1a0ef9/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs + +// ReSharper disable CheckNamespace + +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable UnusedType.Global + +namespace System.Diagnostics.CodeAnalysis; + +/// Specifies that null is allowed as an input even if the corresponding type disallows it. +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class AllowNullAttribute : Attribute; + +/// Specifies that null is disallowed as an input even if the corresponding type allows it. +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class DisallowNullAttribute : Attribute; + +/// Specifies that an output may be null even if the corresponding type disallows it. +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class MaybeNullAttribute : Attribute; + +/// +/// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input +/// argument was not null when the call returns. +/// +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class NotNullAttribute : Attribute; + +/// +/// Specifies that when a method returns , the parameter may be null even if the corresponding +/// type disallows it. +/// +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Parameter)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class MaybeNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// +#pragma warning disable IDE0290 // Use primary constructor + public MaybeNullWhenAttribute (bool returnValue) { ReturnValue = returnValue; } +#pragma warning restore IDE0290 // Use primary constructor + + /// Gets the return value condition. + public bool ReturnValue { get; } +} + +/// +/// Specifies that when a method returns , the parameter will not be null even if the +/// corresponding type allows it. +/// +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Parameter)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class NotNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// +#pragma warning disable IDE0290 // Use primary constructor + public NotNullWhenAttribute (bool returnValue) { ReturnValue = returnValue; } +#pragma warning restore IDE0290 // Use primary constructor + + /// Gets the return value condition. + public bool ReturnValue { get; } +} + +/// Specifies that the output will be non-null if the named parameter is non-null. +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class NotNullIfNotNullAttribute : Attribute +{ + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// +#pragma warning disable IDE0290 // Use primary constructor + public NotNullIfNotNullAttribute (string parameterName) { ParameterName = parameterName; } +#pragma warning restore IDE0290 // Use primary constructor + + /// Gets the associated parameter name. + public string ParameterName { get; } +} + +/// Applied to a method that will never return under any circumstance. +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Method, Inherited = false)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class DoesNotReturnAttribute : Attribute; + +/// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Parameter)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class DoesNotReturnIfAttribute : Attribute +{ + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument + /// to + /// the associated parameter matches this value. + /// +#pragma warning disable IDE0290 // Use primary constructor + public DoesNotReturnIfAttribute (bool parameterValue) { ParameterValue = parameterValue; } +#pragma warning restore IDE0290 // Use primary constructor + + /// Gets the condition parameter value. + public bool ParameterValue { get; } +} + +/// +/// Specifies that the method or property will ensure that the listed field and property members have not-null +/// values. +/// +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class MemberNotNullAttribute : Attribute +{ + /// Initializes the attribute with a field or property member. + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullAttribute (string member) { Members = [member]; } + + /// Initializes the attribute with the list of field and property members. + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullAttribute (params string [] members) { Members = members; } + + /// Gets field or property member names. + public string [] Members { get; } +} + +/// +/// Specifies that the method or property will ensure that the listed field and property members have not-null values +/// when returning with the specified return value condition. +/// +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +internal sealed class MemberNotNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullWhenAttribute (bool returnValue, string member) + { + ReturnValue = returnValue; + Members = [member]; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute (bool returnValue, params string [] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// Gets field or property member names. + public string [] Members { get; } + + /// Gets the return value condition. + public bool ReturnValue { get; } +} diff --git a/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/NumericExtensions.cs b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/NumericExtensions.cs new file mode 100644 index 000000000..8a6df7be9 --- /dev/null +++ b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/NumericExtensions.cs @@ -0,0 +1,43 @@ +// ReSharper disable once CheckNamespace +namespace Terminal.Gui.Analyzers.Internal.Compatibility; + +/// +/// Extension methods for and types. +/// +/// +/// This is mostly just for backward compatibility with netstandard2.0. +/// +public static class NumericExtensions +{ + /// + /// Gets the population count (number of bits set to 1) of this 32-bit value. + /// + /// The value to get the population count of. + /// + /// The algorithm is the well-known SWAR (SIMD Within A Register) method for population count.
+ /// Included for hardware- and runtime- agnostic support for the equivalent of the x86 popcnt instruction, since + /// System.Numerics.Intrinsics isn't available in netstandard2.0.
+ /// It performs the operation simultaneously on 4 bytes at a time, rather than the naive method of testing all 32 bits + /// individually.
+ /// Most compilers can recognize this and turn it into a single platform-specific instruction, when available. + ///
+ /// + /// An unsigned 32-bit integer value containing the population count of . + /// + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public static uint GetPopCount (this uint value) + { + unchecked + { + value -= (value >> 1) & 0x55555555; + value = (value & 0x33333333) + ((value >> 2) & 0x33333333); + value = (value + (value >> 4)) & 0x0F0F0F0F; + + return (value * 0x01010101) >> 24; + } + } + + /// + [MethodImpl (MethodImplOptions.AggressiveInlining)] + public static uint GetPopCount (this int value) { return GetPopCount (Unsafe.As (ref value)); } +} diff --git a/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/RequiredMemberAttribute.cs b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/RequiredMemberAttribute.cs new file mode 100644 index 000000000..2fb8d3841 --- /dev/null +++ b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/RequiredMemberAttribute.cs @@ -0,0 +1,12 @@ +// ReSharper disable CheckNamespace +// ReSharper disable ConditionalAnnotation + +using JetBrains.Annotations; + +namespace System.Runtime.CompilerServices; + +/// Polyfill to enable netstandard2.0 assembly to use the required keyword. +/// Excluded from output assembly via file specified in ApiCompatExcludeAttributesFile element in the project file. +[AttributeUsage (AttributeTargets.Property)] +[UsedImplicitly] +public sealed class RequiredMemberAttribute : Attribute; diff --git a/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/SetsRequiredMembersAttribute.cs b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/SetsRequiredMembersAttribute.cs new file mode 100644 index 000000000..ae09ae2cf --- /dev/null +++ b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/SetsRequiredMembersAttribute.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable once CheckNamespace +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Specifies that this constructor sets all required members for the current type, and callers +/// do not need to set any required members themselves. +/// +[AttributeUsage (AttributeTargets.Constructor)] +public sealed class SetsRequiredMembersAttribute : Attribute; \ No newline at end of file diff --git a/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/SkipLocalsInitAttribute.cs b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/SkipLocalsInitAttribute.cs new file mode 100644 index 000000000..1eb617362 --- /dev/null +++ b/Analyzers/Terminal.Gui.Analyzers.Internal/Compatibility/SkipLocalsInitAttribute.cs @@ -0,0 +1,14 @@ +namespace System.Runtime.CompilerServices; + +[AttributeUsage ( + AttributeTargets.Class + | AttributeTargets.Constructor + | AttributeTargets.Event + | AttributeTargets.Interface + | AttributeTargets.Method + | AttributeTargets.Module + | AttributeTargets.Property + | AttributeTargets.Struct, + Inherited = false)] + +internal sealed class SkipLocalsInitAttribute : Attribute; \ No newline at end of file