1+ using System . Collections ;
2+ using System . Collections . Immutable ;
3+ using System . Text ;
4+
5+ using ICSharpCode . Decompiler . CSharp . Syntax ;
6+
7+ using Microsoft . CodeAnalysis ;
8+ using Microsoft . CodeAnalysis . CSharp ;
9+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
10+ using Microsoft . CodeAnalysis . Text ;
11+
12+ namespace ICSharpCode . Decompiler . Generators ;
13+
14+ [ Generator ]
15+ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
16+ {
17+ record AstNodeAdditions ( string NodeName , bool NeedsAcceptImpls , bool NeedsVisitor , bool NeedsNullNode , int NullNodeBaseCtorParamCount , bool IsTypeNode , string VisitMethodName , string VisitMethodParamType , EquatableArray < ( string Member , string TypeName , bool RecursiveMatch , bool MatchAny ) > ? MembersToMatch ) ;
18+
19+ AstNodeAdditions GetAstNodeAdditions ( GeneratorAttributeSyntaxContext context , CancellationToken ct )
20+ {
21+ var targetSymbol = ( INamedTypeSymbol ) context . TargetSymbol ;
22+ var attribute = context . Attributes . SingleOrDefault ( ad => ad . AttributeClass ? . Name == "DecompilerAstNodeAttribute" ) ! ;
23+ var ( visitMethodName , paramTypeName ) = targetSymbol . Name switch {
24+ "ErrorExpression" => ( "ErrorNode" , "AstNode" ) ,
25+ string s when s . Contains ( "AstType" ) => ( s . Replace ( "AstType" , "Type" ) , s ) ,
26+ _ => ( targetSymbol . Name , targetSymbol . Name ) ,
27+ } ;
28+
29+ List < ( string Member , string TypeName , bool RecursiveMatch , bool MatchAny ) > ? membersToMatch = null ;
30+
31+ if ( ! targetSymbol . MemberNames . Contains ( "DoMatch" ) )
32+ {
33+ membersToMatch = new ( ) ;
34+
35+ var astNodeType = ( INamedTypeSymbol ) context . SemanticModel . GetSpeculativeSymbolInfo ( context . TargetNode . Span . Start , SyntaxFactory . ParseTypeName ( "AstNode" ) , SpeculativeBindingOption . BindAsTypeOrNamespace ) . Symbol ! ;
36+
37+ if ( targetSymbol . BaseType ! . MemberNames . Contains ( "MatchAttributesAndModifiers" ) )
38+ membersToMatch . Add ( ( "MatchAttributesAndModifiers" , null ! , false , false ) ) ;
39+
40+ foreach ( var m in targetSymbol . GetMembers ( ) )
41+ {
42+ if ( m is not IPropertySymbol property || property . IsIndexer || property . IsOverride )
43+ continue ;
44+ if ( property . GetAttributes ( ) . Any ( a => a . AttributeClass ? . Name == nameof ( ExcludeFromMatchAttribute ) ) )
45+ continue ;
46+ if ( property . Type . MetadataName is "CSharpTokenNode" or "TextLocation" )
47+ continue ;
48+ switch ( property . Type )
49+ {
50+ case INamedTypeSymbol named when named . IsDerivedFrom ( astNodeType ) || named . MetadataName == "AstNodeCollection`1" :
51+ membersToMatch . Add ( ( property . Name , named . Name , true , false ) ) ;
52+ break ;
53+ case INamedTypeSymbol { TypeKind : TypeKind . Enum } named when named . GetMembers ( ) . Any ( _ => _ . Name == "Any" ) :
54+ membersToMatch . Add ( ( property . Name , named . Name , false , true ) ) ;
55+ break ;
56+ default :
57+ membersToMatch . Add ( ( property . Name , property . Type . Name , false , false ) ) ;
58+ break ;
59+ }
60+ }
61+ }
62+
63+ return new ( targetSymbol . Name , ! targetSymbol . MemberNames . Contains ( "AcceptVisitor" ) ,
64+ NeedsVisitor : ! targetSymbol . IsAbstract && targetSymbol . BaseType ! . IsAbstract ,
65+ NeedsNullNode : ( bool ) attribute . ConstructorArguments [ 0 ] . Value ! ,
66+ NullNodeBaseCtorParamCount : targetSymbol . InstanceConstructors . Min ( m => m . Parameters . Length ) ,
67+ IsTypeNode : targetSymbol . Name == "AstType" || targetSymbol . BaseType ? . Name == "AstType" ,
68+ visitMethodName , paramTypeName , membersToMatch ? . ToEquatableArray ( ) ) ;
69+ }
70+
71+ void WriteGeneratedMembers ( SourceProductionContext context , AstNodeAdditions source )
72+ {
73+ var builder = new StringBuilder ( ) ;
74+
75+ builder . AppendLine ( "namespace ICSharpCode.Decompiler.CSharp.Syntax;" ) ;
76+ builder . AppendLine ( ) ;
77+
78+ builder . AppendLine ( "#nullable enable" ) ;
79+ builder . AppendLine ( ) ;
80+
81+ builder . AppendLine ( $ "partial class { source . NodeName } ") ;
82+ builder . AppendLine ( "{" ) ;
83+
84+ if ( source . NeedsNullNode )
85+ {
86+ bool needsNew = source . NodeName != "AstNode" ;
87+
88+ builder . AppendLine ( $ " { ( needsNew ? "new " : "" ) } public static readonly { source . NodeName } Null = new Null{ source . NodeName } ();") ;
89+
90+ builder . AppendLine ( $@ "
91+ sealed class Null{ source . NodeName } : { source . NodeName }
92+ {{
93+ public override NodeType NodeType => NodeType.Unknown;
94+
95+ public override bool IsNull => true;
96+
97+ public override void AcceptVisitor(IAstVisitor visitor)
98+ {{
99+ visitor.VisitNullNode(this);
100+ }}
101+
102+ public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
103+ {{
104+ return visitor.VisitNullNode(this);
105+ }}
106+
107+ public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
108+ {{
109+ return visitor.VisitNullNode(this, data);
110+ }}
111+
112+ protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
113+ {{
114+ return other == null || other.IsNull;
115+ }}" ) ;
116+
117+ if ( source . IsTypeNode )
118+ {
119+ builder . AppendLine (
120+ $@ "
121+
122+ public override Decompiler.TypeSystem.ITypeReference ToTypeReference(Resolver.NameLookupMode lookupMode, Decompiler.TypeSystem.InterningProvider? interningProvider = null)
123+ {{
124+ return Decompiler.TypeSystem.SpecialType.UnknownType;
125+ }}"
126+ ) ;
127+ }
128+
129+ if ( source . NullNodeBaseCtorParamCount > 0 )
130+ {
131+ builder . AppendLine ( $@ "
132+
133+ public Null{ source . NodeName } () : base({ string . Join ( ", " , Enumerable . Repeat ( "default" , source . NullNodeBaseCtorParamCount ) ) } ) {{ }}" ) ;
134+ }
135+
136+ builder . AppendLine ( $@ "
137+ }}
138+
139+ " ) ;
140+
141+ }
142+
143+ if ( source . NeedsAcceptImpls && source . NeedsVisitor )
144+ {
145+ builder . Append ( $@ " public override void AcceptVisitor(IAstVisitor visitor)
146+ {{
147+ visitor.Visit{ source . NodeName } (this);
148+ }}
149+
150+ public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
151+ {{
152+ return visitor.Visit{ source . NodeName } (this);
153+ }}
154+
155+ public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
156+ {{
157+ return visitor.Visit{ source . NodeName } (this, data);
158+ }}
159+ " ) ;
160+ }
161+
162+ if ( source . MembersToMatch != null )
163+ {
164+ builder . Append ( $@ " protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
165+ {{
166+ return other is { source . NodeName } o && !o.IsNull" ) ;
167+
168+ foreach ( var ( member , typeName , recursive , hasAny ) in source . MembersToMatch )
169+ {
170+ if ( member == "MatchAttributesAndModifiers" )
171+ {
172+ builder . Append ( $ "\r \n \t \t \t && this.MatchAttributesAndModifiers(o, match)") ;
173+ }
174+ else if ( recursive )
175+ {
176+ builder . Append ( $ "\r \n \t \t \t && this.{ member } .DoMatch(o.{ member } , match)") ;
177+ }
178+ else if ( hasAny )
179+ {
180+ builder . Append ( $ "\r \n \t \t \t && (this.{ member } == { typeName } .Any || this.{ member } == o.{ member } )") ;
181+ }
182+ else
183+ {
184+ builder . Append ( $ "\r \n \t \t \t && this.{ member } == o.{ member } ") ;
185+ }
186+ }
187+
188+ builder . Append ( @";
189+ }
190+ " ) ;
191+ }
192+
193+ builder . AppendLine ( "}" ) ;
194+
195+ context . AddSource ( source . NodeName + ".g.cs" , SourceText . From ( builder . ToString ( ) , Encoding . UTF8 ) ) ;
196+ }
197+
198+ private void WriteVisitors ( SourceProductionContext context , ImmutableArray < AstNodeAdditions > source )
199+ {
200+ var builder = new StringBuilder ( ) ;
201+
202+ builder . AppendLine ( "namespace ICSharpCode.Decompiler.CSharp.Syntax;" ) ;
203+
204+ source = source
205+ . Concat ( [ new ( "NullNode" , false , true , false , 0 , false , "NullNode" , "AstNode" , null ) , new ( "PatternPlaceholder" , false , true , false , 0 , false , "PatternPlaceholder" , "AstNode" , null ) ] )
206+ . ToImmutableArray ( ) ;
207+
208+ WriteInterface ( "IAstVisitor" , "void" , "" ) ;
209+ WriteInterface ( "IAstVisitor<out S>" , "S" , "" ) ;
210+ WriteInterface ( "IAstVisitor<in T, out S>" , "S" , ", T data" ) ;
211+
212+ context . AddSource ( "IAstVisitor.g.cs" , SourceText . From ( builder . ToString ( ) , Encoding . UTF8 ) ) ;
213+
214+ void WriteInterface ( string name , string ret , string param )
215+ {
216+ builder . AppendLine ( $ "public interface { name } ") ;
217+ builder . AppendLine ( "{" ) ;
218+
219+ foreach ( var type in source . OrderBy ( t => t . VisitMethodName ) )
220+ {
221+ if ( ! type . NeedsVisitor )
222+ continue ;
223+
224+ string extParams , paramName ;
225+ if ( type . VisitMethodName == "PatternPlaceholder" )
226+ {
227+ paramName = "placeholder" ;
228+ extParams = ", PatternMatching.Pattern pattern" + param ;
229+ }
230+ else
231+ {
232+ paramName = char . ToLowerInvariant ( type . VisitMethodName [ 0 ] ) + type . VisitMethodName . Substring ( 1 ) ;
233+ extParams = param ;
234+ }
235+
236+ builder . AppendLine ( $ "\t { ret } Visit{ type . VisitMethodName } ({ type . VisitMethodParamType } { paramName } { extParams } );") ;
237+ }
238+
239+ builder . AppendLine ( "}" ) ;
240+ }
241+ }
242+
243+ public void Initialize ( IncrementalGeneratorInitializationContext context )
244+ {
245+ var astNodeAdditions = context . SyntaxProvider . ForAttributeWithMetadataName (
246+ "ICSharpCode.Decompiler.CSharp.Syntax.DecompilerAstNodeAttribute" ,
247+ ( n , ct ) => n is ClassDeclarationSyntax ,
248+ GetAstNodeAdditions ) ;
249+
250+ var visitorMembers = astNodeAdditions . Collect ( ) ;
251+
252+ context . RegisterSourceOutput ( astNodeAdditions , WriteGeneratedMembers ) ;
253+ context . RegisterSourceOutput ( visitorMembers , WriteVisitors ) ;
254+ }
255+ }
256+
257+ readonly struct EquatableArray < T > : IEquatable < EquatableArray < T > > , IEnumerable < T >
258+ where T : IEquatable < T >
259+ {
260+ readonly T [ ] array ;
261+
262+ public EquatableArray ( T [ ] array )
263+ {
264+ this . array = array ?? throw new ArgumentNullException ( nameof ( array ) ) ;
265+ }
266+
267+ public bool Equals ( EquatableArray < T > other )
268+ {
269+ return other . array . AsSpan ( ) . SequenceEqual ( this . array ) ;
270+ }
271+
272+ public IEnumerator < T > GetEnumerator ( )
273+ {
274+ return ( ( IEnumerable < T > ) array ) . GetEnumerator ( ) ;
275+ }
276+
277+ IEnumerator IEnumerable . GetEnumerator ( )
278+ {
279+ return array . GetEnumerator ( ) ;
280+ }
281+ }
282+
283+ static class EquatableArrayExtensions
284+ {
285+ public static EquatableArray < T > ToEquatableArray < T > ( this List < T > array ) where T : IEquatable < T >
286+ {
287+ return new EquatableArray < T > ( array . ToArray ( ) ) ;
288+ }
289+ }
0 commit comments