Skip to content

Commit dacd031

Browse files
Added source generator caching... yes I know we're not supposed to do this but 3 second intellisense times are unbearable
1 parent 7d7d5ae commit dacd031

20 files changed

+675
-168
lines changed

src/JsonRpc.Generators/AutoImplementParamsGenerator.cs

+36-35
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,18 @@
55
using Microsoft.CodeAnalysis.CSharp;
66
using Microsoft.CodeAnalysis.CSharp.Syntax;
77
using Microsoft.CodeAnalysis.Text;
8+
using OmniSharp.Extensions.JsonRpc.Generators.Cache;
89
using OmniSharp.Extensions.JsonRpc.Generators.Contexts;
910
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
1011
using static OmniSharp.Extensions.JsonRpc.Generators.CommonElements;
1112

1213
namespace OmniSharp.Extensions.JsonRpc.Generators
1314
{
1415
[Generator]
15-
public class AutoImplementParamsGenerator : ISourceGenerator
16+
public class AutoImplementParamsGenerator : CachedSourceGenerator<AutoImplementParamsGenerator.SyntaxReceiver, ClassDeclarationSyntax>
1617
{
17-
18-
public void Initialize(GeneratorInitializationContext context)
19-
{
20-
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
21-
}
22-
23-
public void Execute(GeneratorExecutionContext context)
18+
protected override void Execute(GeneratorExecutionContext context, SyntaxReceiver syntaxReceiver, AddCacheSource<ClassDeclarationSyntax> addCacheSource, ReportCacheDiagnostic<ClassDeclarationSyntax> cacheDiagnostic)
2419
{
25-
if (!( context.SyntaxReceiver is SyntaxReceiver syntaxReceiver ))
26-
{
27-
return;
28-
}
29-
3020
foreach (var candidate in syntaxReceiver.Candidates)
3121
{
3222
var members = new List<MemberDeclarationSyntax>();
@@ -49,9 +39,7 @@ public void Execute(GeneratorExecutionContext context)
4939

5040
if (!candidate.Modifiers.Any(z => z.IsKind(SyntaxKind.PartialKeyword)))
5141
{
52-
context.ReportDiagnostic(
53-
Diagnostic.Create(GeneratorDiagnostics.MustBePartial, candidate.Identifier.GetLocation(), candidate.Identifier.Text)
54-
);
42+
cacheDiagnostic(candidate, static c => Diagnostic.Create(GeneratorDiagnostics.MustBePartial, c.Identifier.GetLocation(), c.Identifier.Text));
5543
}
5644

5745
var cu = CompilationUnit(
@@ -61,64 +49,77 @@ public void Execute(GeneratorExecutionContext context)
6149
SingletonList<MemberDeclarationSyntax>(
6250
NamespaceDeclaration(ParseName(symbol.ContainingNamespace.ToDisplayString()))
6351
.WithMembers(List(members))
64-
)
52+
)
6553
)
6654
.AddUsings(UsingDirective(ParseName("OmniSharp.Extensions.LanguageServer.Protocol.Serialization")))
6755
.WithLeadingTrivia()
6856
.WithTrailingTrivia()
6957
.WithLeadingTrivia(Comment(Preamble.GeneratedByATool), Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)))
7058
.WithTrailingTrivia(Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)), CarriageReturnLineFeed);
7159

72-
context.AddSource(
60+
addCacheSource(
7361
$"{candidate.Identifier.Text}{( candidate.Arity > 0 ? candidate.Arity.ToString() : "" )}.cs",
62+
candidate,
7463
cu.NormalizeWhitespace().GetText(Encoding.UTF8)
7564
);
7665
}
7766
}
7867

7968
private static IEnumerable<MemberDeclarationSyntax> AutoImplementInterfaces(ClassDeclarationSyntax syntax, INamedTypeSymbol symbol)
8069
{
81-
if (syntax.BaseList?.Types.Any(z => z.Type.GetSyntaxName() is "IWorkDoneProgressParams" ) == true
70+
if (syntax.BaseList?.Types.Any(z => z.Type.GetSyntaxName() is "IWorkDoneProgressParams") == true
8271
&& symbol.GetMembers("WorkDoneToken").IsEmpty)
8372
{
8473
yield return PropertyDeclaration(NullableType(IdentifierName("ProgressToken")), Identifier("WorkDoneToken"))
85-
.WithAttributeLists(SingletonList(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("Optional"))))))
86-
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
87-
.WithAccessorList(GetSetAccessor);
74+
.WithAttributeLists(SingletonList(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("Optional"))))))
75+
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
76+
.WithAccessorList(GetSetAccessor);
8877
}
8978

