Skip to content

Commit

Permalink
Correct formatting of config binder generator (#83614)
Browse files Browse the repository at this point in the history
* Correct formatting of config binder generator

* Use span tokenization in multi-line emission logic
  • Loading branch information
layomia authored Mar 22, 2023
1 parent b3ec880 commit b0b7aae
Show file tree
Hide file tree
Showing 12 changed files with 820 additions and 466 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
Expand Down Expand Up @@ -34,7 +35,7 @@ private static class ExceptionMessages
public const string TypeNotSupported = "Unable to bind to type '{0}': '{1}'";
}

private static class Literal
private static class Identifier
{
public const string configuration = nameof(configuration);
public const string element = nameof(element);
Expand All @@ -50,20 +51,26 @@ private static class Literal

public const string Add = nameof(Add);
public const string Any = nameof(Any);
public const string ArgumentNullException = nameof(ArgumentNullException);
public const string Array = nameof(Array);
public const string Bind = nameof(Bind);
public const string BindCore = nameof(BindCore);
public const string Configure = nameof(Configure);
public const string CopyTo = nameof(CopyTo);
public const string ContainsKey = nameof(ContainsKey);
public const string Count = nameof(Count);
public const string Enum = nameof(Enum);
public const string GeneratedConfigurationBinder = nameof(GeneratedConfigurationBinder);
public const string Get = nameof(Get);
public const string GetChildren = nameof(GetChildren);
public const string GetSection = nameof(GetSection);
public const string HasChildren = nameof(HasChildren);
public const string HasValueOrChildren = nameof(HasValueOrChildren);
public const string HasValue = nameof(HasValue);
public const string Helpers = nameof(Helpers);
public const string IConfiguration = nameof(IConfiguration);
public const string IConfigurationSection = nameof(IConfigurationSection);
public const string Int32 = "int";
public const string Length = nameof(Length);
public const string Parse = nameof(Parse);
public const string Resize = nameof(Resize);
Expand All @@ -87,61 +94,19 @@ private static class NotSupportedReason

private static class TypeFullName
{
public const string Array = "System.Array";
public const string ConfigurationKeyNameAttribute = "Microsoft.Extensions.Configuration.ConfigurationKeyNameAttribute";
public const string Dictionary = "System.Collections.Generic.Dictionary`2";
public const string GenericIDictionary = "System.Collections.Generic.IDictionary`2";
public const string HashSet = "System.Collections.Generic.HashSet`1";
public const string ISet = "System.Collections.Generic.ISet`1";
public const string IConfigurationSection = "Microsoft.Extensions.Configuration.IConfigurationSection";
public const string IConfiguration = "Microsoft.Extensions.Configuration.IConfiguration";
public const string IConfigurationSection = "Microsoft.Extensions.Configuration.IConfigurationSection";
public const string IDictionary = "System.Collections.Generic.IDictionary";
public const string ISet = "System.Collections.Generic.ISet`1";
public const string IServiceCollection = "Microsoft.Extensions.DependencyInjection.IServiceCollection";
public const string List = "System.Collections.Generic.List`1";
}

private static bool TypesAreEqual(ITypeSymbol first, ITypeSymbol second)
=> first.Equals(second, SymbolEqualityComparer.Default);

private enum InitializationKind
{
None = 0,
SimpleAssignment = 1,
AssignmentWithNullCheck = 2,
Declaration = 3,
}

private sealed class SourceWriter
{
private readonly StringBuilder _sb = new();
private int _indentationLevel;

public int Length => _sb.Length;
public int IndentationLevel => _indentationLevel;

public void WriteBlockStart(string declaration)
{
WriteLine(declaration);
WriteLine("{");
_indentationLevel++;
}

public void WriteBlockEnd(string? extra = null)
{
_indentationLevel--;
Debug.Assert(_indentationLevel > -1);
WriteLine($"}}{extra}");
}

public void WriteLine(string source)
{
_sb.Append(' ', 4 * _indentationLevel);
_sb.AppendLine(source);
}

public void WriteBlankLine() => _sb.AppendLine();

public string GetSource() => _sb.ToString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,26 @@ public sealed partial class ConfigurationBindingSourceGenerator
{
private sealed class Parser
{
private const string GlobalNameSpaceString = "<global namespace>";

private readonly SourceProductionContext _context;
private readonly KnownTypeData _typeData;

private readonly HashSet<TypeSpec> _typesForBindMethodGen = new();
private readonly HashSet<TypeSpec> _typesForGetMethodGen = new();
private readonly HashSet<TypeSpec> _typesForConfigureMethodGen = new();
private readonly HashSet<TypeSpec> _typesForBindCoreMethodGen = new();

private readonly HashSet<ITypeSymbol> _unsupportedTypes = new(SymbolEqualityComparer.Default);
private readonly Dictionary<ITypeSymbol, TypeSpec?> _createdSpecs = new(SymbolEqualityComparer.Default);

private readonly HashSet<string> _namespaces = new()
{
"System",
"System.Linq",
"Microsoft.Extensions.Configuration"
};

public Parser(SourceProductionContext context, KnownTypeData typeData)
{
_context = context;
Expand Down Expand Up @@ -60,7 +71,15 @@ public Parser(SourceProductionContext context, KnownTypeData typeData)
}
}

return new SourceGenerationSpec(_typesForBindMethodGen, _typesForGetMethodGen, _typesForConfigureMethodGen);
Dictionary<MethodSpecifier, HashSet<TypeSpec>> methods = new()
{
[MethodSpecifier.Bind] = _typesForBindMethodGen,
[MethodSpecifier.Get] = _typesForGetMethodGen,
[MethodSpecifier.Configure] = _typesForConfigureMethodGen,
[MethodSpecifier.BindCore] = _typesForBindCoreMethodGen,
};

return new SourceGenerationSpec(methods, _namespaces);
}

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

// TODO: do we need diagnostic for System.Object?
if (type is not INamedTypeSymbol { } namedType ||
namedType.SpecialType == SpecialType.System_Object ||
namedType.SpecialType == SpecialType.System_Void)
namedType.SpecialType == SpecialType.System_Void ||
// Binding to root-level struct is a no-op.
namedType.IsValueType)
{
return;
}
Expand Down Expand Up @@ -153,7 +173,8 @@ private void ProcessConfigureCall(BinderInvocationOperation binderOperation)
}

