Correct formatting of config binder generator (#83614)
authorLayomi Akinrinade <layomia@gmail.com>
Wed, 22 Mar 2023 01:13:01 +0000 (18:13 -0700)
committerGitHub <noreply@github.com>
Wed, 22 Mar 2023 01:13:01 +0000 (18:13 -0700)
* Correct formatting of config binder generator

* Use span tokenization in multi-line emission logic

12 files changed:
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Emitter.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Helpers.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingSourceGenerator.Parser.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConstructionStrategy.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/SourceGenerationSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/SourceWriter.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/TypeSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestBindCallGen.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestConfigureCallGen.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetCallGen.generated.txt

index 9edc39f..046b518 100644 (file)
@@ -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 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 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<TypeSpec> _privateBindCoreMethodGen_QueuedTypes = new();
-
-            private readonly HashSet<TypeSpec> _internalBindMethodGen_ProcessedTypes = new();
-            private readonly HashSet<TypeSpec> _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 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 _writer.WriteLine(@"// <auto-generated/>
 #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}<T>(this {GlobalName.IServiceCollection} {Literal.services}, {GlobalName.IConfiguration} {Literal.configuration})");
+                _writer.WriteBlockStart($"public static {FullyQualifiedDisplayName.IServiceCollection} {Identifier.Configure}<T>(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 @@ using System.Linq;
 
                 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}<T>(this {GlobalName.IConfiguration} {Literal.configuration})");
+                if (IncludeMethodsForGen(MethodSpecifier.Configure))
+                {
+                    _writer.WriteBlankLine();
+                }
+
+                _writer.WriteBlockStart($"public static T? {Identifier.Get}<T>(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 @@ using System.Linq;
                     case TypeSpecKind.IConfigurationSection:
                         {
                             EmitCastToIConfigurationSection();
-                            EmitAssignment(Literal.obj, Literal.section);
+                            EmitAssignment(Identifier.obj, Identifier.section);
                         }
                         break;
                     case TypeSpecKind.Dictionary:
@@ -244,15 +265,17 @@ using System.Linq;
 
                 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 @@ using System.Linq;
                 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 @@ using System.Linq;
                     // 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 @@ using System.Linq;
 
                         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 @@ using System.Linq;
 
                 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 @@ using System.Linq;
 
             private void EmitBindCoreImplForObject(ObjectSpec type)
             {
+                List<PropertySpec> properties = type.Properties;
+                if (properties.Count == 0)
+                {
+                    return;
+                }
+
                 EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);
 
                 foreach (PropertySpec property in type.Properties)
@@ -357,17 +386,19 @@ using System.Linq;
                     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 @@ using System.Linq;
             {
                 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 @@ using System.Linq;
                 }
                 else
                 {
-                    EmitBindCoreCall(type, expressionForMemberAccess, Literal.section, initKind);
+                    EmitBindCoreCall(type, expressionForMemberAccess, Identifier.section, initKind);
                     writeExtraOnSuccess?.Invoke();
                 }
             }
@@ -468,26 +494,30 @@ using System.Linq;
                 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 @@ using System.Linq;
                 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 @@ using System.Linq;
                             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 @@ using System.Linq;
                             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 @@ using System.Linq;
                 {
                     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 @@ using System.Linq;
                 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 @@ using System.Linq;
                     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 @@ using System.Linq;
                     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 @@ using System.Linq;
                 }
                 else if (type is CollectionSpec { ConcreteType: { } concreteType })
                 {
-                    displayString = concreteType.DisplayString;
+                    displayString = GetTypeDisplayString(concreteType);
                 }
 
                 // Not an array.
@@ -629,7 +657,7 @@ using System.Linq;
                 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 @@ using System.Linq;
                 }
             }
 
-            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;
+            }
         }
     }
 }
index d4b3bc3..0556d13 100644 (file)
@@ -5,6 +5,7 @@ using System;
 using System.Diagnostics;
 using System.Text;
 using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
@@ -34,7 +35,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             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 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             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 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
         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();
-        }
     }
 }
index da8474e..26ed366 100644 (file)
@@ -14,15 +14,26 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
     {
         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;
@@ -60,7 +71,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     }
                 }
 
-                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)
@@ -76,10 +95,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     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 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
 
                 TypeSpec? spec = GetOrCreateTypeSpec(namedType, location);
-                if (spec != null && !specs.Contains(spec))
+                if (spec != null &&
+                    !specs.Contains(spec))
                 {
                     specs.Add(spec);
                 }
@@ -191,7 +212,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 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 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
                 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 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 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;
                 }
@@ -528,7 +574,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 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 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 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)))
index 21db025..e235b2c 100644 (file)
@@ -6,6 +6,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
     internal enum ConstructionStrategy
     {
         NotApplicable = 0,
-        ParameterlessConstructor = 1,
+        NotSupported = 1,
+        ParameterlessConstructor = 2,
     }
 }
index 0c1a30f..b37529e 100644 (file)
@@ -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>
 
