Skip to content

Commit

Permalink
Correct formatting of config binder generator
Browse files Browse the repository at this point in the history
  • Loading branch information
layomia committed Mar 21, 2023
1 parent 4bd7819 commit af4468f
Show file tree
Hide file tree
Showing 10 changed files with 738 additions and 429 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,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 +50,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,30 +93,21 @@ 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();
Expand All @@ -119,9 +116,12 @@ private sealed class SourceWriter
public int Length => _sb.Length;
public int IndentationLevel => _indentationLevel;

public void WriteBlockStart(string declaration)
public void WriteBlockStart(string? declaration = null)
{
WriteLine(declaration);
if (declaration is not null)
{
WriteLine(declaration);
}
WriteLine("{");
_indentationLevel++;
}
Expand All @@ -139,8 +139,46 @@ public void WriteLine(string source)
_sb.AppendLine(source);
}

public void WriteBlock(string source)
{
foreach (string value in source.Split('\n'))
{
string line = value.Trim();
switch (line)
{
case "{":
{
WriteBlockStart();
}
break;
case "}":
{
WriteBlockEnd();
}
break;
case "":
{
WriteBlankLine();
}
break;
default:
{
WriteLine(line);
}
break;
}
}
}

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

public void RemoveBlankLine()
{
int newLineLength = Environment.NewLine.Length;
int lastNewLineStartIndex = Length - newLineLength;
_sb.Remove(lastNewLineStartIndex, newLineLength);
}

public string GetSource() => _sb.ToString();
}
}
Expand Down
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
@@ -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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,26 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
internal record TypeSpec
{
private static readonly SymbolDisplayFormat s_minimalDisplayFormat = new SymbolDisplayFormat(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

public TypeSpec(ITypeSymbol type)
{
DisplayString = type.ToDisplayString();
FullyQualifiedDisplayString = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
MinimalDisplayString = type.ToDisplayString(s_minimalDisplayFormat);
Namespace = type.ContainingNamespace?.ToDisplayString();
SpecialType = type.SpecialType;
IsValueType = type.IsValueType;
}

public string DisplayString { get; }
public string FullyQualifiedDisplayString { get; }

public string MinimalDisplayString { get; }

public string? Namespace { get; }

public SpecialType SpecialType { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1255,7 +1255,6 @@ public void CanBindByteArray()
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(dic);
var config = configurationBuilder.Build();

var options = config.Get<ByteArrayOptions>();
Assert.Equal(bytes, options.MyByteArray);
}
Expand Down

0 comments on commit af4468f

Please sign in to comment.