Skip to content

Commit 9f8adbd

Browse files
committed
Use span tokenization in multi-line emission logic
1 parent af4468f commit 9f8adbd

File tree

7 files changed

+170
-125
lines changed

7 files changed

+170
-125
lines changed

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Emitter.cs

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7-
using System.Text;
87
using System.Text.RegularExpressions;
98
using Microsoft.CodeAnalysis;
10-
using Microsoft.CodeAnalysis.Text;
119

1210
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
1311
{
@@ -79,8 +77,7 @@ public void Emit()
7977

8078
EmitGenerationNamespaceAndHelpers();
8179

82-
SourceText source = SourceText.From(_writer.GetSource(), Encoding.UTF8);
83-
_context.AddSource($"{Identifier.GeneratedConfigurationBinder}.g.cs", source);
80+
_context.AddSource($"{Identifier.GeneratedConfigurationBinder}.g.cs", _writer.ToSourceText());
8481
}
8582

8683
private void EmitConfigureMethod()
@@ -275,10 +272,10 @@ private void EmitBindCoreImplForArray(EnumerableSpec type)
275272

276273
// Resize array and copy fill with additional
277274
_writer.WriteBlock($$"""
278-
{{Identifier.Int32}} {{Identifier.originalCount}} = {{Identifier.obj}}.{{Identifier.Length}};
279-
{{Identifier.Array}}.{{Identifier.Resize}}(ref {{Identifier.obj}}, {{Identifier.originalCount}} + {{tempVarName}}.{{Identifier.Count}});
280-
{{tempVarName}}.{{Identifier.CopyTo}}({{Identifier.obj}}, {{Identifier.originalCount}});
281-
""");
275+
{{Identifier.Int32}} {{Identifier.originalCount}} = {{Identifier.obj}}.{{Identifier.Length}};
276+
{{Identifier.Array}}.{{Identifier.Resize}}(ref {{Identifier.obj}}, {{Identifier.originalCount}} + {{tempVarName}}.{{Identifier.Count}});
277+
{{tempVarName}}.{{Identifier.CopyTo}}({{Identifier.obj}}, {{Identifier.originalCount}});
278+
""");
282279
}
283280

284281
private void EmitBindCoreImplForDictionary(DictionarySpec type)
@@ -675,11 +672,11 @@ private void EmitObjectInit(TypeSpec type, string expressionForMemberAccess, Ini
675672
private void EmitIConfigurationHasValueOrChildrenCheck()
676673
{
677674
_writer.WriteBlock($$"""
678-
if (!{{GetHelperMethodDisplayString(Identifier.HasValueOrChildren)}}({{Identifier.configuration}}))
679-
{
680-
return default;
681-
}
682-
""");
675+
if (!{{GetHelperMethodDisplayString(Identifier.HasValueOrChildren)}}({{Identifier.configuration}}))
676+
{
677+
return default;
678+
}
679+
""");
683680
_writer.WriteBlankLine();
684681
}
685682

@@ -700,31 +697,29 @@ private void EmitHelperMethods()
700697
private void EmitHasValueOrChildrenMethod()
701698
{
702699
_writer.WriteBlock($$"""
703-
public static bool {{Identifier.HasValueOrChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}})
704-
{
705-
if (({{Identifier.configuration}} as {{Identifier.IConfigurationSection}})?.{{Identifier.Value}} is not null)
706-
{
707-
return true;
708-
}
709-
710-
return {{Identifier.HasChildren}}({{Identifier.configuration}});
711-
}
712-
""");
700+
public static bool {{Identifier.HasValueOrChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}})
701+
{
702+
if (({{Identifier.configuration}} as {{Identifier.IConfigurationSection}})?.{{Identifier.Value}} is not null)
703+
{
704+
return true;
705+
}
706+
return {{Identifier.HasChildren}}({{Identifier.configuration}});
707+
}
708+
""");
713709
}
714710

715711
private void EmitHasChildrenMethod()
716712
{
717713
_writer.WriteBlock($$"""
718-
public static bool {{Identifier.HasChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}})
719-
{
720-
foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
721-
{
722-
return true;
723-
}
724-
725-
return false;
726-
}
727-
""");
714+
public static bool {{Identifier.HasChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}})
715+
{
716+
foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
717+
{
718+
return true;
719+
}
720+
return false;
721+
}
722+
""");
728723
}
729724