@@ -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>
index 2743d9b..aeeb9e1 100644 (file)
@@ -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,
+    }
 }
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 (file)
index 0000000..46aa348
--- /dev/null
@@ -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<char> source)
+        {
+            _sb.Append(' ', 4 * _indentationLevel);
+            fixed (char* ptr = source)
+            {
+                _sb.Append(ptr, source.Length);
+                WriteBlankLine();
+            }
+        }
+
+        public void WriteBlock(string source)
+        {
+            bool isFinalLine;
+            ReadOnlySpan<char> remainingText = source.AsSpan();
+
+            do
+            {
+                ReadOnlySpan<char> 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<char> GetNextLine(ref ReadOnlySpan<char> remainingText, out bool isFinalLine)
+        {
+            if (remainingText.IsEmpty)
+            {
+                isFinalLine = true;
+                return default;
+            }
+
+            ReadOnlySpan<char> next;
+            ReadOnlySpan<char> 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;
+        }
+    }
+}
index cd500c6..06ad7ce 100644 (file)
@@ -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; }
 
index 6d5e1fb..89af4c3 100644 (file)
@@ -1255,7 +1255,6 @@ namespace Microsoft.Extensions
             var configurationBuilder = new ConfigurationBuilder();
             configurationBuilder.AddInMemoryCollection(dic);
             var config = configurationBuilder.Build();
-
             var options = config.Get<ByteArrayOptions>();
             Assert.Equal(bytes, options.MyByteArray);
         }
index 5a0abd1..30c909d 100644 (file)
 // <auto-generated/>
 #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<int> 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<string, string> 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<int> temp3 = obj.MyList;
-            temp3 ??= new System.Collections.Generic.List<int>();
-            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<string, string> temp5 = obj.MyDictionary;
-            temp5 ??= new System.Collections.Generic.Dictionary<string, string>();
-            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<string, Program.MyClass2> obj)
         {
-            System.Collections.Generic.Dictionary<string, Program.MyClass2> temp7 = obj.MyComplexDictionary;
-            temp7 ??= new System.Collections.Generic.Dictionary<string, Program.MyClass2>();
-            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<int> 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<string, string> 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<string, Program.MyClass2> obj)
-    {
-        if (obj is null)
-        {
-            throw new global::System.ArgumentNullException(nameof(obj));
-        }
+            IConfigurationSection section8 = configuration.GetSection("MyList");
+            if (HasChildren(section8))
+            {
+                List<int> temp9 = obj.MyList;
+                temp9 ??= new List<int>();
+                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<string, string> temp11 = obj.MyDictionary;
+                temp11 ??= new Dictionary<string, string>();
+                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<string, Program.MyClass2> temp13 = obj.MyComplexDictionary;
+                temp13 ??= new Dictionary<string, Program.MyClass2>();
+                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;
     }
 }
index f03c909..6f19224 100644 (file)
 // <auto-generated/>
 #nullable enable
 
-using System.Linq;
-
 internal static class GeneratedConfigurationBinder
 {
     public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<T>(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<Program.MyClass>(obj =>
+            return services.Configure<global::Program.MyClass>(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<int> 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<string, string> 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<int> temp4 = obj.MyList;
-            temp4 ??= new System.Collections.Generic.List<int>();
-            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<string, string> temp6 = obj.MyDictionary;
-            temp6 ??= new System.Collections.Generic.Dictionary<string, string>();
-            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<int> 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<int> temp7 = obj.MyList;
+                temp7 ??= new List<int>();
+                BindCore(section6, ref temp7);
+                obj.MyList = temp7;
             }
-        }
-    }
 
-    private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref System.Collections.Generic.Dictionary<string, string> obj)
-    {
-        if (obj is null)
-        {
-            throw new global::System.ArgumentNullException(nameof(obj));
+            IConfigurationSection section8 = configuration.GetSection("MyDictionary");
+            if (HasChildren(section8))
+            {
+                Dictionary<string, string> temp9 = obj.MyDictionary;
+                temp9 ??= new Dictionary<string, string>();
+                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;
     }
 }
index b0ff688..32e497e 100644 (file)
@@ -1,8 +1,6 @@
 // <auto-generated/>
 #nullable enable
 
-using System.Linq;
-
 internal static class GeneratedConfigurationBinder
 {
     public static T? Get<T>(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<int> 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<string, string> 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<int> temp4 = obj.MyList;
-            temp4 ??= new System.Collections.Generic.List<int>();
-            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<string, string> temp6 = obj.MyDictionary;
-            temp6 ??= new System.Collections.Generic.Dictionary<string, string>();
-            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<int> 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<int> temp7 = obj.MyList;
+                temp7 ??= new List<int>();
+                BindCore(section6, ref temp7);
+                obj.MyList = temp7;
             }
-        }
-    }
 
-    private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref System.Collections.Generic.Dictionary<string, string> obj)
-    {
-        if (obj is null)
-        {
-            throw new global::System.ArgumentNullException(nameof(obj));
+            IConfigurationSection section8 = configuration.GetSection("MyDictionary");
+            if (HasChildren(section8))
+            {
+                Dictionary<string, string> temp9 = obj.MyDictionary;
+                temp9 ??= new Dictionary<string, string>();
+                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;
     }
 }