diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Emitter.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Emitter.cs index 9edc39feb44bf..046b518f70a45 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Emitter.cs @@ -4,10 +4,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Text; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -20,40 +18,42 @@ private static class Expression public const string nullableSectionValue = "section?.Value"; public const string sectionKey = "section.Key"; public const string sectionValue = "section.Value"; + + public const string ConvertFromBase64String = "Convert.FromBase64String"; } - private static class GlobalName + private static class FullyQualifiedDisplayName { - public const string Enum = "global::System.Enum"; - public const string FromBase64String = "global::System.Convert.FromBase64String"; + public const string ArgumentNullException = "global::System.ArgumentNullException"; + public const string Helpers = $"global::{GeneratorProjectName}.{Identifier.Helpers}"; public const string IConfiguration = "global::Microsoft.Extensions.Configuration.IConfiguration"; - public const string IConfigurationSection = "global::Microsoft.Extensions.Configuration.IConfigurationSection"; - public const string Int32 = "int"; + public const string IConfigurationSection = IConfiguration + "Section"; + public const string InvalidOperationException = "global::System.InvalidOperationException"; public const string IServiceCollection = "global::Microsoft.Extensions.DependencyInjection.IServiceCollection"; - public const string Object = "object"; - public const string String = "string"; + public const string NotSupportedException = "global::System.NotSupportedException"; } - private static class KeyWord + private enum InitializationKind { - public const string @default = nameof(@default); - public const string @null = nameof(@null); + None = 0, + SimpleAssignment = 1, + AssignmentWithNullCheck = 2, + Declaration = 3, } private readonly SourceProductionContext _context; private readonly SourceGenerationSpec _generationSpec; - private readonly Queue _privateBindCoreMethodGen_QueuedTypes = new(); - - private readonly HashSet _internalBindMethodGen_ProcessedTypes = new(); - private readonly HashSet _privateBindCoreMethodGen_ProcessedTypes = new(); - // Postfix for stringValueX variables used to save config value indexer // results e.g. if (configuration["Key"] is string stringValue0) { ... } private int _parseValueCount; private readonly SourceWriter _writer = new(); + private readonly Regex _arrayBracketsRegex = new(Regex.Escape("[]")); + + private bool _useFullyQualifiedNames = true; + public Emitter(SourceProductionContext context, SourceGenerationSpec generationSpec) { _context = context; @@ -64,43 +64,42 @@ public void Emit() { _writer.WriteLine(@"// #nullable enable - -using System.Linq; "); - _writer.WriteBlockStart($"internal static class {Literal.GeneratedConfigurationBinder}"); - + // Generated binder for user consumption. + _writer.WriteBlockStart($"internal static class {Identifier.GeneratedConfigurationBinder}"); EmitConfigureMethod(); - EmitGetMethod(); - EmitBindMethods(); + _writer.WriteBlockEnd(); - EmitIConfigurationHasChildrenHelperMethod(); + _writer.WriteBlankLine(); - _writer.WriteBlockEnd(); + EmitGenerationNamespaceAndHelpers(); - SourceText source = SourceText.From(_writer.GetSource(), Encoding.UTF8); - _context.AddSource($"{Literal.GeneratedConfigurationBinder}.g.cs", source); + _context.AddSource($"{Identifier.GeneratedConfigurationBinder}.g.cs", _writer.ToSourceText()); } private void EmitConfigureMethod() { - if (_generationSpec.TypesForConfigureMethodGen.Count == 0) + if (!IncludeMethodsForGen(MethodSpecifier.Configure)) { return; } - _writer.WriteBlockStart($"public static {GlobalName.IServiceCollection} {Literal.Configure}(this {GlobalName.IServiceCollection} {Literal.services}, {GlobalName.IConfiguration} {Literal.configuration})"); + _writer.WriteBlockStart($"public static {FullyQualifiedDisplayName.IServiceCollection} {Identifier.Configure}(this {FullyQualifiedDisplayName.IServiceCollection} {Identifier.services}, {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration})"); - foreach (TypeSpec type in _generationSpec.TypesForConfigureMethodGen) + EmitCheckForNullArgument_WithBlankLine(Identifier.configuration, useFullyQualifiedNames: true); + + foreach (TypeSpec type in _generationSpec.Methods[MethodSpecifier.Configure]) { - string typeDisplayString = type.DisplayString; + string typeDisplayString = type.FullyQualifiedDisplayString; _writer.WriteBlockStart($"if (typeof(T) == typeof({typeDisplayString}))"); - _writer.WriteBlockStart($@"return {Literal.services}.{Literal.Configure}<{typeDisplayString}>({Literal.obj} =>"); - EmitBindLogicFromIConfiguration(type, Literal.obj, InitializationKind.None); + _writer.WriteBlockStart($@"return {Identifier.services}.{Identifier.Configure}<{typeDisplayString}>({Identifier.obj} =>"); + EmitIConfigurationHasValueOrChildrenCheck(); + EmitBindLogicFromIConfiguration(type, Identifier.obj, InitializationKind.None); _writer.WriteBlockEnd(");"); _writer.WriteBlockEnd(); @@ -109,91 +108,113 @@ private void EmitConfigureMethod() Emit_NotSupportedException_UnableToBindType(NotSupportedReason.TypeNotDetectedAsInput); _writer.WriteBlockEnd(); - - _writer.WriteBlankLine(); } private void EmitGetMethod() { - if (_generationSpec.TypesForGetMethodGen.Count == 0) + if (!IncludeMethodsForGen(MethodSpecifier.Get)) { return; } - _writer.WriteBlockStart($"public static T? {Literal.Get}(this {GlobalName.IConfiguration} {Literal.configuration})"); + if (IncludeMethodsForGen(MethodSpecifier.Configure)) + { + _writer.WriteBlankLine(); + } + + _writer.WriteBlockStart($"public static T? {Identifier.Get}(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration})"); + + EmitCheckForNullArgument_WithBlankLine(Identifier.configuration, useFullyQualifiedNames: true); - EmitCheckForNullArgument_WithBlankLine(Literal.configuration); + EmitIConfigurationHasValueOrChildrenCheck(); - foreach (TypeSpec type in _generationSpec.TypesForGetMethodGen) + foreach (TypeSpec type in _generationSpec.Methods[MethodSpecifier.Get]) { - string typeDisplayString = type.DisplayString; + string typeDisplayString = type.FullyQualifiedDisplayString; _writer.WriteBlockStart($"if (typeof(T) == typeof({typeDisplayString}))"); - EmitBindLogicFromIConfiguration(type, Literal.obj, InitializationKind.Declaration); - _writer.WriteLine($"return (T)({GlobalName.Object}){Literal.obj};"); + EmitBindLogicFromIConfiguration(type, Identifier.obj, InitializationKind.Declaration); + _writer.WriteLine($"return (T)(object){Identifier.obj};"); _writer.WriteBlockEnd(); _writer.WriteBlankLine(); } Emit_NotSupportedException_UnableToBindType(NotSupportedReason.TypeNotDetectedAsInput); _writer.WriteBlockEnd(); - _writer.WriteBlankLine(); } private void EmitBindMethods() { - if (_generationSpec.TypesForBindMethodGen.Count > 0) + if (!IncludeMethodsForGen(MethodSpecifier.Bind)) { - foreach (TypeSpec type in _generationSpec.TypesForBindMethodGen) - { - EmitBindMethod(type); - } + return; } - // Get & Configure method generation might have queued types for private BindCore impl - while (_privateBindCoreMethodGen_QueuedTypes.Count > 0) + if (IncludeMethodsForGen(MethodSpecifier.Configure | MethodSpecifier.Get)) { - EmitBindCoreMethod(_privateBindCoreMethodGen_QueuedTypes.Dequeue()); + _writer.WriteBlankLine(); + } + + foreach (TypeSpec type in _generationSpec.Methods[MethodSpecifier.Bind]) + { + EmitBindMethod(type); + _writer.WriteBlankLine(); } + + _writer.RemoveBlankLine(); } private void EmitBindMethod(TypeSpec type) { - if (_internalBindMethodGen_ProcessedTypes.Contains(type)) - { - return; - } + _writer.WriteLine( + @$"public static void {Identifier.Bind}(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, {type.FullyQualifiedDisplayString} {Identifier.obj}) => " + + $"{FullyQualifiedDisplayName.Helpers}.{Identifier.BindCore}({Identifier.configuration}, ref {Identifier.obj});"); + } - _internalBindMethodGen_ProcessedTypes.Add(type); + private void EmitGenerationNamespaceAndHelpers() + { + _useFullyQualifiedNames = false; - // Binding to root level struct is a no-op. - // TODO: maybe this should be a debug assert & the parser shouldn't include them. - if (type.IsValueType) + if (IncludeMethodsForGen(MethodSpecifier.BindCore | MethodSpecifier.HasValueOrChildren | MethodSpecifier.HasChildren)) { - return; - } + // Helper class in source-generation namespace. + _writer.WriteBlockStart($"namespace {GeneratorProjectName}"); + EmitHelperUsingStatements(); - _privateBindCoreMethodGen_QueuedTypes.Enqueue(type); + _writer.WriteBlankLine(); - _writer.WriteLine( - @$"public static void {Literal.Bind}(this {GlobalName.IConfiguration} {Literal.configuration}, {type.DisplayString} {Literal.obj}) => " + - $"{Literal.BindCore}({Literal.configuration}, ref {Literal.obj});"); - _writer.WriteBlankLine(); + _writer.WriteBlockStart($"internal static class {Identifier.Helpers}"); + EmitBindCoreMethods(); + EmitHelperMethods(); + _writer.WriteBlockEnd(); + + _writer.WriteBlockEnd(); + } } - private void EmitBindCoreMethod(TypeSpec type) + private void EmitHelperUsingStatements() { - if (_privateBindCoreMethodGen_ProcessedTypes.Contains(type)) + foreach (string @namespace in _generationSpec.Namespaces) { - return; + _writer.WriteLine($"using {@namespace};"); } - _privateBindCoreMethodGen_ProcessedTypes.Add(type); + } - string objParameterExpression = $"ref {type.DisplayString} {Literal.obj}"; - _writer.WriteBlockStart(@$"private static void {Literal.BindCore}({GlobalName.IConfiguration} {Literal.configuration}, {objParameterExpression})"); + private void EmitBindCoreMethods() + { + foreach (TypeSpec type in _generationSpec.Methods[MethodSpecifier.BindCore]) + { + EmitBindCoreMethod(type); + _writer.WriteBlankLine(); + } + } + + private void EmitBindCoreMethod(TypeSpec type) + { + string objParameterExpression = $"ref {type.MinimalDisplayString} {Identifier.obj}"; + _writer.WriteBlockStart(@$"public static void {Identifier.BindCore}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression})"); EmitBindCoreImpl(type); _writer.WriteBlockEnd(); - _writer.WriteBlankLine(); } private void EmitBindCoreImpl(TypeSpec type) @@ -208,7 +229,7 @@ private void EmitBindCoreImpl(TypeSpec type) case TypeSpecKind.IConfigurationSection: { EmitCastToIConfigurationSection(); - EmitAssignment(Literal.obj, Literal.section); + EmitAssignment(Identifier.obj, Identifier.section); } break; case TypeSpecKind.Dictionary: @@ -244,15 +265,17 @@ private void EmitBindCoreImplForArray(EnumerableSpec type) EmitCheckForNullArgument_WithBlankLine_IfRequired(isValueType: false); - string tempVarName = GetIncrementalVarName(Literal.temp); + string tempVarName = GetIncrementalVarName(Identifier.temp); // Create and bind to temp list - EmitBindCoreCall(concreteType, tempVarName, Literal.configuration, InitializationKind.Declaration); + EmitBindCoreCall(concreteType, tempVarName, Identifier.configuration, InitializationKind.Declaration); // Resize array and copy fill with additional - EmitAssignment($"{GlobalName.Int32} {Literal.originalCount}", $"{Literal.obj}.{Literal.Length}"); - _writer.WriteLine($"{TypeFullName.Array}.{Literal.Resize}(ref {Literal.obj}, {Literal.originalCount} + {tempVarName}.{Literal.Count});"); - _writer.WriteLine($"{tempVarName}.{Literal.CopyTo}({Literal.obj}, {Literal.originalCount});"); + _writer.WriteBlock($$""" + {{Identifier.Int32}} {{Identifier.originalCount}} = {{Identifier.obj}}.{{Identifier.Length}}; + {{Identifier.Array}}.{{Identifier.Resize}}(ref {{Identifier.obj}}, {{Identifier.originalCount}} + {{tempVarName}}.{{Identifier.Count}}); + {{tempVarName}}.{{Identifier.CopyTo}}({{Identifier.obj}}, {{Identifier.originalCount}}); + """); } private void EmitBindCoreImplForDictionary(DictionarySpec type) @@ -262,14 +285,14 @@ private void EmitBindCoreImplForDictionary(DictionarySpec type) TypeSpec keyType = type.KeyType; TypeSpec elementType = type.ElementType; - EmitVarDeclaration(keyType, Literal.key); + EmitVarDeclaration(keyType, Identifier.key); - _writer.WriteBlockStart($"foreach ({TypeFullName.IConfigurationSection} {Literal.section} in {Literal.configuration}.{Literal.GetChildren}())"); + _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())"); // Parse key EmitBindLogicFromString( keyType, - Literal.key, + Identifier.key, expressionForConfigStringValue: Expression.sectionKey, writeExtraOnSuccess: Emit_BindAndAddLogic_ForElement); @@ -278,21 +301,21 @@ void Emit_BindAndAddLogic_ForElement() // For simple types: do regular dictionary add if (elementType.SpecKind == TypeSpecKind.StringBasedParse) { - EmitVarDeclaration(elementType, Literal.element); + EmitVarDeclaration(elementType, Identifier.element); EmitBindLogicFromIConfigurationSectionValue( elementType, - Literal.element, + Identifier.element, InitializationKind.SimpleAssignment, - writeExtraOnSuccess: () => EmitAssignment($"{Literal.obj}[{Literal.key}]", Literal.element)); + writeExtraOnSuccess: () => EmitAssignment($"{Identifier.obj}[{Identifier.key}]", Identifier.element)); } else // For complex types: { - string displayString = elementType.DisplayString + (elementType.IsValueType ? string.Empty : "?"); + string displayString = elementType.MinimalDisplayString + (elementType.IsValueType ? string.Empty : "?"); // If key already exists, bind to value to existing element instance if not null (for ref types) - string conditionToUseExistingElement = $"if ({Literal.obj}.{Literal.TryGetValue}({Literal.key}, out {displayString} {Literal.element})"; + string conditionToUseExistingElement = $"if ({Identifier.obj}.{Identifier.TryGetValue}({Identifier.key}, out {displayString} {Identifier.element})"; conditionToUseExistingElement += !elementType.IsValueType - ? $" && {Literal.element} is not {KeyWord.@null})" + ? $" && {Identifier.element} is not null)" : ")"; _writer.WriteBlockStart(conditionToUseExistingElement); EmitBindLogicForElement(InitializationKind.None); @@ -305,8 +328,8 @@ void Emit_BindAndAddLogic_ForElement() void EmitBindLogicForElement(InitializationKind initKind) { - EmitBindLogicFromIConfigurationSectionValue(elementType, Literal.element, initKind); - EmitAssignment($"{Literal.obj}[{Literal.key}]", Literal.element); + EmitBindLogicFromIConfigurationSectionValue(elementType, Identifier.element, initKind); + EmitAssignment($"{Identifier.obj}[{Identifier.key}]", Identifier.element); } } } @@ -321,25 +344,25 @@ private void EmitBindCoreImplForEnumerable(EnumerableSpec type) TypeSpec elementType = type.ElementType; - EmitVarDeclaration(elementType, Literal.element); - _writer.WriteBlockStart($"foreach ({TypeFullName.IConfigurationSection} {Literal.section} in {Literal.configuration}.{Literal.GetChildren}())"); + EmitVarDeclaration(elementType, Identifier.element); + _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())"); EmitBindLogicFromIConfigurationSectionValue( elementType, - Literal.element, + Identifier.element, InitializationKind.SimpleAssignment, writeExtraOnSuccess: EmitAddLogicForElement); void EmitAddLogicForElement() { - string addExpression = $"{Literal.obj}.{Literal.Add}({Literal.element})"; + string addExpression = $"{Identifier.obj}.{Identifier.Add}({Identifier.element})"; if (elementType.IsValueType) { _writer.WriteLine($"{addExpression};"); } else { - _writer.WriteLine($"if ({Literal.element} is not {KeyWord.@null}) {{ {addExpression}; }}"); + _writer.WriteLine($"if ({Identifier.element} is not null) {{ {addExpression}; }}"); } } @@ -348,6 +371,12 @@ void EmitAddLogicForElement() private void EmitBindCoreImplForObject(ObjectSpec type) { + List properties = type.Properties; + if (properties.Count == 0) + { + return; + } + EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType); foreach (PropertySpec property in type.Properties) @@ -357,17 +386,19 @@ private void EmitBindCoreImplForObject(ObjectSpec type) EmitBindCoreImplForProperty(property, propertyType, parentType: type); _writer.WriteBlankLine(); } + + _writer.RemoveBlankLine(); } private void EmitBindCoreImplForProperty(PropertySpec property, TypeSpec propertyType, TypeSpec parentType) { string configurationKeyName = property.ConfigurationKeyName; - string propertyParentReference = property.IsStatic ? parentType.DisplayString : Literal.obj; + string propertyParentReference = property.IsStatic ? parentType.MinimalDisplayString : Identifier.obj; string expressionForPropertyAccess = $"{propertyParentReference}.{property.Name}"; - string expressionForConfigSectionAccess = $@"{Literal.configuration}.{Literal.GetSection}(""{configurationKeyName}"")"; - string expressionForConfigValueIndexer = $@"{Literal.configuration}[""{configurationKeyName}""]"; + string expressionForConfigSectionAccess = $@"{Identifier.configuration}.{Identifier.GetSection}(""{configurationKeyName}"")"; + string expressionForConfigValueIndexer = $@"{Identifier.configuration}[""{configurationKeyName}""]"; bool canGet = property.CanGet; bool canSet = property.CanSet; @@ -427,25 +458,20 @@ private void EmitBindLogicFromIConfiguration(TypeSpec type, string expressionFor { if (type.SpecKind is TypeSpecKind.StringBasedParse or TypeSpecKind.ByteArray) { - EmitCastToIConfigurationSection(); if (initKind is InitializationKind.Declaration) { - EmitAssignment($"{type.DisplayString} {expressionForMemberAccess}", KeyWord.@default); + EmitCastToIConfigurationSection(); + EmitAssignment($"{GetTypeDisplayString(type)} {expressionForMemberAccess}", "default"); + } + else + { + EmitCastToIConfigurationSection(); } EmitBindLogicFromString(type, expressionForMemberAccess, Expression.sectionValue); } else { - if (initKind is InitializationKind.Declaration) - { - EmitAssignment($"{TypeFullName.IConfigurationSection}? {Literal.section}", $"{Literal.configuration} as {TypeFullName.IConfigurationSection}"); - _writer.WriteBlockStart($"if ({Expression.nullableSectionValue} is null && !{Literal.configuration}.{Literal.GetChildren}().{Literal.Any}())"); - _writer.WriteLine($"return {KeyWord.@default};"); - _writer.WriteBlockEnd(); - _writer.WriteBlankLine(); - } - - EmitBindCoreCall(type, expressionForMemberAccess, Literal.configuration, initKind); + EmitBindCoreCall(type, expressionForMemberAccess, Identifier.configuration, initKind); } } @@ -457,7 +483,7 @@ private void EmitBindLogicFromIConfigurationSectionValue(TypeSpec type, string e } else { - EmitBindCoreCall(type, expressionForMemberAccess, Literal.section, initKind); + EmitBindCoreCall(type, expressionForMemberAccess, Identifier.section, initKind); writeExtraOnSuccess?.Invoke(); } } @@ -468,26 +494,30 @@ private void EmitBindCoreCall( string expressionForConfigArg, InitializationKind initKind) { - string tempVarName = GetIncrementalVarName(Literal.temp); + string tempVarName = GetIncrementalVarName(Identifier.temp); if (initKind is InitializationKind.AssignmentWithNullCheck) { - EmitAssignment($"{type.DisplayString} {tempVarName}", $"{expressionForMemberAccess}"); + EmitAssignment($"{type.MinimalDisplayString} {tempVarName}", $"{expressionForMemberAccess}"); EmitObjectInit(type, tempVarName, InitializationKind.AssignmentWithNullCheck); - _writer.WriteLine($@"{Literal.BindCore}({expressionForConfigArg}, ref {tempVarName});"); + EmitBindCoreCall(tempVarName); } else if (initKind is InitializationKind.None && type.IsValueType) { EmitObjectInit(type, tempVarName, InitializationKind.Declaration); - _writer.WriteLine($@"{Literal.BindCore}({expressionForConfigArg}, ref {tempVarName});"); + _writer.WriteLine($@"{Identifier.BindCore}({expressionForConfigArg}, ref {tempVarName});"); EmitAssignment(expressionForMemberAccess, tempVarName); } else { EmitObjectInit(type, expressionForMemberAccess, initKind); - _writer.WriteLine($@"{Literal.BindCore}({expressionForConfigArg}, ref {expressionForMemberAccess});"); + EmitBindCoreCall(expressionForMemberAccess); } - _privateBindCoreMethodGen_QueuedTypes.Enqueue(type); + void EmitBindCoreCall(string varName) + { + string bindCoreCall = $@"{GetHelperMethodDisplayString(Identifier.BindCore)}({expressionForConfigArg}, ref {varName});"; + _writer.WriteLine(bindCoreCall); + } } private void EmitBindCoreCallForProperty( @@ -496,14 +526,15 @@ private void EmitBindCoreCallForProperty( string expressionForPropertyAccess, string expressionForConfigSectionAccess) { - string bindCoreConfigArg = GetIncrementalVarName(Literal.section); - EmitAssignment($"{GlobalName.IConfigurationSection} {bindCoreConfigArg}", expressionForConfigSectionAccess); - _writer.WriteBlockStart($"if ({Literal.HasChildren}({bindCoreConfigArg}))"); + string bindCoreConfigArg = GetIncrementalVarName(Identifier.section); + EmitAssignment($"{Identifier.IConfigurationSection} {bindCoreConfigArg}", expressionForConfigSectionAccess); + _writer.WriteBlockStart($"if ({Identifier.HasChildren}({bindCoreConfigArg}))"); bool canGet = property.CanGet; bool canSet = property.CanSet; + string effectivePropertyTypeDisplayString = effectivePropertyType.MinimalDisplayString; - string tempVarName = GetIncrementalVarName(Literal.temp); + string tempVarName = GetIncrementalVarName(Identifier.temp); if (effectivePropertyType.IsValueType) { if (canSet) @@ -513,16 +544,16 @@ private void EmitBindCoreCallForProperty( TypeSpec actualPropertyType = property.Type; if (actualPropertyType.SpecKind is TypeSpecKind.Nullable) { - string nullableTempVarName = GetIncrementalVarName(Literal.temp); + string nullableTempVarName = GetIncrementalVarName(Identifier.temp); EmitAssignment( - $"{actualPropertyType.DisplayString} {nullableTempVarName}", expressionForPropertyAccess); + $"{actualPropertyType.MinimalDisplayString} {nullableTempVarName}", expressionForPropertyAccess); EmitAssignment( - $"{effectivePropertyType.DisplayString} {tempVarName}", - $"{nullableTempVarName}.{Literal.HasValue} ? {nullableTempVarName}.{Literal.Value} : new {effectivePropertyType.DisplayString}()"); + $"{effectivePropertyTypeDisplayString} {tempVarName}", + $"{nullableTempVarName}.{Identifier.HasValue} ? {nullableTempVarName}.{Identifier.Value} : new {effectivePropertyTypeDisplayString}()"); } else { - EmitAssignment($"{effectivePropertyType.DisplayString} {tempVarName}", $"{expressionForPropertyAccess}"); + EmitAssignment($"{effectivePropertyTypeDisplayString} {tempVarName}", $"{expressionForPropertyAccess}"); } } else @@ -530,16 +561,15 @@ private void EmitBindCoreCallForProperty( EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.Declaration); } - _writer.WriteLine($@"{Literal.BindCore}({bindCoreConfigArg}, ref {tempVarName});"); + _writer.WriteLine($@"{Identifier.BindCore}({bindCoreConfigArg}, ref {tempVarName});"); EmitAssignment(expressionForPropertyAccess, tempVarName); - _privateBindCoreMethodGen_QueuedTypes.Enqueue(effectivePropertyType); } } else if (canGet) { - EmitAssignment($"{effectivePropertyType.DisplayString} {tempVarName}", $"{expressionForPropertyAccess}"); + EmitAssignment($"{effectivePropertyTypeDisplayString} {tempVarName}", $"{expressionForPropertyAccess}"); EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.AssignmentWithNullCheck); - _writer.WriteLine($@"{Literal.BindCore}({bindCoreConfigArg}, ref {tempVarName});"); + _writer.WriteLine($@"{Identifier.BindCore}({bindCoreConfigArg}, ref {tempVarName});"); if (canSet) { @@ -550,13 +580,11 @@ private void EmitBindCoreCallForProperty( { Debug.Assert(canSet); EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.Declaration); - _writer.WriteLine($@"{Literal.BindCore}({bindCoreConfigArg}, ref {tempVarName});"); + _writer.WriteLine($@"{Identifier.BindCore}({bindCoreConfigArg}, ref {tempVarName});"); EmitAssignment(expressionForPropertyAccess, tempVarName); } _writer.WriteBlockEnd(); - - _privateBindCoreMethodGen_QueuedTypes.Enqueue(effectivePropertyType); } private void EmitBindLogicFromString( @@ -565,9 +593,9 @@ private void EmitBindLogicFromString( string expressionForConfigStringValue, Action? writeExtraOnSuccess = null) { - string typeDisplayString = type.DisplayString; - string stringValueVarName = GetIncrementalVarName(Literal.stringValue); - string assignmentCondition = $"{expressionForConfigStringValue} is {GlobalName.String} {stringValueVarName}"; + string typeDisplayString = type.FullyQualifiedDisplayString; + string stringValueVarName = GetIncrementalVarName(Identifier.stringValue); + string assignmentCondition = $"{expressionForConfigStringValue} is string {stringValueVarName}"; string rhs; if (type.SpecialType != SpecialType.None) @@ -575,19 +603,19 @@ private void EmitBindLogicFromString( rhs = type.SpecialType switch { SpecialType.System_String => stringValueVarName, - SpecialType.System_Object => KeyWord.@default, - _ => $"{typeDisplayString}.{Literal.Parse}({stringValueVarName})" + SpecialType.System_Object => "default", + _ => $"{typeDisplayString}.{Identifier.Parse}({stringValueVarName})" }; } else if (type.SpecKind == TypeSpecKind.Enum) { - string enumValueVarName = GetIncrementalVarName(Literal.enumValue); - assignmentCondition += $" && {GlobalName.Enum}.{Literal.TryParse}({stringValueVarName}, true, out {typeDisplayString} {enumValueVarName})"; + string enumValueVarName = GetIncrementalVarName(Identifier.enumValue); + assignmentCondition += $" && {Identifier.Enum}.{Identifier.TryParse}({stringValueVarName}, true, out {typeDisplayString} {enumValueVarName})"; rhs = enumValueVarName; } else if (type.SpecKind == TypeSpecKind.ByteArray) { - rhs = $"{GlobalName.FromBase64String}({stringValueVarName})"; + rhs = $"{Expression.ConvertFromBase64String}({stringValueVarName})"; } else { @@ -607,12 +635,12 @@ private void EmitObjectInit(TypeSpec type, string expressionForMemberAccess, Ini return; } - string displayString = type.DisplayString; + string displayString = GetTypeDisplayString(type); + string expressionForInit = null; if (type is EnumerableSpec { SpecKind: TypeSpecKind.Array } arrayType) { - Regex regex = new(Regex.Escape("[]")); - expressionForInit = $"new {regex.Replace(type.DisplayString, "[0]", 1)};"; + expressionForInit = $"new {_arrayBracketsRegex.Replace(displayString, "[0]", 1)};"; } else if (type.ConstructionStrategy != ConstructionStrategy.ParameterlessConstructor) { @@ -620,7 +648,7 @@ private void EmitObjectInit(TypeSpec type, string expressionForMemberAccess, Ini } else if (type is CollectionSpec { ConcreteType: { } concreteType }) { - displayString = concreteType.DisplayString; + displayString = GetTypeDisplayString(concreteType); } // Not an array. @@ -629,7 +657,7 @@ private void EmitObjectInit(TypeSpec type, string expressionForMemberAccess, Ini if (initKind == InitializationKind.Declaration) { Debug.Assert(!expressionForMemberAccess.Contains(".")); - EmitAssignment($"{displayString} {expressionForMemberAccess}", expressionForInit); + EmitAssignment($"var {expressionForMemberAccess}", expressionForInit); } else if (initKind == InitializationKind.AssignmentWithNullCheck) { @@ -641,47 +669,129 @@ private void EmitObjectInit(TypeSpec type, string expressionForMemberAccess, Ini } } - private void EmitCastToIConfigurationSection() + private void EmitIConfigurationHasValueOrChildrenCheck() { - _writer.WriteBlockStart($"if ({Literal.configuration} is not {TypeFullName.IConfigurationSection} {Literal.section})"); - _writer.WriteLine("throw new global::System.InvalidOperationException();"); - _writer.WriteBlockEnd(); + _writer.WriteBlock($$""" + if (!{{GetHelperMethodDisplayString(Identifier.HasValueOrChildren)}}({{Identifier.configuration}})) + { + return default; + } + """); + _writer.WriteBlankLine(); } - private void EmitIConfigurationHasChildrenHelperMethod() + private void EmitHelperMethods() { - _writer.WriteBlockStart($"public static bool {Literal.HasChildren}({GlobalName.IConfiguration} {Literal.configuration})"); - _writer.WriteBlockStart($"foreach ({GlobalName.IConfigurationSection} {Literal.section} in {Literal.configuration}.{Literal.GetChildren}())"); - _writer.WriteLine($"return true;"); - _writer.WriteBlockEnd(); - _writer.WriteLine($"return false;"); - _writer.WriteBlockEnd(); + if (IncludeMethodsForGen(MethodSpecifier.HasValueOrChildren)) + { + EmitHasValueOrChildrenMethod(); + _writer.WriteBlankLine(); + EmitHasChildrenMethod(); + } + else if (IncludeMethodsForGen(MethodSpecifier.HasChildren)) + { + EmitHasChildrenMethod(); + } + } + + private void EmitHasValueOrChildrenMethod() + { + _writer.WriteBlock($$""" + public static bool {{Identifier.HasValueOrChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}}) + { + if (({{Identifier.configuration}} as {{Identifier.IConfigurationSection}})?.{{Identifier.Value}} is not null) + { + return true; + } + return {{Identifier.HasChildren}}({{Identifier.configuration}}); + } + """); + } + + private void EmitHasChildrenMethod() + { + _writer.WriteBlock($$""" + public static bool {{Identifier.HasChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}}) + { + foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}()) + { + return true; + } + return false; + } + """); } - private void EmitVarDeclaration(TypeSpec type, string varName) => _writer.WriteLine($"{type.DisplayString} {varName};"); + private void EmitVarDeclaration(TypeSpec type, string varName) => _writer.WriteLine($"{type.MinimalDisplayString} {varName};"); private void EmitAssignment(string lhsSource, string rhsSource) => _writer.WriteLine($"{lhsSource} = {rhsSource};"); + private void EmitCastToIConfigurationSection() + { + string sectionTypeDisplayString; + string exceptionTypeDisplayString; + if (_useFullyQualifiedNames) + { + sectionTypeDisplayString = FullyQualifiedDisplayName.IConfigurationSection; + exceptionTypeDisplayString = FullyQualifiedDisplayName.InvalidOperationException; + } + else + { + sectionTypeDisplayString = Identifier.IConfigurationSection; + exceptionTypeDisplayString = nameof(InvalidOperationException); + } + + _writer.WriteBlock($$""" + if ({{Identifier.configuration}} is not {{sectionTypeDisplayString}} {{Identifier.section}}) + { + throw new {{exceptionTypeDisplayString}}(); + } + """); + } + private void Emit_NotSupportedException_UnableToBindType(string reason, string typeDisplayString = "{typeof(T)}") => - _writer.WriteLine(@$"throw new global::System.NotSupportedException($""{string.Format(ExceptionMessages.TypeNotSupported, typeDisplayString, reason)}"");"); + _writer.WriteLine(@$"throw new {FullyQualifiedDisplayName.NotSupportedException}($""{string.Format(ExceptionMessages.TypeNotSupported, typeDisplayString, reason)}"");"); private void EmitCheckForNullArgument_WithBlankLine_IfRequired(bool isValueType) { if (!isValueType) { - EmitCheckForNullArgument_WithBlankLine(Literal.obj); + EmitCheckForNullArgument_WithBlankLine(Identifier.obj); } } - private void EmitCheckForNullArgument_WithBlankLine(string argName) + private void EmitCheckForNullArgument_WithBlankLine(string argName, bool useFullyQualifiedNames = false) { - _writer.WriteBlockStart($"if ({argName} is {KeyWord.@null})"); - _writer.WriteLine($"throw new global::System.ArgumentNullException(nameof({argName}));"); - _writer.WriteBlockEnd(); + string exceptionTypeDisplayString = useFullyQualifiedNames + ? FullyQualifiedDisplayName.ArgumentNullException + : Identifier.ArgumentNullException; + + _writer.WriteBlock($$""" + if ({{argName}} is null) + { + throw new {{exceptionTypeDisplayString}}(nameof({{argName}})); + } + """); + _writer.WriteBlankLine(); } + private bool IncludeMethodsForGen(MethodSpecifier method) + => (_generationSpec.MethodsToGen & method) != 0; + private string GetIncrementalVarName(string prefix) => $"{prefix}{_parseValueCount++}"; + + private string GetTypeDisplayString(TypeSpec type) => _useFullyQualifiedNames ? type.FullyQualifiedDisplayString : type.MinimalDisplayString; + + private string GetHelperMethodDisplayString(string methodName) + { + if (_useFullyQualifiedNames) + { + methodName = FullyQualifiedDisplayName.Helpers + "." + methodName; + } + + return methodName; + } } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Helpers.cs index d4b3bc3a661b7..0556d13cbd323 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Helpers.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -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); @@ -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); @@ -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(); - } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Parser.cs index da8474e54a6c0..26ed3669ccdb7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Parser.cs @@ -14,15 +14,26 @@ public sealed partial class ConfigurationBindingSourceGenerator { private sealed class Parser { + private const string GlobalNameSpaceString = ""; + private readonly SourceProductionContext _context; private readonly KnownTypeData _typeData; private readonly HashSet _typesForBindMethodGen = new(); private readonly HashSet _typesForGetMethodGen = new(); private readonly HashSet _typesForConfigureMethodGen = new(); + private readonly HashSet _typesForBindCoreMethodGen = new(); + private readonly HashSet _unsupportedTypes = new(SymbolEqualityComparer.Default); private readonly Dictionary _createdSpecs = new(SymbolEqualityComparer.Default); + private readonly HashSet _namespaces = new() + { + "System", + "System.Linq", + "Microsoft.Extensions.Configuration" + }; + public Parser(SourceProductionContext context, KnownTypeData typeData) { _context = context; @@ -60,7 +71,15 @@ public Parser(SourceProductionContext context, KnownTypeData typeData) } } - return new SourceGenerationSpec(_typesForBindMethodGen, _typesForGetMethodGen, _typesForConfigureMethodGen); + Dictionary> methods = new() + { + [MethodSpecifier.Bind] = _typesForBindMethodGen, + [MethodSpecifier.Get] = _typesForGetMethodGen, + [MethodSpecifier.Configure] = _typesForConfigureMethodGen, + [MethodSpecifier.BindCore] = _typesForBindCoreMethodGen, + }; + + return new SourceGenerationSpec(methods, _namespaces); } private void ProcessBindCall(BinderInvocationOperation binderOperation) @@ -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; } @@ -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); } @@ -191,7 +212,18 @@ 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)) { @@ -199,9 +231,17 @@ private void ProcessConfigureCall(BinderInvocationOperation binderOperation) } 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); @@ -209,6 +249,12 @@ private void ProcessConfigureCall(BinderInvocationOperation binderOperation) T CacheSpec(T? s) where T : TypeSpec { + string @namespace = s.Namespace; + if (@namespace != null && @namespace != GlobalNameSpaceString) + { + _namespaces.Add(@namespace); + } + _createdSpecs[type] = s; return s; } @@ -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))) { @@ -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))) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConstructionStrategy.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConstructionStrategy.cs index 21db02547258a..e235b2cf48397 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConstructionStrategy.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConstructionStrategy.cs @@ -6,6 +6,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration internal enum ConstructionStrategy { NotApplicable = 0, - ParameterlessConstructor = 1, + NotSupported = 1, + ParameterlessConstructor = 2, } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj index 0c1a30fb76bd0..b37529ecbc87e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj @@ -5,9 +5,7 @@ false true cs - - - + true $(DefineConstants);LAUNCH_DEBUGGER @@ -34,6 +32,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/SourceGenerationSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/SourceGenerationSpec.cs index 2743d9b52f3b8..aeeb9e19a6daa 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/SourceGenerationSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/SourceGenerationSpec.cs @@ -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 TypesForBindMethodGen, - HashSet TypesForGetMethodGen, - HashSet TypesForConfigureMethodGen); + Dictionary> Methods, + HashSet Namespaces) + { + private MethodSpecifier? _methodsToGen; + public MethodSpecifier MethodsToGen + { + get + { + if (!_methodsToGen.HasValue) + { + _methodsToGen = MethodSpecifier.None; + + foreach (KeyValuePair> 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, + } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/SourceWriter.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/SourceWriter.cs new file mode 100644 index 0000000000000..46aa34852ec50 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/SourceWriter.cs @@ -0,0 +1,129 @@ +// 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.Diagnostics; +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + internal sealed class SourceWriter + { + private readonly StringBuilder _sb = new(); + private int _indentationLevel; + + public int Length => _sb.Length; + public int IndentationLevel => _indentationLevel; + + private static readonly char[] s_newLine = Environment.NewLine.ToCharArray(); + + public void WriteBlockStart(string? declaration = null) + { + if (declaration is not null) + { + 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 unsafe void WriteLine(ReadOnlySpan source) + { + _sb.Append(' ', 4 * _indentationLevel); + fixed (char* ptr = source) + { + _sb.Append(ptr, source.Length); + WriteBlankLine(); + } + } + + public void WriteBlock(string source) + { + bool isFinalLine; + ReadOnlySpan remainingText = source.AsSpan(); + + do + { + ReadOnlySpan line = GetNextLine(ref remainingText, out isFinalLine); + switch (line) + { + case "{": + { + WriteBlockStart(); + } + break; + case "}": + { + WriteBlockEnd(); + } + break; + default: + { + WriteLine(line); + } + break; + } + } while (!isFinalLine); + } + + public void WriteBlankLine() => _sb.AppendLine(); + + public void RemoveBlankLine() + { + int newLineLength = s_newLine.Length; + int lastNewLineStartIndex = Length - newLineLength; + _sb.Remove(lastNewLineStartIndex, newLineLength); + } + + public SourceText ToSourceText() + { + Debug.Assert(_indentationLevel == 0 && _sb.Length > 0); + return SourceText.From(_sb.ToString(), Encoding.UTF8); + } + + private static ReadOnlySpan GetNextLine(ref ReadOnlySpan remainingText, out bool isFinalLine) + { + if (remainingText.IsEmpty) + { + isFinalLine = true; + return default; + } + + ReadOnlySpan next; + ReadOnlySpan rest; + + remainingText = remainingText.Trim(); + + int lineLength = remainingText.IndexOf(s_newLine); + if (lineLength == -1) + { + lineLength = remainingText.Length; + isFinalLine = true; + rest = default; + } + else + { + rest = remainingText.Slice(lineLength + 1); + isFinalLine = false; + } + + next = remainingText.Slice(0, lineLength); + remainingText = rest; + return next; + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/TypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/TypeSpec.cs index cd500c6b9db06..06ad7ceeb292d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/TypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/TypeSpec.cs @@ -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; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs index 6d5e1fbeb1333..89af4c37e21b6 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -1255,7 +1255,6 @@ public void CanBindByteArray() var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddInMemoryCollection(dic); var config = configurationBuilder.Build(); - var options = config.Get(); Assert.Equal(bytes, options.MyByteArray); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestBindCallGen.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestBindCallGen.generated.txt index 5a0abd17224ed..30c909d2d5e9d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestBindCallGen.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestBindCallGen.generated.txt @@ -1,142 +1,145 @@ // #nullable enable -using System.Linq; - internal static class GeneratedConfigurationBinder { - public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, Program.MyClass obj) => BindCore(configuration, ref obj); + public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using System; + using System.Linq; + using Microsoft.Extensions.Configuration; + using System.Collections.Generic; - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref Program.MyClass obj) + internal static class Helpers { - if (obj is null) + public static void BindCore(IConfiguration configuration, ref List obj) { - throw new global::System.ArgumentNullException(nameof(obj)); - } + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } - if (configuration["MyString"] is string stringValue0) - { - obj.MyString = stringValue0; + int element; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string stringValue0) + { + element = int.Parse(stringValue0); + obj.Add(element); + } + } } - if (configuration["MyInt"] is string stringValue1) + public static void BindCore(IConfiguration configuration, ref Dictionary obj) { - obj.MyInt = int.Parse(stringValue1); - } + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } - global::Microsoft.Extensions.Configuration.IConfigurationSection section2 = configuration.GetSection("MyList"); - if (HasChildren(section2)) - { - System.Collections.Generic.List temp3 = obj.MyList; - temp3 ??= new System.Collections.Generic.List(); - BindCore(section2, ref temp3); - obj.MyList = temp3; + string key; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Key is string stringValue1) + { + key = stringValue1; + string element; + if (section.Value is string stringValue2) + { + element = stringValue2; + obj[key] = element; + } + } + } } - global::Microsoft.Extensions.Configuration.IConfigurationSection section4 = configuration.GetSection("MyDictionary"); - if (HasChildren(section4)) + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj) { - System.Collections.Generic.Dictionary temp5 = obj.MyDictionary; - temp5 ??= new System.Collections.Generic.Dictionary(); - BindCore(section4, ref temp5); - obj.MyDictionary = temp5; } - global::Microsoft.Extensions.Configuration.IConfigurationSection section6 = configuration.GetSection("MyComplexDictionary"); - if (HasChildren(section6)) + public static void BindCore(IConfiguration configuration, ref Dictionary obj) { - System.Collections.Generic.Dictionary temp7 = obj.MyComplexDictionary; - temp7 ??= new System.Collections.Generic.Dictionary(); - BindCore(section6, ref temp7); - obj.MyComplexDictionary = temp7; - } - - } + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref System.Collections.Generic.List obj) - { - if (obj is null) - { - throw new global::System.ArgumentNullException(nameof(obj)); + string key; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Key is string stringValue3) + { + key = stringValue3; + if (obj.TryGetValue(key, out Program.MyClass2? element) && element is not null) + { + BindCore(section, ref element); + obj[key] = element; + } + else + { + element = new Program.MyClass2(); + BindCore(section, ref element); + obj[key] = element; + } + } + } } - int element; - foreach (Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren()) + public static void BindCore(IConfiguration configuration, ref Program.MyClass obj) { - if (section.Value is string stringValue8) + if (obj is null) { - element = int.Parse(stringValue8); - obj.Add(element); + throw new ArgumentNullException(nameof(obj)); } - } - } - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref System.Collections.Generic.Dictionary obj) - { - if (obj is null) - { - throw new global::System.ArgumentNullException(nameof(obj)); - } + if (configuration["MyString"] is string stringValue6) + { + obj.MyString = stringValue6; + } - string key; - foreach (Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren()) - { - if (section.Key is string stringValue9) + if (configuration["MyInt"] is string stringValue7) { - key = stringValue9; - string element; - if (section.Value is string stringValue10) - { - element = stringValue10; - obj[key] = element; - } + obj.MyInt = int.Parse(stringValue7); } - } - } - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref System.Collections.Generic.Dictionary obj) - { - if (obj is null) - { - throw new global::System.ArgumentNullException(nameof(obj)); - } + IConfigurationSection section8 = configuration.GetSection("MyList"); + if (HasChildren(section8)) + { + List temp9 = obj.MyList; + temp9 ??= new List(); + BindCore(section8, ref temp9); + obj.MyList = temp9; + } - string key; - foreach (Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren()) - { - if (section.Key is string stringValue11) + IConfigurationSection section10 = configuration.GetSection("MyDictionary"); + if (HasChildren(section10)) { - key = stringValue11; - if (obj.TryGetValue(key, out Program.MyClass2? element) && element is not null) - { - BindCore(section, ref element); - obj[key] = element; - } - else - { - element = new Program.MyClass2(); - BindCore(section, ref element); - obj[key] = element; - } + Dictionary temp11 = obj.MyDictionary; + temp11 ??= new Dictionary(); + BindCore(section10, ref temp11); + obj.MyDictionary = temp11; } - } - } - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref Program.MyClass2 obj) - { - if (obj is null) - { - throw new global::System.ArgumentNullException(nameof(obj)); + IConfigurationSection section12 = configuration.GetSection("MyComplexDictionary"); + if (HasChildren(section12)) + { + Dictionary temp13 = obj.MyComplexDictionary; + temp13 ??= new Dictionary(); + BindCore(section12, ref temp13); + obj.MyComplexDictionary = temp13; + } } - } - - public static bool HasChildren(global::Microsoft.Extensions.Configuration.IConfiguration configuration) - { - foreach (global::Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren()) + public static bool HasChildren(IConfiguration configuration) { - return true; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + return true; + } + return false; } - return false; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestConfigureCallGen.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestConfigureCallGen.generated.txt index f03c9097c33a9..6f19224ea7b84 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestConfigureCallGen.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestConfigureCallGen.generated.txt @@ -1,107 +1,134 @@ // #nullable enable -using System.Linq; - internal static class GeneratedConfigurationBinder { public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration) { - if (typeof(T) == typeof(Program.MyClass)) + if (configuration is null) + { + throw new global::System.ArgumentNullException(nameof(configuration)); + } + + if (typeof(T) == typeof(global::Program.MyClass)) { - return services.Configure(obj => + return services.Configure(obj => { - BindCore(configuration, ref obj); + if (!global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.HasValueOrChildren(configuration)) + { + return default; + } + + global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj); }); } throw new global::System.NotSupportedException($"Unable to bind to type '{typeof(T)}': 'Generator parser did not detect the type as input'"); } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using System; + using System.Linq; + using Microsoft.Extensions.Configuration; + using System.Collections.Generic; - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref Program.MyClass obj) + internal static class Helpers { - if (obj is null) + public static void BindCore(IConfiguration configuration, ref List obj) { - throw new global::System.ArgumentNullException(nameof(obj)); - } + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } - if (configuration["MyString"] is string stringValue1) - { - obj.MyString = stringValue1; + int element; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string stringValue1) + { + element = int.Parse(stringValue1); + obj.Add(element); + } + } } - if (configuration["MyInt"] is string stringValue2) + public static void BindCore(IConfiguration configuration, ref Dictionary obj) { - obj.MyInt = int.Parse(stringValue2); - } + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } - global::Microsoft.Extensions.Configuration.IConfigurationSection section3 = configuration.GetSection("MyList"); - if (HasChildren(section3)) - { - System.Collections.Generic.List temp4 = obj.MyList; - temp4 ??= new System.Collections.Generic.List(); - BindCore(section3, ref temp4); - obj.MyList = temp4; + string key; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Key is string stringValue2) + { + key = stringValue2; + string element; + if (section.Value is string stringValue3) + { + element = stringValue3; + obj[key] = element; + } + } + } } - global::Microsoft.Extensions.Configuration.IConfigurationSection section5 = configuration.GetSection("MyDictionary"); - if (HasChildren(section5)) + public static void BindCore(IConfiguration configuration, ref Program.MyClass obj) { - System.Collections.Generic.Dictionary temp6 = obj.MyDictionary; - temp6 ??= new System.Collections.Generic.Dictionary(); - BindCore(section5, ref temp6); - obj.MyDictionary = temp6; - } + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } - } + if (configuration["MyString"] is string stringValue4) + { + obj.MyString = stringValue4; + } - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref System.Collections.Generic.List obj) - { - if (obj is null) - { - throw new global::System.ArgumentNullException(nameof(obj)); - } + if (configuration["MyInt"] is string stringValue5) + { + obj.MyInt = int.Parse(stringValue5); + } - int element; - foreach (Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string stringValue7) + IConfigurationSection section6 = configuration.GetSection("MyList"); + if (HasChildren(section6)) { - element = int.Parse(stringValue7); - obj.Add(element); + List temp7 = obj.MyList; + temp7 ??= new List(); + BindCore(section6, ref temp7); + obj.MyList = temp7; } - } - } - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref System.Collections.Generic.Dictionary obj) - { - if (obj is null) - { - throw new global::System.ArgumentNullException(nameof(obj)); + IConfigurationSection section8 = configuration.GetSection("MyDictionary"); + if (HasChildren(section8)) + { + Dictionary temp9 = obj.MyDictionary; + temp9 ??= new Dictionary(); + BindCore(section8, ref temp9); + obj.MyDictionary = temp9; + } } - string key; - foreach (Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren()) + public static bool HasValueOrChildren(IConfiguration configuration) { - if (section.Key is string stringValue8) + if ((configuration as IConfigurationSection)?.Value is not null) { - key = stringValue8; - string element; - if (section.Value is string stringValue9) - { - element = stringValue9; - obj[key] = element; - } + return true; } + return HasChildren(configuration); } - } - public static bool HasChildren(global::Microsoft.Extensions.Configuration.IConfiguration configuration) - { - foreach (global::Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren()) + public static bool HasChildren(IConfiguration configuration) { - return true; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + return true; + } + return false; } - return false; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetCallGen.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetCallGen.generated.txt index b0ff688d005e2..32e497efd4df1 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetCallGen.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetCallGen.generated.txt @@ -1,8 +1,6 @@ // #nullable enable -using System.Linq; - internal static class GeneratedConfigurationBinder { public static T? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) @@ -12,106 +10,124 @@ internal static class GeneratedConfigurationBinder throw new global::System.ArgumentNullException(nameof(configuration)); } - if (typeof(T) == typeof(Program.MyClass)) + if (!global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.HasValueOrChildren(configuration)) { - Microsoft.Extensions.Configuration.IConfigurationSection? section = configuration as Microsoft.Extensions.Configuration.IConfigurationSection; - if (section?.Value is null && !configuration.GetChildren().Any()) - { - return default; - } + return default; + } - Program.MyClass obj = new Program.MyClass(); - BindCore(configuration, ref obj); + if (typeof(T) == typeof(global::Program.MyClass)) + { + var obj = new global::Program.MyClass(); + global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj); return (T)(object)obj; } throw new global::System.NotSupportedException($"Unable to bind to type '{typeof(T)}': 'Generator parser did not detect the type as input'"); } +} - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref Program.MyClass obj) +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using System; + using System.Linq; + using Microsoft.Extensions.Configuration; + using System.Collections.Generic; + + internal static class Helpers { - if (obj is null) + public static void BindCore(IConfiguration configuration, ref List obj) { - throw new global::System.ArgumentNullException(nameof(obj)); - } + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } - if (configuration["MyString"] is string stringValue1) - { - obj.MyString = stringValue1; + int element; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string stringValue1) + { + element = int.Parse(stringValue1); + obj.Add(element); + } + } } - if (configuration["MyInt"] is string stringValue2) + public static void BindCore(IConfiguration configuration, ref Dictionary obj) { - obj.MyInt = int.Parse(stringValue2); - } + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } - global::Microsoft.Extensions.Configuration.IConfigurationSection section3 = configuration.GetSection("MyList"); - if (HasChildren(section3)) - { - System.Collections.Generic.List temp4 = obj.MyList; - temp4 ??= new System.Collections.Generic.List(); - BindCore(section3, ref temp4); - obj.MyList = temp4; + string key; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Key is string stringValue2) + { + key = stringValue2; + string element; + if (section.Value is string stringValue3) + { + element = stringValue3; + obj[key] = element; + } + } + } } - global::Microsoft.Extensions.Configuration.IConfigurationSection section5 = configuration.GetSection("MyDictionary"); - if (HasChildren(section5)) + public static void BindCore(IConfiguration configuration, ref Program.MyClass obj) { - System.Collections.Generic.Dictionary temp6 = obj.MyDictionary; - temp6 ??= new System.Collections.Generic.Dictionary(); - BindCore(section5, ref temp6); - obj.MyDictionary = temp6; - } + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } - } + if (configuration["MyString"] is string stringValue4) + { + obj.MyString = stringValue4; + } - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref System.Collections.Generic.List obj) - { - if (obj is null) - { - throw new global::System.ArgumentNullException(nameof(obj)); - } + if (configuration["MyInt"] is string stringValue5) + { + obj.MyInt = int.Parse(stringValue5); + } - int element; - foreach (Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string stringValue7) + IConfigurationSection section6 = configuration.GetSection("MyList"); + if (HasChildren(section6)) { - element = int.Parse(stringValue7); - obj.Add(element); + List temp7 = obj.MyList; + temp7 ??= new List(); + BindCore(section6, ref temp7); + obj.MyList = temp7; } - } - } - private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref System.Collections.Generic.Dictionary obj) - { - if (obj is null) - { - throw new global::System.ArgumentNullException(nameof(obj)); + IConfigurationSection section8 = configuration.GetSection("MyDictionary"); + if (HasChildren(section8)) + { + Dictionary temp9 = obj.MyDictionary; + temp9 ??= new Dictionary(); + BindCore(section8, ref temp9); + obj.MyDictionary = temp9; + } } - string key; - foreach (Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren()) + public static bool HasValueOrChildren(IConfiguration configuration) { - if (section.Key is string stringValue8) + if ((configuration as IConfigurationSection)?.Value is not null) { - key = stringValue8; - string element; - if (section.Value is string stringValue9) - { - element = stringValue9; - obj[key] = element; - } + return true; } + return HasChildren(configuration); } - } - public static bool HasChildren(global::Microsoft.Extensions.Configuration.IConfiguration configuration) - { - foreach (global::Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren()) + public static bool HasChildren(IConfiguration configuration) { - return true; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + return true; + } + return false; } - return false; } }