Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct formatting of config binder generator #83614

Merged
merged 2 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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