730725
private void EmitVarDeclaration(TypeSpec type, string varName) => _writer.WriteLine($"{type.MinimalDisplayString} {varName};");
@@ -747,11 +742,11 @@ private void EmitCastToIConfigurationSection()
747742
}
748743

749744
_writer.WriteBlock($$"""
750-
if ({{Identifier.configuration}} is not {{sectionTypeDisplayString}} {{Identifier.section}})
751-
{
752-
throw new {{exceptionTypeDisplayString}}();
753-
}
754-
""");
745+
if ({{Identifier.configuration}} is not {{sectionTypeDisplayString}} {{Identifier.section}})
746+
{
747+
throw new {{exceptionTypeDisplayString}}();
748+
}
749+
""");
755750
}
756751

757752
private void Emit_NotSupportedException_UnableToBindType(string reason, string typeDisplayString = "{typeof(T)}") =>
@@ -772,11 +767,11 @@ private void EmitCheckForNullArgument_WithBlankLine(string argName, bool useFull
772767
: Identifier.ArgumentNullException;
773768

774769
_writer.WriteBlock($$"""
775-
if ({{argName}} is null)
776-
{
777-
throw new {{exceptionTypeDisplayString}}(nameof({{argName}}));
778-
}
779-
""");
770+
if ({{argName}} is null)
771+
{
772+
throw new {{exceptionTypeDisplayString}}(nameof({{argName}}));
773+
}
774+
""");
780775

781776
_writer.WriteBlankLine();
782777
}

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Helpers.cs

Lines changed: 1 addition & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics;
66
using System.Text;
77
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.Text;
89

910
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
1011
{
@@ -107,79 +108,5 @@ private static class TypeFullName
107108

108109
private static bool TypesAreEqual(ITypeSymbol first, ITypeSymbol second)
109110
=> first.Equals(second, SymbolEqualityComparer.Default);
110-
111-
private sealed class SourceWriter
112-
{
113-
private readonly StringBuilder _sb = new();
114-
private int _indentationLevel;
115-
116-
public int Length => _sb.Length;
117-
public int IndentationLevel => _indentationLevel;
118-
119-
public void WriteBlockStart(string? declaration = null)
120-
{
121-
if (declaration is not null)
122-
{
123-
WriteLine(declaration);
124-
}
125-
WriteLine("{");
126-
_indentationLevel++;
127-
}
128-
129-
public void WriteBlockEnd(string? extra = null)
130-
{
131-
_indentationLevel--;
132-
Debug.Assert(_indentationLevel > -1);
133-
WriteLine($"}}{extra}");
134-
}
135-
136-
public void WriteLine(string source)
137-
{
138-
_sb.Append(' ', 4 * _indentationLevel);
139-
_sb.AppendLine(source);
140-
}
141-
142-
public void WriteBlock(string source)
143-
{
144-
foreach (string value in source.Split('\n'))
145-
{
146-
string line = value.Trim();
147-
switch (line)
148-
{
149-
case "{":
150-
{
151-
WriteBlockStart();
152-
}
153-
break;
154-
case "}":
155-
{
156-
WriteBlockEnd();
157-
}
158-
break;
159-
case "":
160-
{
161-
WriteBlankLine();
162-
}
163-
break;
164-
default:
165-
{
166-
WriteLine(line);
167-
}
168-
break;
169-
}
170-
}
171-
}
172-
173-
public void WriteBlankLine() => _sb.AppendLine();
174-
175-
public void RemoveBlankLine()
176-
{
177-
int newLineLength = Environment.NewLine.Length;
178-
int lastNewLineStartIndex = Length - newLineLength;
179-
_sb.Remove(lastNewLineStartIndex, newLineLength);
180-
}
181-
182-
public string GetSource() => _sb.ToString();
183-
}
184111
}
185112
}

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
66
<UsingToolXliff>true</UsingToolXliff>
77
<AnalyzerLanguage>cs</AnalyzerLanguage>
8-
</PropertyGroup>
9-
10-
<PropertyGroup>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
119
<DefineConstants Condition="'$(LaunchDebugger)' == 'true'">$(DefineConstants);LAUNCH_DEBUGGER</DefineConstants>
1210
</PropertyGroup>
1311