90-
if (syntax.BaseList?.Types.Any(z => z.Type.GetSyntaxName() is "IPartialItemsRequest" or "IPartialItemRequest") == true
79+
if (syntax.BaseList?.Types.Any(z => z.Type.GetSyntaxName() is "IPartialItemsRequest" or "IPartialItemRequest") == true
9180
&& symbol.GetMembers("PartialResultToken").IsEmpty)
9281
{
9382
yield return PropertyDeclaration(NullableType(IdentifierName("ProgressToken")), Identifier("PartialResultToken"))
94-
.WithAttributeLists(SingletonList(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("Optional"))))))
95-
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
96-
.WithAccessorList(GetSetAccessor);
83+
.WithAttributeLists(SingletonList(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("Optional"))))))
84+
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
85+
.WithAccessorList(GetSetAccessor);
9786
}
9887
}
9988

100-
/// <summary>
101-
/// Created on demand before each generation pass
102-
/// </summary>
103-
internal class SyntaxReceiver : ISyntaxReceiver
89+
public AutoImplementParamsGenerator() : base(() => new SyntaxReceiver(Cache)) { }
90+
91+
public static CacheContainer<ClassDeclarationSyntax> Cache = new();
92+
93+
public class SyntaxReceiver : SyntaxReceiverCache<ClassDeclarationSyntax>
10494
{
10595
private string _attributes;
106-
public List<ClassDeclarationSyntax> Candidates { get; } = new ();
96+
public List<ClassDeclarationSyntax> Candidates { get; } = new();
10797

108-
public SyntaxReceiver()
98+
public SyntaxReceiver(CacheContainer<ClassDeclarationSyntax> cacheContainer) : base(cacheContainer)
10999
{
110100
_attributes = "Method,RegistrationOptions";
111101
}
112102

103+
public override string? GetKey(ClassDeclarationSyntax syntax)
104+
{
105+
var hasher = new CacheKeyHasher();
106+
hasher.Append(syntax.SyntaxTree.FilePath);
107+
hasher.Append(syntax.Identifier.Text);
108+
hasher.Append(syntax.TypeParameterList);
109+
hasher.Append(syntax.AttributeLists);
110+
hasher.Append(syntax.BaseList);
111+
return hasher;
112+
}
113+
113114
/// <summary>
114115
/// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
115116
/// </summary>
116-
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
117+
public override void OnVisitNode(ClassDeclarationSyntax syntaxNode)
117118
{
118119
// any field with at least one attribute is a candidate for property generation
119-
if (syntaxNode is ClassDeclarationSyntax tds && tds.AttributeLists.ContainsAttribute(_attributes))
120+
if (syntaxNode.AttributeLists.ContainsAttribute(_attributes))
120121
{
121-
Candidates.Add(tds);
122+
Candidates.Add(syntaxNode);
122123
}
123124
}
124125
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Collections.Generic;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.Text;
4+
5+
namespace OmniSharp.Extensions.JsonRpc.Generators.Cache
6+
{
7+
public delegate void AddCacheSource<in T>(string hintName, T syntaxNode, SourceText sourceText) where T : SyntaxNode;
8+
9+
public delegate void ReportCacheDiagnostic<T>(T syntaxNode, CacheDiagnosticFactory<T> diagnostic) where T : SyntaxNode;
10+
11+
public delegate Diagnostic CacheDiagnosticFactory<in T>(T syntaxNode) where T : SyntaxNode;
12+
13+
public delegate Location LocationFactory<in T>(T syntaxNode) where T : SyntaxNode;
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Collections.Immutable;
2+
using System.Linq;
3+
using System.Threading;
4+
using Microsoft.CodeAnalysis;
5+
6+
namespace OmniSharp.Extensions.JsonRpc.Generators.Cache
7+
{
8+
public class CacheContainer<T> where T : SyntaxNode
9+
{
10+
private ImmutableDictionary<string, ImmutableArray<SourceTextCache>> _sourceTexts;
11+
private ImmutableDictionary<string, ImmutableArray<CacheDiagnosticFactory<T>>> _cacheDiagnostics;
12+
13+
public CacheContainer()
14+
{
15+
_sourceTexts = ImmutableDictionary<string, ImmutableArray<SourceTextCache>>.Empty;
16+
_cacheDiagnostics = ImmutableDictionary<string, ImmutableArray<CacheDiagnosticFactory<T>>>.Empty;
17+
}
18+
19+
public ImmutableDictionary<string, ImmutableArray<SourceTextCache>> SourceTexts => _sourceTexts;
20+
public ImmutableDictionary<string, ImmutableArray<CacheDiagnosticFactory<T>>> Diagnostics => _cacheDiagnostics;
21+
22+
public void Swap(
23+
ImmutableDictionary<string, (T syntaxNode, ImmutableArray<SourceTextCache>.Builder sources)>.Builder foundCache,
24+
ImmutableDictionary<string, ImmutableArray<CacheDiagnosticFactory<T>>.Builder>.Builder diagnosticFactories
25+
)
26+
{
27+
Interlocked.Exchange(
28+
ref _sourceTexts,
29+
foundCache.ToImmutableDictionary(z => z.Key, z => z.Value.sources.ToImmutable())
30+
);
31+
Interlocked.Exchange(
32+
ref _cacheDiagnostics,
33+
diagnosticFactories.ToImmutableDictionary(z => z.Key, z => z.Value.ToImmutable())
34+
);
35+
}
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Security.Cryptography;
3+
using System.Text;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
7+
namespace OmniSharp.Extensions.JsonRpc.Generators.Cache
8+
{
9+
class CacheKeyHasher : IDisposable
10+
{
11+
public static bool Cache = true;
12+
private readonly SHA1 _hasher;
13+
14+
public CacheKeyHasher()
15+
{
16+
_hasher = SHA1.Create();
17+
}
18+
19+
public void Append(string textToHash)
20+
{
21+
var inputBuffer = Encoding.UTF8.GetBytes(textToHash);
22+
_hasher.TransformBlock(inputBuffer, 0, inputBuffer.Length, inputBuffer, 0);
23+
}
24+
25+
public void Append(TypeSyntax? typeSyntax)
26+
{
27+
if (typeSyntax?.GetSyntaxName() is { } a)
28+
{
29+
Append(a);
30+
}
31+
}
32+
33+
public void Append(TypeParameterListSyntax? typeParameterListSyntax)
34+
{
35+
if (typeParameterListSyntax is null or { Parameters: { Count: 0} }) return;
36+
foreach (var item in typeParameterListSyntax.Parameters)
37+
{
38+
Append(item.Identifier.Text);
39+
Append(item.AttributeLists);
40+
}
41+
}
42+
43+
public void Append(BaseListSyntax? baseListSyntax)
44+
{
45+
if (baseListSyntax is null) return;
46+
foreach (var item in baseListSyntax.Types)
47+
{
48+
Append(item.Type);
49+
}
50+
}
51+
52+
public void Append(SyntaxList<AttributeListSyntax> attributeList)
53+
{
54+
foreach (var item in attributeList)
55+
{
56+
Append(item);
57+
}
58+
}
59+
60+
public void Append(AttributeListSyntax attributeList)
61+
{
62+
if (attributeList is { Attributes: { Count: 0 } }) return;
63+
foreach (var item in attributeList.Attributes)
64+
{
65+
Append(item);
66+
}
67+
}
68+
69+
public void Append(AttributeSyntax attribute)
70+
{
71+
Append(attribute.Name.GetSyntaxName() ?? string.Empty);
72+
if (attribute.ArgumentList?.Arguments is { Count: > 0 } arguments)
73+
{
74+
foreach (var item in arguments)
75+
{
76+
if (item.NameEquals is { })
77+
{
78+
Append(item.NameEquals.Name.GetSyntaxName() ?? string.Empty);
79+
}
80+
81+
Append(
82+
item switch {
83+
{ Expression: TypeOfExpressionSyntax tyof } => tyof.Type.GetSyntaxName() is { Length: >0 } name ? name : string.Empty,
84+
{ Expression: LiteralExpressionSyntax { } literal } => literal.Token.Text,
85+
_ => string.Empty
86+
}
87+
);
88+
}
89+
}
90+
}
91+
92+
private string ConvertByteArrayToString()
93+
{
94+
_hasher.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
95+
var sb = new StringBuilder();
96+
foreach (var b in _hasher.Hash)
97+
{
98+
sb.Append(b.ToString("X2"));
99+
}
100+
101+
return sb.ToString();
102+
}
103+
104+
public override string ToString() => ConvertByteArrayToString();
105+
106+
public static implicit operator string(CacheKeyHasher value) => value.ToString();
107+
108+
public void Dispose() => _hasher.Dispose();
109+
}
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using Microsoft.CodeAnalysis;
3+
4+
namespace OmniSharp.Extensions.JsonRpc.Generators.Cache
5+
{
6+
/// <summary>
7+
/// We're not supposed to do this... but in realistic.
8+
/// </summary>
9+
public abstract class CachedSourceGenerator<T, TSyntax> : ISourceGenerator
10+
where T : ISyntaxReceiver, IReceiverCache<TSyntax>
11+
where TSyntax : SyntaxNode
12+
{
13+
private readonly Func<T> _syntaxReceiverFactory;
14+
15+
public CachedSourceGenerator(Func<T> syntaxReceiverFactory)
16+
{
17+
_syntaxReceiverFactory = syntaxReceiverFactory;
18+
}
19+
20+
public void Initialize(GeneratorInitializationContext context)
21+
{
22+
context.RegisterForSyntaxNotifications(() => _syntaxReceiverFactory());
23+
}
24+
25+
public void Execute(GeneratorExecutionContext context)
26+
{
27+
if (!( context.SyntaxReceiver is T syntaxReceiver )) return;
28+
29+
syntaxReceiver.Start(context);
30+
Execute(
31+
context, syntaxReceiver,
32+
(name, node, text) => {
33+
context.AddSource(name, text);
34+
35+
if (CacheKeyHasher.Cache)
36+
{
37+
syntaxReceiver.AddCacheSource(name, node, text);
38+
}
39+
},
40+
(node, diagnostic) => {
41+
context.ReportDiagnostic(diagnostic(node));
42+
43+
if (CacheKeyHasher.Cache)
44+
{
45+
syntaxReceiver.ReportCacheDiagnostic(node, diagnostic);
46+
}
47+
}
48+
);
49+
foreach (var item in syntaxReceiver.CachedSources)
50+
{
51+
context.AddSource(item.Name, item.SourceText);
52+
}
53+
foreach (var item in syntaxReceiver.CachedDiagnostics)
54+
{
55+
context.ReportDiagnostic(item);
56+
}
57+
58+
syntaxReceiver.Finish(context);
59+
}
60+
61+
protected abstract void Execute(GeneratorExecutionContext context, T syntaxReceiver, AddCacheSource<TSyntax> addCacheSource, ReportCacheDiagnostic<TSyntax> cacheDiagnostic);
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Collections.Generic;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.Text;
4+
5+
namespace OmniSharp.Extensions.JsonRpc.Generators.Cache
6+
{
7+
public interface IReceiverCache<T>
8+
where T : SyntaxNode
9+
{
10+
string? GetKey(T syntax);
11+
void Start(GeneratorExecutionContext context);
12+
void Finish(GeneratorExecutionContext context);
13+
IEnumerable<SourceTextCache> CachedSources { get; }
14+
IEnumerable<Diagnostic> CachedDiagnostics { get; }
15+
void AddCacheSource(string hintName, T syntaxNode, SourceText sourceText);
16+
void ReportCacheDiagnostic(T syntaxNode, CacheDiagnosticFactory<T> diagnostic);
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Collections.Generic;
2+
using System.Collections.Immutable;
3+
using Microsoft.CodeAnalysis;
4+
using Microsoft.CodeAnalysis.Text;
5+
6+
namespace OmniSharp.Extensions.JsonRpc.Generators.Cache
7+
{
8+
public record SourceTextCache(string Name, SourceText SourceText);
9+
public record DiagnosticCache<T>(ImmutableArray<CacheDiagnosticFactory<T>> Diagnostics) where T : SyntaxNode;
10+
}

0 commit comments

Comments
 (0)