using System; using System.CodeDom.Compiler; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; using Microsoft.CodeAnalysis.Text; using Terminal.Gui.Analyzers.Internal.Constants; namespace Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions; /// /// The class responsible for turning an /// into actual C# code. /// /// Try to use this type as infrequently as possible. /// /// A reference to an which will be used /// to generate the extension class code. The object will not be validated, /// so it is critical that it be correct and remain unchanged while in use /// by an instance of this class. Behavior if those rules are not followed /// is undefined. /// [SuppressMessage ("CodeQuality", "IDE0079", Justification = "Suppressions here are intentional and the warnings they disable are just noise.")] internal sealed class CodeWriter (in EnumExtensionMethodsGenerationInfo metadata) : IStandardCSharpCodeGenerator { // Using the null suppression operator here because this will always be // initialized to non-null before a reference to it is returned. private SourceText _sourceText = null!; /// public EnumExtensionMethodsGenerationInfo Metadata { [MethodImpl (MethodImplOptions.AggressiveInlining)] [return: NotNull] get; [param: DisallowNull] set; } = metadata; /// public ref readonly SourceText GenerateSourceText (Encoding? encoding = null) { encoding ??= Encoding.UTF8; _sourceText = SourceText.From (GetFullSourceText (), encoding); return ref _sourceText; } /// /// Gets the using directive for the namespace containing the enum, /// if different from the extension class namespace, or an empty string, if they are the same. /// private string EnumNamespaceUsingDirective => Metadata.TargetTypeNamespace != Metadata.GeneratedTypeNamespace // ReSharper disable once HeapView.ObjectAllocation ? $"using {Metadata.TargetTypeNamespace};" : string.Empty; private string EnumTypeKeyword => Metadata.EnumBackingTypeCode switch { TypeCode.Int32 => "int", TypeCode.UInt32 => "uint", _ => string.Empty }; /// Gets the class declaration line. private string ExtensionClassDeclarationLine => $"public static partial class {Metadata.GeneratedTypeName}"; // ReSharper disable once HeapView.ObjectAllocation /// Gets the XmlDoc for the extension class declaration. private string ExtensionClassDeclarationXmlDoc => $"/// Extension methods for the type."; // ReSharper disable once HeapView.ObjectAllocation /// Gets the extension class file-scoped namespace directive. private string ExtensionClassNamespaceDirective => $"namespace {Metadata.GeneratedTypeNamespace};"; /// /// An attribute to decorate the extension class with for easy mapping back to the target enum type, for reflection and /// analysis. /// private string ExtensionsForTypeAttributeLine => $"[ExtensionsForEnumType<{Metadata.TargetTypeFullName}>]"; /// /// Creates the code for the FastHasFlags method. /// /// /// Since the generator already only writes code for enums backed by and , /// this method is safe, as we'll always be using a DWORD. /// /// An instance of an to write to. private void GetFastHasFlagsMethods (IndentedTextWriter w) { // The version taking the same enum type as the check value. w.WriteLine ( $"/// Determines if the specified flags are set in the current value of this ."); w.WriteLine ("/// NO VALIDATION IS PERFORMED!"); w.WriteLine ( $"/// True, if all flags present in are also present in the current value of the .
Otherwise false.
"); w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining); w.Push ( $"{Metadata.Accessibility.ToCSharpString ()} static bool FastHasFlags (this {Metadata.TargetTypeFullName} e, {Metadata.TargetTypeFullName} checkFlags)"); w.WriteLine ($"ref uint enumCurrentValueRef = ref Unsafe.As<{Metadata.TargetTypeFullName},uint> (ref e);"); w.WriteLine ($"ref uint checkFlagsValueRef = ref Unsafe.As<{Metadata.TargetTypeFullName},uint> (ref checkFlags);"); w.WriteLine ("return (enumCurrentValueRef & checkFlagsValueRef) == checkFlagsValueRef;"); w.Pop (); // The version taking the underlying type of the enum as the check value. w.WriteLine ( $"/// Determines if the specified mask bits are set in the current value of this ."); w.WriteLine ( $"/// The value to check against the value."); w.WriteLine ("/// A mask to apply to the current value."); w.WriteLine ( $"/// True, if all bits set to 1 in the mask are also set to 1 in the current value of the .
Otherwise false.
"); w.WriteLine ("/// NO VALIDATION IS PERFORMED!"); w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining); w.Push ( $"{Metadata.Accessibility.ToCSharpString ()} static bool FastHasFlags (this {Metadata.TargetTypeFullName} e, {EnumTypeKeyword} mask)"); w.WriteLine ($"ref {EnumTypeKeyword} enumCurrentValueRef = ref Unsafe.As<{Metadata.TargetTypeFullName},{EnumTypeKeyword}> (ref e);"); w.WriteLine ("return (enumCurrentValueRef & mask) == mask;"); w.Pop (); } /// /// Creates the code for the FastIsDefined method. /// [SuppressMessage ("ReSharper", "SwitchStatementHandlesSomeKnownEnumValuesWithDefault", Justification = "Only need to handle int and uint.")] [SuppressMessage ("ReSharper", "SwitchStatementMissingSomeEnumCasesNoDefault", Justification = "Only need to handle int and uint.")] private void GetFastIsDefinedMethod (IndentedTextWriter w) { w.WriteLine ( $"/// Determines if the specified value is explicitly defined as a named value of the type."); w.WriteLine ( "/// Only explicitly named values return true, as with IsDefined. Combined valid flag values of flags enums which are not explicitly named will return false."); w.Push ( $"{Metadata.Accessibility.ToCSharpString ()} static bool FastIsDefined (this {Metadata.TargetTypeFullName} e, {EnumTypeKeyword} value)"); w.Push ("return value switch"); switch (Metadata.EnumBackingTypeCode) { case TypeCode.Int32: foreach (int definedValue in Metadata._intMembers) { w.WriteLine ($"{definedValue:D} => true,"); } break; case TypeCode.UInt32: foreach (uint definedValue in Metadata._uIntMembers) { w.WriteLine ($"{definedValue:D} => true,"); } break; } w.WriteLine ("_ => false"); w.Pop ("};"); w.Pop (); } private string GetFullSourceText () { StringBuilder sb = new ( $""" {Strings.Templates.StandardHeader} [assembly: {Strings.AssemblyExtendedEnumTypeAttributeFullName} (typeof({Metadata.TargetTypeFullName}), typeof({Metadata.GeneratedTypeFullName}))] {EnumNamespaceUsingDirective} {ExtensionClassNamespaceDirective} {ExtensionClassDeclarationXmlDoc} {Strings.Templates.AttributesForGeneratedTypes} {ExtensionsForTypeAttributeLine} {ExtensionClassDeclarationLine} """, 4096); using IndentedTextWriter w = new (new StringWriter (sb)); w.Push (); GetNamedValuesToInt32Method (w); GetNamedValuesToUInt32Method (w); if (Metadata.GenerateFastIsDefined) { GetFastIsDefinedMethod (w); } if (Metadata.GenerateFastHasFlags) { GetFastHasFlagsMethods (w); } w.Pop (); w.Flush (); return sb.ToString (); } [MethodImpl (MethodImplOptions.AggressiveInlining)] private void GetNamedValuesToInt32Method (IndentedTextWriter w) { w.WriteLine ( $"/// Directly converts this value to an value with the same binary representation."); w.WriteLine ("/// NO VALIDATION IS PERFORMED!"); w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining); w.Push ($"{Metadata.Accessibility.ToCSharpString ()} static int AsInt32 (this {Metadata.TargetTypeFullName} e)"); w.WriteLine ($"return Unsafe.As<{Metadata.TargetTypeFullName},int> (ref e);"); w.Pop (); } [MethodImpl (MethodImplOptions.AggressiveInlining)] private void GetNamedValuesToUInt32Method (IndentedTextWriter w) { w.WriteLine ( $"/// Directly converts this value to a value with the same binary representation."); w.WriteLine ("/// NO VALIDATION IS PERFORMED!"); w.WriteLine (Strings.DotnetNames.Attributes.Applications.AggressiveInlining); w.Push ($"{Metadata.Accessibility.ToCSharpString ()} static uint AsUInt32 (this {Metadata.TargetTypeFullName} e)"); w.WriteLine ($"return Unsafe.As<{Metadata.TargetTypeFullName},uint> (ref e);"); w.Pop (); } }