1+ using System . Diagnostics ;
2+ using System . Globalization ;
3+ using System . Text . RegularExpressions ;
4+ using Microsoft . CodeAnalysis ;
5+ using Microsoft . CodeAnalysis . CSharp ;
6+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
7+ using Microsoft . Extensions . Options ;
8+ using static Microsoft . CodeAnalysis . CSharp . SyntaxFactory ;
9+
10+ namespace Silk . NET . SilkTouch . Mods ;
11+
12+ /// <summary>
13+ /// Contains various transformations related to enums.
14+ /// </summary>
15+ [ ModConfiguration < Configuration > ]
16+ public class TransformEnums ( IOptionsSnapshot < TransformEnums . Configuration > cfg ) : IMod
17+ {
18+ /// <summary>
19+ /// TransformEnums mod configuration.
20+ /// </summary>
21+ public record Configuration
22+ {
23+ /// <summary>
24+ /// Transforms [Flags] enums to have a "None = 0" member if they do not already have an equivalent.
25+ /// </summary>
26+ public bool AddNoneMemberToFlags { get ; init ; } = false ;
27+
28+ /// <summary>
29+ /// Removes enum members that match the enum member filter.
30+ /// </summary>
31+ /// <remarks>
32+ /// This was originally designed to remove max enum value members.
33+ /// </remarks>
34+ public EnumMemberFilterConfiguration [ ] RemoveMembers { get ; init ; } = [ ] ;
35+ }
36+
37+ /// <summary>
38+ /// Represents a filter used to match enum members.
39+ /// </summary>
40+ public record EnumMemberFilterConfiguration
41+ {
42+ /// <summary>
43+ /// The enum type name must match this regex.
44+ /// </summary>
45+ public string TypeName { get ; init ; } = ".*" ;
46+
47+ /// <summary>
48+ /// The enum member name must match this regex.
49+ /// </summary>
50+ public string MemberName { get ; init ; } = ".*" ;
51+
52+ /// <summary>
53+ /// The enum member value must match this in value.
54+ /// This value will be parsed as an integer. Hexadecimal is allowed.
55+ /// Use null to disable this filter.
56+ /// </summary>
57+ public string ? MemberValue { get ; init ; }
58+ }
59+
60+ private class EnumMemberFilter
61+ {
62+ public Regex TypeName { get ; }
63+ public Regex MemberName { get ; }
64+ public long ? MemberValue { get ; }
65+
66+ public EnumMemberFilter ( EnumMemberFilterConfiguration configuration )
67+ {
68+ TypeName = new Regex ( configuration . TypeName ) ;
69+ MemberName = new Regex ( configuration . MemberName ) ;
70+
71+ if ( configuration . MemberValue != null )
72+ {
73+ if ( configuration . MemberValue . StartsWith ( "0x" ) )
74+ {
75+ MemberValue = long . Parse ( configuration . MemberValue [ "0x" . Length ..] , NumberStyles . AllowHexSpecifier ) ;
76+ }
77+ else
78+ {
79+ MemberValue = long . Parse ( configuration . MemberValue ) ;
80+ }
81+ }
82+ }
83+
84+ public bool IsTypeMatch ( ITypeSymbol ? enumType )
85+ {
86+ return enumType != null && TypeName . IsMatch ( enumType . Name ) ;
87+ }
88+
89+ public bool IsMemberMatch ( ISymbol ? enumMember )
90+ {
91+ if ( enumMember is not IFieldSymbol fieldSymbol )
92+ {
93+ return false ;
94+ }
95+
96+ if ( ! MemberName . IsMatch ( enumMember . Name ) )
97+ {
98+ return false ;
99+ }
100+
101+ if ( MemberValue == null )
102+ {
103+ // Filter is disabled
104+ return true ;
105+ }
106+
107+ if ( fieldSymbol . ConstantValue == null )
108+ {
109+ // We don't know the constant value for sure
110+ // Return false as a default
111+ return false ;
112+ }
113+
114+ return Convert . ToInt64 ( fieldSymbol . ConstantValue ) == MemberValue ;
115+ }
116+ }
117+
118+ /// <inheritdoc />
119+ public async Task ExecuteAsync ( IModContext ctx , CancellationToken ct = default )
120+ {
121+ var config = cfg . Get ( ctx . JobKey ) ;
122+ var removeMemberFilters = config . RemoveMembers . Select ( c => new EnumMemberFilter ( c ) ) . ToList ( ) ;
123+
124+ var proj = ctx . SourceProject ;
125+ if ( proj == null )
126+ {
127+ return ;
128+ }
129+
130+ var compilation = await proj . GetCompilationAsync ( ct ) ;
131+ if ( compilation == null )
132+ {
133+ return ;
134+ }
135+
136+ var rewriter = new Rewriter ( config , removeMemberFilters , compilation ) ;
137+ foreach ( var docId in proj ? . DocumentIds ?? [ ] )
138+ {
139+ var doc = proj ! . GetDocument ( docId ) ?? throw new InvalidOperationException ( "Document missing" ) ;
140+ proj = doc . WithSyntaxRoot (
141+ rewriter . Visit ( await doc . GetSyntaxRootAsync ( ct ) ) ? . NormalizeWhitespace ( )
142+ ?? throw new InvalidOperationException ( "Visit returned null." )
143+ ) . Project ;
144+ }
145+
146+ ctx . SourceProject = proj ;
147+ }
148+
149+ private class Rewriter ( Configuration config , List < EnumMemberFilter > removeMemberFilters , Compilation compilation ) : CSharpSyntaxRewriter
150+ {
151+ public override SyntaxNode ? VisitEnumDeclaration ( EnumDeclarationSyntax node )
152+ {
153+ var semanticModel = compilation . GetSemanticModel ( node . SyntaxTree ) ;
154+ var symbol = semanticModel . GetDeclaredSymbol ( node ) ;
155+ if ( symbol == null )
156+ {
157+ return base . VisitEnumDeclaration ( node ) ;
158+ }
159+
160+ // This list is used to defer the modification of the enum declaration syntax node
161+ // This is important because modifying the node will detach it from the semantic model
162+ var members = node . Members . ToList ( ) ;
163+
164+ foreach ( var filter in removeMemberFilters )
165+ {
166+ if ( ! filter . IsTypeMatch ( symbol ) )
167+ {
168+ continue ;
169+ }
170+
171+ members = members
172+ . Where ( member => ! filter . IsMemberMatch ( semanticModel . GetDeclaredSymbol ( member ) ) )
173+ . ToList ( ) ;
174+ }
175+
176+ var isFlagsEnum = node . AttributeLists . SelectMany ( list => list . Attributes )
177+ . Any ( attribute => attribute . IsAttribute ( "System.Flags" ) ) ;
178+
179+ if ( node . Identifier . ToString ( ) == "ClusterAccelerationStructureAddressResolutionFlagsNV" )
180+ {
181+ Debugger . Break ( ) ;
182+ }
183+
184+ if ( isFlagsEnum && config . AddNoneMemberToFlags )
185+ {
186+ // Add None member if it doesn't exist yet
187+ var hasNoneMember = symbol . Members ( ) . Any ( member =>
188+ {
189+ if ( member is not IFieldSymbol fieldSymbol )
190+ {
191+ return false ;
192+ }
193+
194+ if ( member . Name == "None" )
195+ {
196+ return true ;
197+ }
198+
199+ if ( fieldSymbol . ConstantValue == null )
200+ {
201+ // We don't know the constant value for sure
202+ // Return false as a default
203+ return false ;
204+ }
205+
206+ return Convert . ToInt64 ( fieldSymbol . ConstantValue ) == 0 ;
207+ } ) ;
208+
209+ if ( ! hasNoneMember )
210+ {
211+ var noneMember = EnumMemberDeclaration ( "None" )
212+ . WithEqualsValue (
213+ EqualsValueClause (
214+ LiteralExpression ( SyntaxKind . NumericLiteralExpression , Literal ( 0 ) )
215+ )
216+ ) ;
217+
218+ members . Insert ( 0 , noneMember ) ;
219+ }
220+ }
221+
222+ node = node . WithMembers ( [ ..members ] ) ;
223+
224+ return base . VisitEnumDeclaration ( node ) ;
225+ }
226+ }
227+ }
0 commit comments