TypeSpec? spec = GetOrCreateTypeSpec(namedType, location);
if (spec != null && !specs.Contains(spec))
if (spec != null &&
!specs.Contains(spec))
{
specs.Add(spec);
}
Expand Down Expand Up @@ -191,24 +212,49 @@ private void ProcessConfigureCall(BinderInvocationOperation binderOperation)
else if (type is IArrayTypeSymbol { } arrayType)
{
spec = CreateArraySpec(arrayType, location);
return spec == null ? null : CacheSpec(spec);
if (spec is null)
{
return null;
}

if (spec.SpecKind != TypeSpecKind.ByteArray)
{
Debug.Assert(spec.SpecKind is TypeSpecKind.Array);
_typesForBindCoreMethodGen.Add(spec);
}

return CacheSpec(spec);
}
else if (TypesAreEqual(type, _typeData.SymbolForIConfigurationSection))
{
return CacheSpec(new TypeSpec(type) { Location = location, SpecKind = TypeSpecKind.IConfigurationSection });
}
else if (type is INamedTypeSymbol namedType)
{
return IsCollection(namedType)
? CacheSpec(CreateCollectionSpec(namedType, location))
: CacheSpec(CreateObjectSpec(namedType, location));
spec = IsCollection(namedType)
? CreateCollectionSpec(namedType, location)
: CreateObjectSpec(namedType, location);

if (spec is null)
{
return null;
}

_typesForBindCoreMethodGen.Add(spec);
return CacheSpec(spec);
}

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

T CacheSpec<T>(T? s) where T : TypeSpec
{
string @namespace = s.Namespace;
if (@namespace != null && @namespace != GlobalNameSpaceString)
{
_namespaces.Add(@namespace);
}

_createdSpecs[type] = s;
return s;
}
Expand Down Expand Up @@ -528,7 +574,7 @@ private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol element)
INamedTypeSymbol current = type;
while (current != null)
{
if (current.GetMembers(Literal.Add).Any(member =>
if (current.GetMembers(Identifier.Add).Any(member =>
member is IMethodSymbol { Parameters.Length: 1 } method &&
TypesAreEqual(element, method.Parameters[0].Type)))
{
Expand All @@ -544,7 +590,7 @@ private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol element, ITy
INamedTypeSymbol current = type;
while (current != null)
{
if (current.GetMembers(Literal.Add).Any(member =>
if (current.GetMembers(Identifier.Add).Any(member =>
member is IMethodSymbol { Parameters.Length: 2 } method &&
TypesAreEqual(key, method.Parameters[0].Type) &&
TypesAreEqual(element, method.Parameters[1].Type)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
internal enum ConstructionStrategy
{
NotApplicable = 0,
ParameterlessConstructor = 1,
NotSupported = 1,
ParameterlessConstructor = 2,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
<UsingToolXliff>true</UsingToolXliff>
<AnalyzerLanguage>cs</AnalyzerLanguage>
</PropertyGroup>

<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants Condition="'$(LaunchDebugger)' == 'true'">$(DefineConstants);LAUNCH_DEBUGGER</DefineConstants>
</PropertyGroup>

Expand All @@ -34,6 +32,7 @@
<Compile Include="PopulationStrategy.cs" />
<Compile Include="PropertySpec.cs" />
<Compile Include="SourceGenerationSpec.cs" />
<Compile Include="SourceWriter.cs" />
<Compile Include="TypeSpecKind.cs" />
<Compile Include="TypeSpec.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,59 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{

internal sealed record SourceGenerationSpec(
HashSet<TypeSpec> TypesForBindMethodGen,
HashSet<TypeSpec> TypesForGetMethodGen,
HashSet<TypeSpec> TypesForConfigureMethodGen);
Dictionary<MethodSpecifier, HashSet<TypeSpec>> Methods,
HashSet<string> Namespaces)
{
private MethodSpecifier? _methodsToGen;
public MethodSpecifier MethodsToGen
{
get
{
if (!_methodsToGen.HasValue)
{
_methodsToGen = MethodSpecifier.None;

foreach (KeyValuePair<MethodSpecifier, HashSet<TypeSpec>> method in Methods)
{
if (method.Value.Count > 0)
{
MethodSpecifier specifier = method.Key;

if (specifier is MethodSpecifier.Configure or MethodSpecifier.Get)
{
_methodsToGen |= MethodSpecifier.HasValueOrChildren;
}
else if (specifier is MethodSpecifier.BindCore)
{
_methodsToGen |= MethodSpecifier.HasChildren;
}

_methodsToGen |= specifier;
}
}
}

return _methodsToGen.Value;
}
}
}

[Flags]
internal enum MethodSpecifier
{
None = 0x0,
Bind = 0x1,
Get = 0x2,
Configure = 0x4,
BindCore = 0x8,
HasValueOrChildren = 0x10,
HasChildren = 0x20,
}
}
Loading

0 comments on commit b0b7aae

Please sign in to comment.