Skip to content

Commit b0b7aae

Browse files
authored
Correct formatting of config binder generator (#83614)
* Correct formatting of config binder generator * Use span tokenization in multi-line emission logic
1 parent b3ec880 commit b0b7aae

12 files changed

+820
-466
lines changed

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

Lines changed: 274 additions & 164 deletions
Large diffs are not rendered by default.

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

Lines changed: 10 additions & 45 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
{
@@ -34,7 +35,7 @@ private static class ExceptionMessages
3435
public const string TypeNotSupported = "Unable to bind to type '{0}': '{1}'";
3536
}
3637

37-
private static class Literal
38+
private static class Identifier
3839
{
3940
public const string configuration = nameof(configuration);
4041
public const string element = nameof(element);
@@ -50,20 +51,26 @@ private static class Literal
5051

5152
public const string Add = nameof(Add);
5253
public const string Any = nameof(Any);
54+
public const string ArgumentNullException = nameof(ArgumentNullException);
55+
public const string Array = nameof(Array);
5356
public const string Bind = nameof(Bind);
5457
public const string BindCore = nameof(BindCore);
5558
public const string Configure = nameof(Configure);
5659
public const string CopyTo = nameof(CopyTo);
5760
public const string ContainsKey = nameof(ContainsKey);
5861
public const string Count = nameof(Count);
62+
public const string Enum = nameof(Enum);
5963
public const string GeneratedConfigurationBinder = nameof(GeneratedConfigurationBinder);
6064
public const string Get = nameof(Get);
6165
public const string GetChildren = nameof(GetChildren);
6266
public const string GetSection = nameof(GetSection);
6367
public const string HasChildren = nameof(HasChildren);
68+
public const string HasValueOrChildren = nameof(HasValueOrChildren);
6469
public const string HasValue = nameof(HasValue);
70+
public const string Helpers = nameof(Helpers);
6571
public const string IConfiguration = nameof(IConfiguration);
6672
public const string IConfigurationSection = nameof(IConfigurationSection);
73+
public const string Int32 = "int";
6774
public const string Length = nameof(Length);
6875
public const string Parse = nameof(Parse);
6976
public const string Resize = nameof(Resize);
@@ -87,61 +94,19 @@ private static class NotSupportedReason
8794

8895
private static class TypeFullName
8996
{
90-
public const string Array = "System.Array";
9197
public const string ConfigurationKeyNameAttribute = "Microsoft.Extensions.Configuration.ConfigurationKeyNameAttribute";
9298
public const string Dictionary = "System.Collections.Generic.Dictionary`2";
9399
public const string GenericIDictionary = "System.Collections.Generic.IDictionary`2";
94100
public const string HashSet = "System.Collections.Generic.HashSet`1";
95-
public const string ISet = "System.Collections.Generic.ISet`1";
96-
public const string IConfigurationSection = "Microsoft.Extensions.Configuration.IConfigurationSection";
97101
public const string IConfiguration = "Microsoft.Extensions.Configuration.IConfiguration";
102+
public const string IConfigurationSection = "Microsoft.Extensions.Configuration.IConfigurationSection";
98103
public const string IDictionary = "System.Collections.Generic.IDictionary";
104+
public const string ISet = "System.Collections.Generic.ISet`1";
99105
public const string IServiceCollection = "Microsoft.Extensions.DependencyInjection.IServiceCollection";
100106
public const string List = "System.Collections.Generic.List`1";
101107
}
102108

103109
private static bool TypesAreEqual(ITypeSymbol first, ITypeSymbol second)
104110
=> first.Equals(second, SymbolEqualityComparer.Default);
105-
106-
private enum InitializationKind
107-
{
108-
None = 0,
109-
SimpleAssignment = 1,
110-
AssignmentWithNullCheck = 2,
111-
Declaration = 3,
112-
}
113-
114-
private sealed class SourceWriter
115-
{
116-
private readonly StringBuilder _sb = new();
117-
private int _indentationLevel;
118-
119-
public int Length => _sb.Length;
120-
public int IndentationLevel => _indentationLevel;
121-
122-
public void WriteBlockStart(string declaration)
123-
{
124-
WriteLine(declaration);
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 WriteBlankLine() => _sb.AppendLine();
143-
144-
public string GetSource() => _sb.ToString();
145-
}
146111
}
147112
}

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

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,26 @@ public sealed partial class ConfigurationBindingSourceGenerator
1414
{
1515
private sealed class Parser
1616
{
17+
private const string GlobalNameSpaceString = "<global namespace>";
18+
1719
private readonly SourceProductionContext _context;
1820
private readonly KnownTypeData _typeData;
1921

2022
private readonly HashSet<TypeSpec> _typesForBindMethodGen = new();
2123
private readonly HashSet<TypeSpec> _typesForGetMethodGen = new();
2224
private readonly HashSet<TypeSpec> _typesForConfigureMethodGen = new();
25+
private readonly HashSet<TypeSpec> _typesForBindCoreMethodGen = new();
26+
2327
private readonly HashSet<ITypeSymbol> _unsupportedTypes = new(SymbolEqualityComparer.Default);
2428
private readonly Dictionary<ITypeSymbol, TypeSpec?> _createdSpecs = new(SymbolEqualityComparer.Default);
2529

30+
private readonly HashSet<string> _namespaces = new()
31+
{
32+
"System",
33+
"System.Linq",
34+
"Microsoft.Extensions.Configuration"
35+
};
36+
2637
public Parser(SourceProductionContext context, KnownTypeData typeData)
2738
{
2839
_context = context;
@@ -60,7 +71,15 @@ public Parser(SourceProductionContext context, KnownTypeData typeData)
6071
}
6172
}
6273

63-
return new SourceGenerationSpec(_typesForBindMethodGen, _typesForGetMethodGen, _typesForConfigureMethodGen);
74+
Dictionary<MethodSpecifier, HashSet<TypeSpec>> methods = new()
75+
{
76+
[MethodSpecifier.Bind] = _typesForBindMethodGen,
77+
[MethodSpecifier.Get] = _typesForGetMethodGen,
78+
[MethodSpecifier.Configure] = _typesForConfigureMethodGen,
79+
[MethodSpecifier.BindCore] = _typesForBindCoreMethodGen,
80+
};
81+
82+
return new SourceGenerationSpec(methods, _namespaces);
6483
}
6584

6685
private void ProcessBindCall(BinderInvocationOperation binderOperation)
@@ -76,10 +95,11 @@ private void ProcessBindCall(BinderInvocationOperation binderOperation)
7695
IConversionOperation argument = arguments[1].Value as IConversionOperation;
7796
ITypeSymbol? type = ResolveType(argument)?.WithNullableAnnotation(NullableAnnotation.None);
7897

79-
// TODO: do we need diagnostic for System.Object?
8098
if (type is not INamedTypeSymbol { } namedType ||
8199
namedType.SpecialType == SpecialType.System_Object ||
82-
namedType.SpecialType == SpecialType.System_Void)
100+
namedType.SpecialType == SpecialType.System_Void ||
101+
// Binding to root-level struct is a no-op.
102+
namedType.IsValueType)
83103
{
84104
return;
85105
}
@@ -153,7 +173,8 @@ private void ProcessConfigureCall(BinderInvocationOperation binderOperation)
153173
}
154174

