Skip to content

Commit a0df371

Browse files
authored
Merge pull request #487 from CommunityToolkit/dev/generator-tweaks
More source generator optimizations
2 parents 540fa1c + 7ae28e0 commit a0df371

File tree

2 files changed

+215
-41
lines changed

2 files changed

+215
-41
lines changed

CommunityToolkit.Mvvm.SourceGenerators/Extensions/INamedTypeSymbolExtensions.cs

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Collections.Generic;
67
using System.Text;
8+
using CommunityToolkit.Mvvm.SourceGenerators.Helpers;
79
using Microsoft.CodeAnalysis;
810

911
namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
@@ -20,27 +22,46 @@ internal static class INamedTypeSymbolExtensions
2022
/// <returns>The full metadata name for <paramref name="symbol"/> that is also a valid filename.</returns>
2123
public static string GetFullMetadataNameForFileName(this INamedTypeSymbol symbol)
2224
{
23-
static StringBuilder BuildFrom(ISymbol? symbol, StringBuilder builder)
25+
using ImmutableArrayBuilder<char> builder = ImmutableArrayBuilder<char>.Rent();
26+
27+
static void BuildFrom(ISymbol? symbol, in ImmutableArrayBuilder<char> builder)
2428
{
25-
return symbol switch
29+
switch (symbol)
2630
{
27-
INamespaceSymbol ns when ns.IsGlobalNamespace => builder,
28-
INamespaceSymbol ns when ns.ContainingNamespace is { IsGlobalNamespace: false }
29-
=> BuildFrom(ns.ContainingNamespace, builder.Insert(0, $".{ns.MetadataName}")),
30-
ITypeSymbol ts when ts.ContainingType is ISymbol pt
31-
=> BuildFrom(pt, builder.Insert(0, $"+{ts.MetadataName}")),
32-
ITypeSymbol ts when ts.ContainingNamespace is ISymbol pn and not INamespaceSymbol { IsGlobalNamespace: true }
33-
=> BuildFrom(pn, builder.Insert(0, $".{ts.MetadataName}")),
34-
ISymbol => BuildFrom(symbol.ContainingSymbol, builder.Insert(0, symbol.MetadataName)),
35-
_ => builder
36-
};
31+
// Namespaces that are nested also append a leading '.'
32+
case INamespaceSymbol { ContainingNamespace.IsGlobalNamespace: false }:
33+
BuildFrom(symbol.ContainingNamespace, in builder);
34+
builder.Add('.');
35+
builder.AddRange(symbol.MetadataName.AsSpan());
36+
break;
37+
// Other namespaces (ie. the one right before global) skip the leading '.'
38+
case INamespaceSymbol { IsGlobalNamespace: false }:
39+
builder.AddRange(symbol.MetadataName.AsSpan());
40+
break;
41+
// Types with no namespace just have their metadata name directly written
42+
case ITypeSymbol { ContainingSymbol: INamespaceSymbol { IsGlobalNamespace: true } }:
43+
builder.AddRange(symbol.MetadataName.AsSpan());
44+
break;
45+
// Types with a containing non-global namespace also append a leading '.'
46+
case ITypeSymbol { ContainingSymbol: INamespaceSymbol namespaceSymbol }:
47+
BuildFrom(namespaceSymbol, in builder);
48+
builder.Add('.');
49+
builder.AddRange(symbol.MetadataName.AsSpan());
50+
break;
51+
// Nested types append a leading '+'
52+
case ITypeSymbol { ContainingSymbol: ITypeSymbol typeSymbol }:
53+
BuildFrom(typeSymbol, in builder);
54+
builder.Add('+');
55+
builder.AddRange(symbol.MetadataName.AsSpan());
56+
break;
57+
default:
58+
break;
59+
}
3760
}
3861

39-
// Build the full metadata name by concatenating the metadata names of all symbols from the input
40-
// one to the outermost namespace, if any. Additionally, the ` and + symbols need to be replaced
41-
// to avoid errors when generating code. This is a known issue with source generators not accepting
42-
// those characters at the moment, see: https://github.com/dotnet/roslyn/issues/58476.
43-
return BuildFrom(symbol, new StringBuilder(256)).Replace('`', '-').Replace('+', '.').ToString();
62+
BuildFrom(symbol, in builder);
63+
64+
return builder.ToString();
4465
}
4566

4667
/// <summary>

CommunityToolkit.Mvvm.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs

Lines changed: 177 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,81 +2,234 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
// This file is ported and adapted from ComputeSharp (Sergio0694/ComputeSharp),
6+
// more info in ThirdPartyNotices.txt in the root of the project.
7+
58
using System;
69
using System.Collections.Immutable;
10+
using System.Runtime.CompilerServices;
711