@@ -34,6 +32,7 @@
3432
<Compile Include="PopulationStrategy.cs" />
3533
<Compile Include="PropertySpec.cs" />
3634
<Compile Include="SourceGenerationSpec.cs" />
35+
<Compile Include="SourceWriter.cs" />
3736
<Compile Include="TypeSpecKind.cs" />
3837
<Compile Include="TypeSpec.cs" />
3938
</ItemGroup>
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Text;
7+
using Microsoft.CodeAnalysis.Text;
8+
9+
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
10+
{
11+
internal sealed class SourceWriter
12+
{
13+
private readonly StringBuilder _sb = new();
14+
private int _indentationLevel;
15+
16+
public int Length => _sb.Length;
17+
public int IndentationLevel => _indentationLevel;
18+
19+
private static readonly char[] s_newLine = Environment.NewLine.ToCharArray();
20+
21+
public void WriteBlockStart(string? declaration = null)
22+
{
23+
if (declaration is not null)
24+
{
25+
WriteLine(declaration);
26+
}
27+
WriteLine("{");
28+
_indentationLevel++;
29+
}
30+
31+
public void WriteBlockEnd(string? extra = null)
32+
{
33+
_indentationLevel--;
34+
Debug.Assert(_indentationLevel > -1);
35+
WriteLine($"}}{extra}");
36+
}
37+
38+
public void WriteLine(string source)
39+
{
40+
_sb.Append(' ', 4 * _indentationLevel);
41+
_sb.AppendLine(source);
42+
}
43+
44+
public unsafe void WriteLine(ReadOnlySpan<char> source)
45+
{
46+
_sb.Append(' ', 4 * _indentationLevel);
47+
fixed (char* ptr = source)
48+
{
49+
_sb.Append(ptr, source.Length);
50+
WriteBlankLine();
51+
}
52+
}
53+
54+
public void WriteBlock(string source)
55+
{
56+
bool isFinalLine;
57+
ReadOnlySpan<char> remainingText = source.AsSpan();
58+
59+
do
60+
{
61+
ReadOnlySpan<char> line = GetNextLine(ref remainingText, out isFinalLine);
62+
switch (line)
63+
{
64+
case "{":
65+
{
66+
WriteBlockStart();
67+
}
68+
break;
69+
case "}":
70+
{
71+
WriteBlockEnd();
72+
}
73+
break;
74+
default:
75+
{
76+
WriteLine(line);
77+
}
78+
break;
79+
}
80+
} while (!isFinalLine);
81+
}
82+
83+
public void WriteBlankLine() => _sb.AppendLine();
84+
85+
public void RemoveBlankLine()
86+
{
87+
int newLineLength = s_newLine.Length;
88+
int lastNewLineStartIndex = Length - newLineLength;
89+
_sb.Remove(lastNewLineStartIndex, newLineLength);
90+
}
91+
92+
public SourceText ToSourceText()
93+
{
94+
Debug.Assert(_indentationLevel == 0 && _sb.Length > 0);
95+
return SourceText.From(_sb.ToString(), Encoding.UTF8);
96+
}
97+
98+
private static ReadOnlySpan<char> GetNextLine(ref ReadOnlySpan<char> remainingText, out bool isFinalLine)
99+
{
100+
if (remainingText.IsEmpty)
101+
{
102+
isFinalLine = true;
103+
return default;
104+
}
105+
106+
ReadOnlySpan<char> next;
107+
ReadOnlySpan<char> rest;
108+
109+
remainingText = remainingText.Trim();
110+
111+
int lineLength = remainingText.IndexOf(s_newLine);
112+
if (lineLength == -1)
113+
{
114+
lineLength = remainingText.Length;
115+
isFinalLine = true;
116+
rest = default;
117+
}
118+
else
119+
{
120+
rest = remainingText.Slice(lineLength + 1);
121+
isFinalLine = false;
122+
}
123+
124+
next = remainingText.Slice(0, lineLength);
125+
remainingText = rest;
126+
return next;
127+
}
128+
}
129+
}

src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestBindCallGen.generated.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
139139
{
140140
return true;
141141
}
142-
143142
return false;
144143
}
145144
}

src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestConfigureCallGen.generated.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
119119
{
120120
return true;
121121
}
122-
123122
return HasChildren(configuration);
124123
}
125124

@@ -129,7 +128,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
129128
{
130129
return true;
131130
}
132-
133131
return false;
134132
}
135133
}

0 commit comments

Comments
 (0)