155175
TypeSpec? spec = GetOrCreateTypeSpec(namedType, location);
156-
if (spec != null && !specs.Contains(spec))
176+
if (spec != null &&
177+
!specs.Contains(spec))
157178
{
158179
specs.Add(spec);
159180
}
@@ -191,24 +212,49 @@ private void ProcessConfigureCall(BinderInvocationOperation binderOperation)
191212
else if (type is IArrayTypeSymbol { } arrayType)
192213
{
193214
spec = CreateArraySpec(arrayType, location);
194-
return spec == null ? null : CacheSpec(spec);
215+
if (spec is null)
216+
{
217+
return null;
218+
}
219+
220+
if (spec.SpecKind != TypeSpecKind.ByteArray)
221+
{
222+
Debug.Assert(spec.SpecKind is TypeSpecKind.Array);
223+
_typesForBindCoreMethodGen.Add(spec);
224+
}
225+
226+
return CacheSpec(spec);
195227
}
196228
else if (TypesAreEqual(type, _typeData.SymbolForIConfigurationSection))
197229
{
198230
return CacheSpec(new TypeSpec(type) { Location = location, SpecKind = TypeSpecKind.IConfigurationSection });
199231
}
200232
else if (type is INamedTypeSymbol namedType)
201233
{
202-
return IsCollection(namedType)
203-
? CacheSpec(CreateCollectionSpec(namedType, location))
204-
: CacheSpec(CreateObjectSpec(namedType, location));
234+
spec = IsCollection(namedType)
235+
? CreateCollectionSpec(namedType, location)
236+
: CreateObjectSpec(namedType, location);
237+
238+
if (spec is null)
239+
{
240+
return null;
241+
}
242+
243+
_typesForBindCoreMethodGen.Add(spec);
244+
return CacheSpec(spec);
205245
}
206246