812
namespace CommunityToolkit.Mvvm.SourceGenerators.Helpers;
913

1014
/// <summary>
11-
/// A helper type to build <see cref="ImmutableArray{T}"/> instances with pooled buffers.
15+
/// A helper type to build sequences of values with pooled buffers.
1216
/// </summary>
13-
/// <typeparam name="T">The type of items to create arrays for.</typeparam>
14-
internal ref struct ImmutableArrayBuilder<T>
17+
/// <typeparam name="T">The type of items to create sequences for.</typeparam>
18+
internal struct ImmutableArrayBuilder<T> : IDisposable
1519
{
1620
/// <summary>
17-
/// The shared <see cref="ObjectPool{T}"/> instance to share <see cref="ImmutableArray{T}.Builder"/> objects.
21+
/// The shared <see cref="ObjectPool{T}"/> instance to share <see cref="Writer"/> objects.
1822
/// </summary>
19-
private static readonly ObjectPool<ImmutableArray<T>.Builder> sharedObjectPool = new(ImmutableArray.CreateBuilder<T>);
23+
private static readonly ObjectPool<Writer> SharedObjectPool = new(static () => new Writer());
2024

2125
/// <summary>
22-
/// The rented <see cref="ImmutableArray{T}.Builder"/> instance to use.
26+
/// The rented <see cref="Writer"/> instance to use.
2327
/// </summary>
24-
private ImmutableArray<T>.Builder? builder;
28+
private Writer? writer;
2529

2630
/// <summary>
27-
/// Rents a new pooled <see cref="ImmutableArray{T}.Builder"/> instance through a new <see cref="ImmutableArrayBuilder{T}"/> value.
31+
/// Creates a <see cref="ImmutableArrayBuilder{T}"/> value with a pooled underlying data writer.
2832
/// </summary>
29-
/// <returns>A <see cref="ImmutableArrayBuilder{T}"/> to interact with the underlying <see cref="ImmutableArray{T}.Builder"/> instance.</returns>
33+
/// <returns>A <see cref="ImmutableArrayBuilder{T}"/> instance to write data to.</returns>
3034
public static ImmutableArrayBuilder<T> Rent()
3135
{
32-
return new(sharedObjectPool.Allocate());
36+
return new(SharedObjectPool.Allocate());
3337
}
3438

3539
/// <summary>
3640
/// Creates a new <see cref="ImmutableArrayBuilder{T}"/> object with the specified parameters.
3741
/// </summary>
38-
/// <param name="builder"></param>
39-
private ImmutableArrayBuilder(ImmutableArray<T>.Builder builder)
42+
/// <param name="writer">The target data writer to use.</param>
43+
private ImmutableArrayBuilder(Writer writer)
4044
{
41-
this.builder = builder;
45+
this.writer = writer;
4246
}
4347

4448
/// <inheritdoc cref="ImmutableArray{T}.Builder.Count"/>
45-
public readonly int Count
49+
public int Count
50+
{
51+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
52+
get => this.writer!.Count;
53+
}
54+
55+
/// <summary>
56+
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
57+
/// </summary>
58+
public readonly ReadOnlySpan<T> WrittenSpan
4659
{
47-
get => this.builder!.Count;
60+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
61+
get => this.writer!.WrittenSpan;
4862
}
4963

5064
/// <inheritdoc cref="ImmutableArray{T}.Builder.Add(T)"/>
5165
public readonly void Add(T item)
5266
{
53-
this.builder!.Add(item);
67+
this.writer!.Add(item);
68+
}
69+
70+
/// <summary>
71+
/// Adds the specified items to the end of the array.
72+
/// </summary>
73+
/// <param name="items">The items to add at the end of the array.</param>
74+
public readonly void AddRange(ReadOnlySpan<T> items)
75+
{
76+
this.writer!.AddRange(items);
5477
}
5578

5679
/// <inheritdoc cref="ImmutableArray{T}.Builder.ToImmutable"/>
5780
public readonly ImmutableArray<T> ToImmutable()
5881
{
59-
return this.builder!.ToImmutable();
82+
T[] array = this.writer!.WrittenSpan.ToArray();
83+
84+
return Unsafe.As<T[], ImmutableArray<T>>(ref array);
6085
}
6186

6287
/// <inheritdoc cref="ImmutableArray{T}.Builder.ToArray"/>
6388
public readonly T[] ToArray()
6489
{
65-
return this.builder!.ToArray();
90+
return this.writer!.WrittenSpan.ToArray();
6691
}
6792

68-
/// <inheritdoc cref="IDisposable.Dispose"/>
93+
/// <inheritdoc/>
94+
public override readonly string ToString()
95+
{
96+
return this.writer!.WrittenSpan.ToString();
97+
}
98+
99+
/// <inheritdoc/>
69100
public void Dispose()
70101
{
71-
ImmutableArray<T>.Builder? builder = this.builder;
102+
Writer? writer = this.writer;
72103

73-
this.builder = null;
104+
this.writer = null;
74105

75-
if (builder is not null)
106+
if (writer is not null)
76107
{
77-
builder.Clear();
108+
writer.Clear();
78109

79-
sharedObjectPool.Free(builder);
110+
SharedObjectPool.Free(writer);
111+
}
112+
}
113+
114+
/// <summary>
115+
/// A class handling the actual buffer writing.
116+
/// </summary>
117+
private sealed class Writer
118+
{
119+
/// <summary>
120+
/// The underlying <typeparamref name="T"/> array.
121+
/// </summary>
122+
private T[] array;
123+
124+
/// <summary>
125+
/// The starting offset within <see cref="array"/>.
126+
/// </summary>
127+
private int index;
128+
129+
/// <summary>
130+
/// Creates a new <see cref="Writer"/> instance with the specified parameters.
131+
/// </summary>
132+
public Writer()
133+
{
134+
if (typeof(T) == typeof(char))
135+
{
136+
this.array = new T[1024];
137+
}
138+
else
139+
{
140+
this.array = new T[8];
141+
}
142+
143+
this.index = 0;
144+
}
145+
146+
/// <inheritdoc cref="ImmutableArrayBuilder{T}.Count"/>
147+
public int Count
148+
{
149+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
150+
get => this.array!.Length;
151+
}
152+
153+
/// <inheritdoc cref="ImmutableArrayBuilder{T}.WrittenSpan"/>
154+
public ReadOnlySpan<T> WrittenSpan
155+
{
156+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
157+
get => new(this.array, 0, this.index);
80158
}
159+
160+
/// <inheritdoc cref="ImmutableArrayBuilder{T}.Add"/>
161+
public void Add(T value)
162+
{
163+
EnsureCapacity(1);
164+
165+
this.array[this.index++] = value;
166+
}
167+
168+
/// <inheritdoc cref="ImmutableArrayBuilder{T}.AddRange"/>
169+
public void AddRange(ReadOnlySpan<T> items)
170+
{
171+
EnsureCapacity(items.Length);
172+
173+
items.CopyTo(this.array.AsSpan(this.index));
174+
175+
this.index += items.Length;
176+
}
177+
178+
/// <summary>
179+
/// Clears the items in the current writer.
180+
/// </summary>
181+
public void Clear()
182+
{
183+
if (typeof(T) != typeof(char))
184+
{
185+
this.array.AsSpan(0, this.index).Clear();
186+
}
187+
188+
this.index = 0;
189+
}
190+
191+
/// <summary>
192+
/// Ensures that <see cref="array"/> has enough free space to contain a given number of new items.
193+
/// </summary>
194+
/// <param name="requestedSize">The minimum number of items to ensure space for in <see cref="array"/>.</param>
195+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
196+
private void EnsureCapacity(int requestedSize)
197+
{
198+
if (requestedSize > this.array.Length - this.index)
199+
{
200+
ResizeBuffer(requestedSize);
201+
}
202+
}
203+
204+
/// <summary>
205+
/// Resizes <see cref="array"/> to ensure it can fit the specified number of new items.
206+
/// </summary>
207+
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
208+
[MethodImpl(MethodImplOptions.NoInlining)]
209+
private void ResizeBuffer(int sizeHint)
210+
{
211+
int minimumSize = this.index + sizeHint;
212+
int requestedSize = Math.Max(this.array.Length * 2, minimumSize);
213+
214+
T[] newArray = new T[requestedSize];
215+
216+
Array.Copy(this.array, newArray, this.index);
217+
218+
this.array = newArray;
219+
}
220+
}
221+
}
222+
223+
/// <summary>
224+
/// Private helpers for the <see cref="ImmutableArrayBuilder{T}"/> type.
225+
/// </summary>
226+
file static class ImmutableArrayBuilder
227+
{
228+
/// <summary>
229+
/// Throws an <see cref="ArgumentOutOfRangeException"/> for <c>"index"</c>.
230+
/// </summary>
231+
public static void ThrowArgumentOutOfRangeExceptionForIndex()
232+
{
233+
throw new ArgumentOutOfRangeException("index");
81234
}
82235
}

0 commit comments

Comments
 (0)