@@ -32,6 +32,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
3232 . Where ( pair => pair . Name is { Length : > 0 } )
3333 . Collect ( ) ;
3434
35+ // looking for [AsciiHash] partial static bool TryFormat(enum input, out string/ReadOnlySpan<byte> output) { }
36+ var formatMethods = context . SyntaxProvider
37+ . CreateSyntaxProvider (
38+ static ( node , _ ) => node is MethodDeclarationSyntax decl && IsStaticPartial ( decl . Modifiers ) &&
39+ HasAsciiHash ( decl . AttributeLists ) ,
40+ TransformFormatMethods )
41+ . Where ( pair => pair . Name is { Length : > 0 } )
42+ . Collect ( ) ;
43+
3544 // looking for [AsciiHash("some type")] enum Foo { }
3645 var enums = context . SyntaxProvider
3746 . CreateSyntaxProvider (
@@ -41,9 +50,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
4150 . Collect ( ) ;
4251
4352 context . RegisterSourceOutput (
44- types . Combine ( methods ) . Combine ( enums ) ,
53+ types . Combine ( methods ) . Combine ( formatMethods ) . Combine ( enums ) ,
4554 ( ctx , content ) =>
46- Generate ( ctx , content . Left . Left , content . Left . Right , content . Right ) ) ;
55+ Generate ( ctx , content . Left . Left . Left , content . Left . Left . Right , content . Left . Right , content . Right ) ) ;
4756
4857 static bool IsStaticPartial ( SyntaxTokenList tokens )
4958 => tokens . Any ( SyntaxKind . StaticKeyword ) && tokens . Any ( SyntaxKind . PartialKeyword ) ;
@@ -309,6 +318,80 @@ static bool IsBytes(ITypeSymbol type)
309318 return ( ns , parentType , method . DeclaredAccessibility , method . Name , from , to , caseSensitive , builder . Build ( ) , defaultValue ) ;
310319 }
311320
321+ private ( string Namespace , string ParentType , Accessibility Accessibility , string Name ,
322+ ( string Type , string Name , RefKind RefKind ) From , ( string Type , string Name , RefKind RefKind , bool IsBytes ) To ,
323+ BasicArray < ( string EnumMember , string FormatText ) > Members ) TransformFormatMethods (
324+ GeneratorSyntaxContext ctx ,
325+ CancellationToken cancellationToken )
326+ {
327+ if ( ctx . SemanticModel . GetDeclaredSymbol ( ctx . Node ) is not IMethodSymbol
328+ {
329+ IsStatic : true ,
330+ IsPartialDefinition : true ,
331+ PartialImplementationPart : null ,
332+ Arity : 0 ,
333+ ReturnType . SpecialType : SpecialType . System_Boolean ,
334+ Parameters :
335+ {
336+ IsDefaultOrEmpty : false ,
337+ Length : 2 ,
338+ } ,
339+ } method ) return default ;
340+
341+ if ( TryGetAsciiHashAttribute ( method . GetAttributes ( ) ) is not { } ) return default ;
342+
343+ if ( method . ContainingType is not { } containingType ) return default ;
344+ var parentType = GetName ( containingType ) ;
345+ var ns = containingType . ContainingNamespace . ToDisplayString ( SymbolDisplayFormat . CSharpErrorMessageFormat ) ;
346+
347+ var arg = method . Parameters [ 0 ] ;
348+ if ( arg is not
349+ {
350+ IsOptional : false ,
351+ RefKind : RefKind . None or RefKind . In or RefKind . Ref or RefKind . RefReadOnlyParameter ,
352+ Type : INamedTypeSymbol { TypeKind : TypeKind . Enum } ,
353+ } ) return default ;
354+ var from = ( arg . Type . ToDisplayString ( SymbolDisplayFormat . CSharpErrorMessageFormat ) , arg . Name , arg . RefKind ) ;
355+
356+ var enumMembers = arg . Type . GetMembers ( ) ;
357+ var builder = new BasicArray < ( string EnumMember , string FormatText ) > . Builder ( enumMembers . Length ) ;
358+ HashSet < object > values = new ( ) ;
359+ foreach ( var member in enumMembers )
360+ {
361+ if ( member is IFieldSymbol { IsStatic : true , IsConst : true } field )
362+ {
363+ var rawValue = GetRawValue ( field . Name , TryGetAsciiHashAttribute ( member . GetAttributes ( ) ) ) ;
364+ if ( string . IsNullOrWhiteSpace ( rawValue ) ) continue ;
365+ if ( field . ConstantValue is { } constValue && ! values . Add ( constValue ) ) continue ;
366+ builder . Add ( ( field . Name , rawValue ) ) ;
367+ }
368+ }
369+
370+ arg = method . Parameters [ 1 ] ;
371+ if ( arg is not
372+ {
373+ IsOptional : false ,
374+ RefKind : RefKind . Out ,
375+ } ) return default ;
376+ bool toBytes = IsReadOnlySpanOfByte ( arg . Type ) ;
377+ if ( arg . Type . SpecialType != SpecialType . System_String && ! toBytes ) return default ;
378+ var to = ( arg . Type . ToDisplayString ( SymbolDisplayFormat . CSharpErrorMessageFormat ) , arg . Name , arg . RefKind , toBytes ) ;
379+
380+ return ( ns , parentType , method . DeclaredAccessibility , method . Name , from , to , builder . Build ( ) ) ;
381+
382+ static bool IsReadOnlySpanOfByte ( ITypeSymbol type )
383+ {
384+ return type is INamedTypeSymbol
385+ {
386+ TypeKind : TypeKind . Struct ,
387+ Arity : 1 ,
388+ Name : "ReadOnlySpan" ,
389+ ContainingNamespace : { Name : "System" , ContainingNamespace . IsGlobalNamespace : true } ,
390+ TypeArguments : { Length : 1 } typeArguments ,
391+ } && typeArguments [ 0 ] . SpecialType == SpecialType . System_Byte ;
392+ }
393+ }
394+
312395 private bool IsCaseSensitive ( AttributeData attrib )
313396 {
314397 foreach ( var member in attrib . NamedArguments )
@@ -343,9 +426,13 @@ private void Generate(
343426 ( string Type , string Name , bool IsBytes , RefKind RefKind ) From , ( string Type , string Name , RefKind RefKind ) To ,
344427 ( string Name , bool Value , RefKind RefKind ) CaseSensitive ,
345428 BasicArray < ( string EnumMember , string ParseText ) > Members , int DefaultValue ) > parseMethods ,
429+ ImmutableArray < ( string Namespace , string ParentType , Accessibility Accessibility , string Name ,
430+ ( string Type , string Name , RefKind RefKind ) From ,
431+ ( string Type , string Name , RefKind RefKind , bool IsBytes ) To ,
432+ BasicArray < ( string EnumMember , string FormatText ) > Members ) > formatMethods ,
346433 ImmutableArray < ( string Namespace , string ParentType , string Name , int Count , int MaxChars , int MaxBytes ) > enums )
347434 {
348- if ( types . IsDefaultOrEmpty & parseMethods . IsDefaultOrEmpty & enums . IsDefaultOrEmpty ) return ; // nothing to do
435+ if ( types . IsDefaultOrEmpty & parseMethods . IsDefaultOrEmpty & formatMethods . IsDefaultOrEmpty & enums . IsDefaultOrEmpty ) return ; // nothing to do
349436
350437 var sb = new StringBuilder ( "// <auto-generated />" )
351438 . AppendLine ( ) . Append ( "// " ) . Append ( GetType ( ) . Name ) . Append ( " v" ) . Append ( GetVersion ( ) ) . AppendLine ( ) ;
@@ -356,6 +443,7 @@ private void Generate(
356443
357444 BuildTypeImplementations ( sb , types ) ;
358445 BuildEnumParsers ( sb , parseMethods ) ;
446+ BuildEnumFormatters ( sb , formatMethods ) ;
359447 BuildEnumLengths ( sb , enums ) ;
360448 ctx . AddSource ( nameof ( AsciiHash ) + ".generated.cs" , sb . ToString ( ) ) ;
361449 }
@@ -644,6 +732,102 @@ void Write(bool caseSensitive)
644732 }
645733 }
646734
735+ private void BuildEnumFormatters (
736+ StringBuilder sb ,
737+ in ImmutableArray < ( string Namespace , string ParentType , Accessibility Accessibility , string Name ,
738+ ( string Type , string Name , RefKind RefKind ) From ,
739+ ( string Type , string Name , RefKind RefKind , bool IsBytes ) To ,
740+ BasicArray < ( string EnumMember , string FormatText ) > Members ) > enums )
741+ {
742+ if ( enums . IsDefaultOrEmpty ) return ; // nope
743+
744+ int indent = 0 ;
745+ StringBuilder NewLine ( ) => sb . AppendLine ( ) . Append ( ' ' , indent * 4 ) ;
746+
747+ foreach ( var grp in enums . GroupBy ( l => ( l . Namespace , l . ParentType ) ) )
748+ {
749+ NewLine ( ) ;
750+ int braces = 0 ;
751+ if ( ! string . IsNullOrWhiteSpace ( grp . Key . Namespace ) )
752+ {
753+ NewLine ( ) . Append ( "namespace " ) . Append ( grp . Key . Namespace ) ;
754+ NewLine ( ) . Append ( "{" ) ;
755+ indent ++ ;
756+ braces ++ ;
757+ }
758+
759+ if ( ! string . IsNullOrWhiteSpace ( grp . Key . ParentType ) )
760+ {
761+ if ( grp . Key . ParentType . Contains ( '.' ) ) // nested types
762+ {
763+ foreach ( var part in grp . Key . ParentType . Split ( '.' ) )
764+ {
765+ NewLine ( ) . Append ( "partial class " ) . Append ( part ) ;
766+ NewLine ( ) . Append ( "{" ) ;
767+ indent ++ ;
768+ braces ++ ;
769+ }
770+ }
771+ else
772+ {
773+ NewLine ( ) . Append ( "partial class " ) . Append ( grp . Key . ParentType ) ;
774+ NewLine ( ) . Append ( "{" ) ;
775+ indent ++ ;
776+ braces ++ ;
777+ }
778+ }
779+
780+ foreach ( var method in grp )
781+ {
782+ NewLine ( ) . Append ( Format ( method . Accessibility ) ) . Append ( " static partial bool " )
783+ . Append ( method . Name ) . Append ( "(" )
784+ . Append ( Format ( method . From . RefKind ) )
785+ . Append ( method . From . Type ) . Append ( " " ) . Append ( method . From . Name ) . Append ( ", " )
786+ . Append ( Format ( method . To . RefKind ) )
787+ . Append ( method . To . Type ) . Append ( " " ) . Append ( method . To . Name )
788+ . Append ( ")" ) ;
789+
790+ NewLine ( ) . Append ( "{" ) ;
791+ indent ++ ;
792+ NewLine ( ) . Append ( "// " ) . Append ( method . From . Type ) . Append ( " has " ) . Append ( method . Members . Length ) . Append ( " formatted members" ) ;
793+ NewLine ( ) . Append ( "switch (" ) . Append ( method . From . Name ) . Append ( ")" ) ;
794+ NewLine ( ) . Append ( "{" ) ;
795+ indent ++ ;
796+
797+ foreach ( var member in method . Members )
798+ {
799+ var formatted = SyntaxFactory
800+ . LiteralExpression ( SyntaxKind . StringLiteralExpression , SyntaxFactory . Literal ( member . FormatText ) )
801+ . ToFullString ( ) ;
802+ if ( method . To . IsBytes ) formatted += "u8" ;
803+
804+ NewLine ( ) . Append ( "case " ) . Append ( method . From . Type ) . Append ( "." ) . Append ( member . EnumMember ) . Append ( ":" ) ;
805+ indent ++ ;
806+ NewLine ( ) . Append ( method . To . Name ) . Append ( " = " ) . Append ( formatted ) . Append ( ";" ) ;
807+ NewLine ( ) . Append ( "return true;" ) ;
808+ indent -- ;
809+ }
810+
811+ NewLine ( ) . Append ( "default:" ) ;
812+ indent ++ ;
813+ NewLine ( ) . Append ( method . To . Name ) . Append ( " = " ) . Append ( method . To . IsBytes ? "default" : "default!" ) . Append ( ";" ) ;
814+ NewLine ( ) . Append ( "return false;" ) ;
815+ indent -- ;
816+ indent -- ;
817+ NewLine ( ) . Append ( "}" ) ;
818+ indent -- ;
819+ NewLine ( ) . Append ( "}" ) ;
820+ }
821+
822+ // handle any closing braces
823+ while ( braces -- > 0 )
824+ {
825+ indent -- ;
826+ NewLine ( ) . Append ( "}" ) ;
827+ }
828+ }
829+ }
830+
647831 private static bool HasCaseSensitiveCharacters ( string value )
648832 {
649833 foreach ( char c in value ?? "" )
0 commit comments