207247
ReportUnsupportedType(type, NotSupportedReason.TypeNotSupported, location);
208248
return null;
209249

210250
T CacheSpec<T>(T? s) where T : TypeSpec
211251
{
252+
string @namespace = s.Namespace;
253+
if (@namespace != null && @namespace != GlobalNameSpaceString)
254+
{
255+
_namespaces.Add(@namespace);
256+
}
257+
212258
_createdSpecs[type] = s;
213259
return s;
214260
}
@@ -528,7 +574,7 @@ private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol element)
528574
INamedTypeSymbol current = type;
529575
while (current != null)
530576
{
531-
if (current.GetMembers(Literal.Add).Any(member =>
577+
if (current.GetMembers(Identifier.Add).Any(member =>
532578
member is IMethodSymbol { Parameters.Length: 1 } method &&
533579
TypesAreEqual(element, method.Parameters[0].Type)))
534580
{
@@ -544,7 +590,7 @@ private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol element, ITy
544590
INamedTypeSymbol current = type;
545591
while (current != null)
546592
{
547-
if (current.GetMembers(Literal.Add).Any(member =>
593+
if (current.GetMembers(Identifier.Add).Any(member =>
548594
member is IMethodSymbol { Parameters.Length: 2 } method &&
549595
TypesAreEqual(key, method.Parameters[0].Type) &&
550596
TypesAreEqual(element, method.Parameters[1].Type)))

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConstructionStrategy.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
66
internal enum ConstructionStrategy
77
{
88
NotApplicable = 0,
9-
ParameterlessConstructor = 1,
9+
NotSupported = 1,
10+
ParameterlessConstructor = 2,
1011
}
1112
}

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: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,59 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Generic;
56

67
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
78
{
9+
810
internal sealed record SourceGenerationSpec(
9-
HashSet<TypeSpec> TypesForBindMethodGen,
10-
HashSet<TypeSpec> TypesForGetMethodGen,
11-
HashSet<TypeSpec> TypesForConfigureMethodGen);
11+
Dictionary<MethodSpecifier, HashSet<TypeSpec>> Methods,
12+
HashSet<string> Namespaces)
13+
{
14+
private MethodSpecifier? _methodsToGen;
15+
public MethodSpecifier MethodsToGen
16+
{
17+
get
18+
{
19+
if (!_methodsToGen.HasValue)
20+
{
21+
_methodsToGen = MethodSpecifier.None;
22+
23+
foreach (KeyValuePair<MethodSpecifier, HashSet<TypeSpec>> method in Methods)
24+
{
25+
if (method.Value.Count > 0)
26+
{
27+
MethodSpecifier specifier = method.Key;
28+
29+
if (specifier is MethodSpecifier.Configure or MethodSpecifier.Get)
30+
{
31+
_methodsToGen |= MethodSpecifier.HasValueOrChildren;
32+
}
33+
else if (specifier is MethodSpecifier.BindCore)
34+
{
35+
_methodsToGen |= MethodSpecifier.HasChildren;
36+
}
37+
38+
_methodsToGen |= specifier;
39+
}
40+
}
41+
}
42+
43+
return _methodsToGen.Value;
44+
}
45+
}
46+
}
47+
48+
[Flags]
49+
internal enum MethodSpecifier
50+
{
51+
None = 0x0,
52+
Bind = 0x1,
53+
Get = 0x2,
54+
Configure = 0x4,
55+
BindCore = 0x8,
56+
HasValueOrChildren = 0x10,
57+
HasChildren = 0x20,
58+
}
1259
}

0 commit comments

Comments
 (0)