Skip to content

Commit 7ae28e0

Browse files
committed
Make GetFullMetadataNameForFileName extension zero-alloc
1 parent aabbf95 commit 7ae28e0

File tree

1 file changed

+38
-17
lines changed

1 file changed

+38
-17
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>

0 commit comments

Comments
 (0)