Add support for types with parameterized ctors to config binder gen (#86365)
authorLayomi Akinrinade <laakinri@microsoft.com>
Fri, 26 May 2023 21:58:23 +0000 (14:58 -0700)
committerGitHub <noreply@github.com>
Fri, 26 May 2023 21:58:23 +0000 (14:58 -0700)
* Add support for types with parameterized ctors to config binder gen

* Ensure param-prop binding & exception behavior match reflection implementation

* Fix ctor preference logic and enable passing test

37 files changed:
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter.Helpers.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/ExceptionMessages.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/ParserDiagnostics.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/BinderMethodSpecifier.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/CollectionSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/InitializationStrategy.cs [moved from src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ConstructionStrategy.cs with 85% similarity]
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/NullableSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ObjectSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParameterSpec.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/PropertySpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/SourceGenerationSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/Strings.resx
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.cs.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.de.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.es.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.fr.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.it.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ja.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ko.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pl.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pt-BR.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ru.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.tr.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hans.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hant.xlf
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.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/TestCollectionsGen.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
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetValueCallGen.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestPrimitivesGen.generated.txt

index 7fa5cf3..f309f20 100644 (file)
@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Linq;
 using System.Text.RegularExpressions;
 using Microsoft.CodeAnalysis;
 
@@ -72,6 +73,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 EmitGetCoreMethod();
                 EmitGetValueCoreMethod();
                 EmitBindCoreMethods();
+                EmitInitializeMethods();
                 EmitHelperMethods();
 
                 _writer.WriteBlockEnd(); // End helper class.
@@ -93,7 +95,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 EmitCheckForNullArgument_WithBlankLine(Identifier.configuration, useFullyQualifiedNames: true);
 
-                foreach (TypeSpec type in _generationSpec.RootConfigTypes[BinderMethodSpecifier.Configure])
+                foreach (TypeSpec type in _generationSpec.ConfigTypes[BinderMethodSpecifier.Configure])
                 {
                     string typeDisplayString = type.FullyQualifiedDisplayString;
 
@@ -121,28 +123,28 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 {
                     EmitBlankLineIfRequired();
                     _writer.WriteLine($"public static T? {Identifier.Get}<T>(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}) => " +
-                        $"(T?)({expressionForGetCore}({Identifier.configuration}, typeof(T), {Identifier.configureActions}: null) ?? default(T));");
+                        $"(T?)({expressionForGetCore}({Identifier.configuration}, typeof(T), {Identifier.configureOptions}: null) ?? default(T));");
                 }
 
                 if (_generationSpec.ShouldEmitMethods(BinderMethodSpecifier.Get_T_BinderOptions))
                 {
                     EmitBlankLineIfRequired();
-                    _writer.WriteLine($"public static T? {Identifier.Get}<T>(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayName.ActionOfBinderOptions}? {Identifier.configureActions}) => " +
-                        $"(T?)({expressionForGetCore}({Identifier.configuration}, typeof(T), {Identifier.configureActions}) ?? default(T));");
+                    _writer.WriteLine($"public static T? {Identifier.Get}<T>(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayName.ActionOfBinderOptions}? {Identifier.configureOptions}) => " +
+                        $"(T?)({expressionForGetCore}({Identifier.configuration}, typeof(T), {Identifier.configureOptions}) ?? default(T));");
                 }
 
                 if (_generationSpec.ShouldEmitMethods(BinderMethodSpecifier.Get_TypeOf))
                 {
                     EmitBlankLineIfRequired();
                     _writer.WriteLine($"public static object? {Identifier.Get}(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayName.Type} {Identifier.type}) => " +
-                        $"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureActions}: null);");
+                        $"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureOptions}: null);");
                 }
 
                 if (_generationSpec.ShouldEmitMethods(BinderMethodSpecifier.Get_TypeOf_BinderOptions))
                 {
                     EmitBlankLineIfRequired();
-                    _writer.WriteLine($"public static object? {Identifier.Get}(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayName.Type} {Identifier.type}, {FullyQualifiedDisplayName.ActionOfBinderOptions}? {Identifier.configureActions}) => " +
-                        $"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureActions});");
+                    _writer.WriteLine($"public static object? {Identifier.Get}(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayName.Type} {Identifier.type}, {FullyQualifiedDisplayName.ActionOfBinderOptions}? {Identifier.configureOptions}) => " +
+                        $"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureOptions});");
                 }
             }
 
@@ -186,16 +188,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return;
                 }
 
-                Dictionary<BinderMethodSpecifier, HashSet<TypeSpec>> rootConfigTypes = _generationSpec.RootConfigTypes;
+                Dictionary<BinderMethodSpecifier, HashSet<TypeSpec>> rootConfigTypes = _generationSpec.ConfigTypes;
 
                 if (rootConfigTypes.TryGetValue(BinderMethodSpecifier.Bind_instance, out HashSet<TypeSpec>? typeSpecs))
                 {
                     foreach (TypeSpec type in typeSpecs)
                     {
-                        EmitBlankLineIfRequired();
-                        _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}, {Identifier.binderOptions}: null);");
+                        EmitMethodImplementation(
+                            type,
+                            additionalParams: GetObjParameter(type),
+                            configExpression: Identifier.configuration,
+                            configureOptions: false);
                     }
                 }
 
@@ -203,10 +206,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 {
                     foreach (TypeSpec type in typeSpecs)
                     {
-                        EmitBlankLineIfRequired();
-                        _writer.WriteLine(
-                            $"public static void {Identifier.Bind}(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, {type.FullyQualifiedDisplayString} {Identifier.obj}, {FullyQualifiedDisplayName.ActionOfBinderOptions}? {Identifier.configureActions}) => " +
-                                $"{FullyQualifiedDisplayName.Helpers}.{Identifier.BindCore}({Identifier.configuration}, ref {Identifier.obj}, {Expression.GetBinderOptions}({Identifier.configureActions}));");
+                        EmitMethodImplementation(
+                            type,
+                            additionalParams: $"{GetObjParameter(type)}, {FullyQualifiedDisplayName.ActionOfBinderOptions}? {Identifier.configureOptions}",
+                            configExpression: Identifier.configuration,
+                            configureOptions: true);
                     }
                 }
 
@@ -214,11 +218,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 {
                     foreach (TypeSpec type in typeSpecs)
                     {
-                        EmitBlankLineIfRequired();
-                        _writer.WriteLine($"public static void {Identifier.Bind}(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, string {Identifier.key}, {type.FullyQualifiedDisplayString} {Identifier.obj}) => " +
-                            $"{FullyQualifiedDisplayName.Helpers}.{Identifier.BindCore}({Identifier.configuration}.{Identifier.GetSection}({Identifier.key}), ref {Identifier.obj}, {Identifier.binderOptions}: null);");
+                        EmitMethodImplementation(
+                            type,
+                            additionalParams: $"string {Identifier.key}, {GetObjParameter(type)}",
+                            configExpression: $"{Identifier.configuration}.{Identifier.GetSection}({Identifier.key})",
+                            configureOptions: false);
                     }
                 }
+
+                void EmitMethodImplementation(TypeSpec type, string additionalParams, string configExpression, bool configureOptions)
+                {
+                    string binderOptionsArg = configureOptions ? $"{Expression.GetBinderOptions}({Identifier.configureOptions})" : $"{Identifier.binderOptions}: null";
+                    string returnExpression = type.CanInitialize
+                        ? $"{FullyQualifiedDisplayName.Helpers}.{Identifier.BindCore}({configExpression}, ref {Identifier.obj}, {binderOptionsArg})"
+                        : GetInitException(type.InitExceptionMessage);
+
+                    EmitBlankLineIfRequired();
+                    _writer.WriteLine($"public static void {Identifier.Bind}(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, {additionalParams}) => "
+                        + $"{returnExpression};");
+                }
+
+                string GetObjParameter(TypeSpec type) => $"{type.FullyQualifiedDisplayString} {Identifier.obj}";
             }
             #endregion
 
@@ -238,22 +258,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return;
                 }
 
-                _writer.WriteBlockStart($"public static object? {Identifier.GetCore}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, Action<{Identifier.BinderOptions}>? {Identifier.configureActions})");
+                _writer.WriteBlockStart($"public static object? {Identifier.GetCore}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, Action<{Identifier.BinderOptions}>? {Identifier.configureOptions})");
 
                 EmitCheckForNullArgument_WithBlankLine(Identifier.configuration);
 
-                _writer.WriteLine($"{Identifier.BinderOptions}? {Identifier.binderOptions} = {Identifier.GetBinderOptions}({Identifier.configureActions});");
+                _writer.WriteLine($"{Identifier.BinderOptions}? {Identifier.binderOptions} = {Identifier.GetBinderOptions}({Identifier.configureOptions});");
                 _writer.WriteBlankLine();
 
                 EmitIConfigurationHasValueOrChildrenCheck(voidReturn: false);
 
-                if (_generationSpec.RootConfigTypes.TryGetValue(BinderMethodSpecifier.Get, out HashSet<TypeSpec>? types))
+                if (_generationSpec.ConfigTypes.TryGetValue(BinderMethodSpecifier.Get, out HashSet<TypeSpec>? types))
                 {
                     foreach (TypeSpec type in types)
                     {
                         _writer.WriteBlockStart($"if (type == typeof({type.MinimalDisplayString}))");
-                        EmitBindLogicFromRootMethod(type, Identifier.obj, InitializationKind.Declaration);
-                        _writer.WriteLine($"return {Identifier.obj};");
+
+                        if (type.InitializationStrategy is InitializationStrategy.None || !EmitInitException(type))
+                        {
+                            EmitBindLogicFromRootMethod(type, Identifier.obj, InitializationKind.Declaration);
+                            _writer.WriteLine($"return {Identifier.obj};");
+                        }
+
                         _writer.WriteBlockEnd();
                         _writer.WriteBlankLine();
                     }
@@ -282,7 +307,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 _writer.WriteBlankLine();
 
-                foreach (TypeSpec type in _generationSpec.RootConfigTypes[BinderMethodSpecifier.GetValue])
+                foreach (TypeSpec type in _generationSpec.ConfigTypes[BinderMethodSpecifier.GetValue])
                 {
                     TypeSpec effectiveType = (type as NullableSpec)?.UnderlyingType ?? type;
                     _writer.WriteBlockStart($"if (type == typeof({type.MinimalDisplayString}))");
@@ -308,7 +333,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return;
                 }
 
-                foreach (TypeSpec type in _generationSpec.RootConfigTypes[BinderMethodSpecifier.BindCore])
+                foreach (TypeSpec type in _generationSpec.ConfigTypes[BinderMethodSpecifier.BindCore])
                 {
                     if (type.SpecKind is TypeSpecKind.ParsableFromString)
                     {
@@ -322,12 +347,169 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private void EmitBindCoreMethod(TypeSpec type)
             {
+                Debug.Assert(type.CanInitialize);
+
                 string objParameterExpression = $"ref {type.MinimalDisplayString} {Identifier.obj}";
                 _writer.WriteBlockStart(@$"public static void {Identifier.BindCore}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression}, {Identifier.BinderOptions}? {Identifier.binderOptions})");
                 EmitBindCoreImpl(type);
                 _writer.WriteBlockEnd();
             }
 
+            private void EmitInitializeMethods()
+            {
+                if (!_generationSpec.ShouldEmitMethods(BinderMethodSpecifier.Initialize))
+                {
+                    return;
+                }
+
+                foreach (ObjectSpec type in _generationSpec.ConfigTypes[BinderMethodSpecifier.Initialize])
+                {
+                    EmitBlankLineIfRequired();
+                    EmitInitializeMethod(type);
+                }
+            }
+
+            private void EmitInitializeMethod(ObjectSpec type)
+            {
+                Debug.Assert(type.CanInitialize);
+
+                List<ParameterSpec> ctorParams = type.ConstructorParameters;
+                IEnumerable<PropertySpec> initOnlyProps = type.Properties.Values.Where(prop => prop.SetOnInit);
+                string displayString = type.MinimalDisplayString;
+
+                _writer.WriteBlockStart($"public static {displayString} {type.InitializeMethodDisplayString}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})");
+
+                foreach (ParameterSpec parameter in ctorParams)
+                {
+                    if (!parameter.HasExplicitDefaultValue)
+                    {
+                        _writer.WriteLine($@"({parameter.Type.MinimalDisplayString} {Identifier.Value}, bool {Identifier.HasConfig}) {parameter.Name} = ({parameter.DefaultValue}, false);");
+                    }
+                    else
+                    {
+                        _writer.WriteLine($@"{parameter.Type.MinimalDisplayString} {parameter.Name} = {parameter.DefaultValue};");
+                    }
+                }
+
+                foreach (PropertySpec property in initOnlyProps)
+                {
+                    if (property.MatchingCtorParam is null)
+                    {
+                        _writer.WriteLine($@"{property.Type.MinimalDisplayString} {property.Name} = default!;");
+                    }
+                }
+
+                _writer.WriteBlankLine();
+
+                _writer.WriteBlock($$"""
+                        foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
+                        {
+                            switch ({{Expression.sectionKey}})
+                            {
+                    """);
+
+                List<string> argumentList = new();
+
+                foreach (ParameterSpec parameter in ctorParams)
+                {
+                    EmitMemberBindLogic(parameter.Name, parameter.Type, parameter.ConfigurationKeyName, configValueMustExist: !parameter.HasExplicitDefaultValue);
+                    argumentList.Add(GetExpressionForArgument(parameter));
+                }
+
+                foreach (PropertySpec property in initOnlyProps)
+                {
+                    if (property.ShouldBind() && property.MatchingCtorParam is null)
+                    {
+                        EmitMemberBindLogic(property.Name, property.Type, property.ConfigurationKeyName);
+                    }
+                }
+
+                _writer.WriteBlock("""
+                    default:
+                    {
+                        continue;
+                    }
+                }
+            }
+            """);
+
+                _precedingBlockExists = true;
+
+                foreach (ParameterSpec parameter in ctorParams)
+                {
+                    if (!parameter.HasExplicitDefaultValue)
+                    {
+                        string parameterName = parameter.Name;
+
+                        EmitBlankLineIfRequired();
+                        _writer.WriteBlock($$"""
+                        if (!{{parameterName}}.{{Identifier.HasConfig}})
+                        {
+                            throw new {{GetInvalidOperationDisplayName()}}("{{string.Format(ExceptionMessages.ParameterHasNoMatchingConfig, type.Name, parameterName)}}");
+                        }
+                        """);
+                    }
+                }
+
+                EmitBlankLineIfRequired();
+
+                string returnExpression = $"return new {displayString}({string.Join(", ", argumentList)})";
+                if (!initOnlyProps.Any())
+                {
+                    _writer.WriteLine($"{returnExpression};");
+                }
+                else
+                {
+                    _writer.WriteBlockStart(returnExpression);
+                    foreach (PropertySpec property in initOnlyProps)
+                    {
+                        string propertyName = property.Name;
+                        string initValue = propertyName + (property.MatchingCtorParam is null or ParameterSpec { HasExplicitDefaultValue: true } ? string.Empty : $".{Identifier.Value}");
+                        _writer.WriteLine($@"{propertyName} = {initValue},");
+                    }
+                    _writer.WriteBlockEnd(";");
+                }
+
+                // End method.
+                _writer.WriteBlockEnd();
+
+                #region Local helpers
+                void EmitMemberBindLogic(string memberName, TypeSpec memberType, string configurationKeyName, bool configValueMustExist = false)
+                {
+                    string lhs = memberName + (configValueMustExist ? $".{Identifier.Value}" : string.Empty);
+
+                    _writer.WriteBlockStart($@"case ""{configurationKeyName}"":");
+                    EmitMemberBindLogicCore(memberType, lhs);
+
+                    if (configValueMustExist)
+                    {
+                        _writer.WriteLine($"{memberName}.{Identifier.HasConfig} = true;");
+                    }
+
+                    _writer.WriteBlockEnd();
+                    _writer.WriteLine("break;");
+
+                    void EmitMemberBindLogicCore(TypeSpec type, string lhs)
+                    {
+                        TypeSpecKind kind = type.SpecKind;
+
+                        if (kind is TypeSpecKind.Nullable)
+                        {
+                            EmitMemberBindLogicCore(((NullableSpec)type).UnderlyingType, lhs);
+                        }
+                        else if (kind is TypeSpecKind.ParsableFromString)
+                        {
+                            EmitBindLogicFromString((ParsableFromStringSpec)type, lhs, Expression.sectionValue, Expression.sectionPath);
+                        }
+                        else if (!EmitInitException(type))
+                        {
+                            EmitBindCoreCall(type, lhs, Identifier.section, InitializationKind.SimpleAssignment);
+                        }
+                    }
+                }
+                #endregion
+            }
+
             private void EmitHelperMethods()
             {
                 if (_generationSpec.ShouldEmitMethods(BinderMethodSpecifier.Get | BinderMethodSpecifier.Configure))
@@ -393,15 +575,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             private void EmitGetBinderOptionsHelper()
             {
                 _writer.WriteBlock($$"""
-                    public static {{Identifier.BinderOptions}}? {{Identifier.GetBinderOptions}}(System.Action<BinderOptions>? {{Identifier.configureActions}})
+                    public static {{Identifier.BinderOptions}}? {{Identifier.GetBinderOptions}}(System.Action<BinderOptions>? {{Identifier.configureOptions}})
                     {
-                        if ({{Identifier.configureActions}} is null)
+                        if ({{Identifier.configureOptions}} is null)
                         {
                             return null;
                         }
 
                         {{Identifier.BinderOptions}} {{Identifier.binderOptions}} = new();
-                        {{Identifier.configureActions}}({{Identifier.binderOptions}});
+                        {{Identifier.configureOptions}}({{Identifier.binderOptions}});
 
                         if ({{Identifier.binderOptions}}.BindNonPublicProperties)
                         {
@@ -487,8 +669,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         }
                 }
 
-                string exceptionTypeDisplayString = _useFullyQualifiedNames ? FullyQualifiedDisplayName.InvalidOperationException : Identifier.InvalidOperationException;
-
                 _writer.WriteBlock($$"""
                     public static {{typeDisplayString}} {{type.ParseMethodName}}(string {{Identifier.stringValue}}, Func<string?> {{Identifier.getPath}})
                     {
@@ -503,7 +683,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         }
                         catch ({{innerExceptionTypeDisplayString}} {{Identifier.exception}})
                         {
-                            throw new {{exceptionTypeDisplayString}}($"{{exceptionArg1}}", {{Identifier.exception}});
+                            throw new {{GetInvalidOperationDisplayName()}}($"{{exceptionArg1}}", {{Identifier.exception}});
                         }
                     }
                     """);
@@ -516,13 +696,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 switch (type.SpecKind)
                 {
                     case TypeSpecKind.Enumerable:
+                    case TypeSpecKind.Dictionary:
+                    case TypeSpecKind.Object:
                         {
-                            EmitBindCoreImplForEnumerable((EnumerableSpec)type);
+                            Debug.Assert(type.CanInitialize);
+                            EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);
+                            EmitBindCoreImplForComplexType(type);
                         }
                         break;
-                    case TypeSpecKind.Dictionary:
+                    case TypeSpecKind.Nullable:
                         {
-                            EmitBindCoreImplForDictionary((DictionarySpec)type);
+                            EmitBindCoreImpl(((NullableSpec)type).UnderlyingType);
                         }
                         break;
                     case TypeSpecKind.IConfigurationSection:
@@ -531,33 +715,29 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                             _writer.WriteLine($"{Identifier.obj} = {Identifier.section};");
                         }
                         break;
-                    case TypeSpecKind.Object:
-                        {
-                            EmitBindCoreImplForObject((ObjectSpec)type);
-                        }
-                        break;
-                    case TypeSpecKind.Nullable:
-                        {
-                            EmitBindCoreImpl(((NullableSpec)type).UnderlyingType);
-                        }
-                        break;
                     default:
                         Debug.Fail("Invalid type kind", type.SpecKind.ToString());
                         break;
                 }
             }
 
-            private void EmitBindCoreImplForEnumerable(EnumerableSpec type)
+            private void EmitBindCoreImplForComplexType(TypeSpec type)
             {
-                EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);
-
-                if (type.PopulationStrategy is CollectionPopulationStrategy.Array)
+                if (type.InitializationStrategy is InitializationStrategy.Array)
+                {
+                    EmitPopulationImplForArray((EnumerableSpec)type);
+                }
+                else if (type is EnumerableSpec enumerable)
+                {
+                    EmitPopulationImplForEnumerableWithAdd(enumerable);
+                }
+                else if (type is DictionarySpec dictionary)
                 {
-                    EmitPopulationImplForArray(type);
+                    EmitBindCoreImplForDictionary(dictionary);
                 }
                 else
                 {
-                    EmitPopulationImplForEnumerableWithAdd(type);
+                    EmitBindCoreImplForObject((ObjectSpec)type);
                 }
             }
 
@@ -614,14 +794,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private void EmitBindCoreImplForDictionary(DictionarySpec type)
             {
-                EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);
+                TypeSpec elementType = type.ElementType;
 
                 EmitCollectionCastIfRequired(type, out string objIdentifier);
 
                 _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())");
 
                 ParsableFromStringSpec keyType = type.KeyType;
-                TypeSpec elementType = type.ElementType;
 
                 // Parse key
                 if (keyType.StringParsableTypeKind is StringParsableTypeKind.ConfigValue)
@@ -665,6 +844,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     }
                     else // For complex types:
                     {
+                        Debug.Assert(elementType.CanInitialize);
+
                         bool isValueType = elementType.IsValueType;
                         string expressionForElementIsNotNull = $"{Identifier.element} is not null";
                         string elementTypeDisplayString = elementType.MinimalDisplayString + (elementType.IsValueType ? string.Empty : "?");
@@ -684,13 +865,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                         if (elementType is CollectionSpec
                             {
-                                ConstructionStrategy: ConstructionStrategy.ParameterizedConstructor or ConstructionStrategy.ToEnumerableMethod
+                                InitializationStrategy: InitializationStrategy.ParameterizedConstructor or InitializationStrategy.ToEnumerableMethod
                             } collectionSpec)
                         {
                             // This is a read-only collection. If the element exists and is not null,
                             // we need to copy its contents into a new instance & then append/bind to that.
 
-                            string initExpression = collectionSpec.ConstructionStrategy is ConstructionStrategy.ParameterizedConstructor
+                            string initExpression = collectionSpec.InitializationStrategy is InitializationStrategy.ParameterizedConstructor
                                 ? $"new {collectionSpec.ConcreteType.MinimalDisplayString}({Identifier.element})"
                                 : $"{Identifier.element}.{collectionSpec.ToEnumerableMethodCall!}";
 
@@ -712,31 +893,32 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private void EmitBindCoreImplForObject(ObjectSpec type)
             {
-                Dictionary<string, PropertySpec> properties = type.Properties;
-                if (properties.Count == 0)
+                if (type.Properties.Count == 0)
                 {
                     return;
                 }
 
-                EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);
-
                 string listOfStringDisplayName = "List<string>";
                 _writer.WriteLine($"{listOfStringDisplayName}? {Identifier.temp} = null;");
 
                 _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())");
                 _writer.WriteBlockStart($"switch ({Expression.sectionKey})");
 
-                foreach (PropertySpec property in properties.Values)
+                foreach (PropertySpec property in type.Properties.Values)
                 {
                     _writer.WriteBlockStart($@"case ""{property.ConfigurationKeyName}"":");
 
+                    bool success = true;
                     if (property.ShouldBind())
                     {
-                        EmitBindCoreImplForProperty(property, property.Type!, parentType: type);
+                        success = EmitBindCoreImplForProperty(property, property.Type, parentType: type);
                     }
 
                     _writer.WriteBlockEnd();
-                    _writer.WriteLine("break;");
+                    if (success)
+                    {
+                        _writer.WriteLine("break;");
+                    }
                 }
 
                 _writer.WriteBlock($$"""
@@ -768,7 +950,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             }
 
-            private void EmitBindCoreImplForProperty(PropertySpec property, TypeSpec propertyType, TypeSpec parentType)
+            private bool EmitBindCoreImplForProperty(PropertySpec property, TypeSpec propertyType, TypeSpec parentType)
             {
                 string configurationKeyName = property.ConfigurationKeyName;
 
@@ -793,14 +975,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                             }
                         }
                         break;
-                    case TypeSpecKind.Array:
-                        {
-                            EmitBindCoreCallForProperty(
-                                property,
-                                propertyType,
-                                expressionForPropertyAccess);
-                        }
-                        break;
                     case TypeSpecKind.IConfigurationSection:
                         {
                             _writer.WriteLine($"{expressionForPropertyAccess} = {Identifier.section};");
@@ -814,10 +988,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         break;
                     default:
                         {
+                            if (EmitInitException(propertyType))
+                            {
+                                return false;
+                            }
+
                             EmitBindCoreCallForProperty(property, propertyType, expressionForPropertyAccess);
                         }
                         break;
                 }
+
+                return true;
             }
 
             private void EmitBindLogicFromRootMethod(TypeSpec type, string expressionForMemberAccess, InitializationKind initKind)
@@ -828,22 +1009,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 {
                     EmitBindLogicFromRootMethod(((NullableSpec)type).UnderlyingType, expressionForMemberAccess, initKind);
                 }
-                else if (kind is TypeSpecKind.ParsableFromString)
+                else
                 {
-                    if (initKind is InitializationKind.Declaration)
+                    if (kind is TypeSpecKind.ParsableFromString)
                     {
-                        EmitCastToIConfigurationSection();
-                        _writer.WriteLine($"{GetTypeDisplayString(type)} {expressionForMemberAccess} = default!;");
+                        if (initKind is InitializationKind.Declaration)
+                        {
+                            EmitCastToIConfigurationSection();
+                            _writer.WriteLine($"{GetTypeDisplayString(type)} {expressionForMemberAccess} = default!;");
+                        }
+                        else
+                        {
+                            EmitCastToIConfigurationSection();
+                        }
+                        EmitBindLogicFromString((ParsableFromStringSpec)type, expressionForMemberAccess, Expression.sectionValue, Expression.sectionPath);
                     }
                     else
                     {
-                        EmitCastToIConfigurationSection();
+                        EmitBindCoreCall(type, expressionForMemberAccess, Identifier.configuration, initKind);
                     }
-                    EmitBindLogicFromString((ParsableFromStringSpec)type, expressionForMemberAccess, Expression.sectionValue, Expression.sectionPath);
-                }
-                else
-                {
-                    EmitBindCoreCall(type, expressionForMemberAccess, Identifier.configuration, initKind);
                 }
             }
 
@@ -853,6 +1037,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 string expressionForConfigArg,
                 InitializationKind initKind)
             {
+                Debug.Assert(type.CanInitialize);
+
                 string tempVarName = GetIncrementalVarName(Identifier.temp);
                 if (initKind is InitializationKind.AssignmentWithNullCheck)
                 {
@@ -921,24 +1107,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         _writer.WriteLine($"{expressionForPropertyAccess} = {tempVarName};");
                     }
                 }
-                else if (canGet)
+                else
                 {
-                    _writer.WriteLine($"{effectivePropertyTypeDisplayString} {tempVarName} = {expressionForPropertyAccess};");
-                    EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.AssignmentWithNullCheck);
-                    _writer.WriteLine($@"{Identifier.BindCore}({Identifier.section}, ref {tempVarName}, {Identifier.binderOptions});");
+                    if (canGet)
+                    {
+                        _writer.WriteLine($"{effectivePropertyTypeDisplayString} {tempVarName} = {expressionForPropertyAccess};");
+                        EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.AssignmentWithNullCheck);
+                        _writer.WriteLine($@"{Identifier.BindCore}({Identifier.section}, ref {tempVarName}, {Identifier.binderOptions});");
 
-                    if (canSet)
+                        if (canSet)
+                        {
+                            _writer.WriteLine($"{expressionForPropertyAccess} = {tempVarName};");
+                        }
+                    }
+                    else
                     {
+                        Debug.Assert(canSet);
+                        EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.Declaration);
+                        _writer.WriteLine($@"{Identifier.BindCore}({Identifier.section}, ref {tempVarName}, {Identifier.binderOptions});");
                         _writer.WriteLine($"{expressionForPropertyAccess} = {tempVarName};");
                     }
                 }
-                else
-                {
-                    Debug.Assert(canSet);
-                    EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.Declaration);
-                    _writer.WriteLine($@"{Identifier.BindCore}({Identifier.section}, ref {tempVarName}, {Identifier.binderOptions});");
-                    _writer.WriteLine($"{expressionForPropertyAccess} = {tempVarName};");
-                }
 
                 _writer.WriteBlockEnd();
             }
@@ -968,38 +1157,42 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 return;
             }
 
-            private void EmitObjectInit(TypeSpec type, string expressionForMemberAccess, InitializationKind initKind)
+            private bool EmitObjectInit(TypeSpec type, string expressionForMemberAccess, InitializationKind initKind)
             {
+                Debug.Assert(type.CanInitialize);
+
                 if (initKind is InitializationKind.None)
                 {
-                    return;
+                    // Reachable?
+                    return true;
                 }
 
                 string expressionForInit;
                 CollectionSpec? collectionType = type as CollectionSpec;
 
-                string typeDisplayString;
+                string effectiveDisplayString = GetTypeDisplayString(type);
                 if (collectionType is not null)
                 {
-                    if (collectionType is EnumerableSpec { PopulationStrategy: CollectionPopulationStrategy.Array })
+                    if (collectionType is EnumerableSpec { InitializationStrategy: InitializationStrategy.Array })
                     {
-                        typeDisplayString = GetTypeDisplayString(type);
-                        expressionForInit = $"new {_arrayBracketsRegex.Replace(typeDisplayString, "[0]", 1)}";
+                        expressionForInit = $"new {_arrayBracketsRegex.Replace(effectiveDisplayString, "[0]", 1)}";
                     }
                     else
                     {
-                        typeDisplayString = GetTypeDisplayString(collectionType.ConcreteType ?? collectionType);
-                        expressionForInit = $"new {typeDisplayString}()";
+                        effectiveDisplayString = GetTypeDisplayString(collectionType.ConcreteType ?? collectionType);
+                        expressionForInit = $"new {effectiveDisplayString}()";
                     }
                 }
-                else if (type.ConstructionStrategy is ConstructionStrategy.ParameterlessConstructor)
+                else if (type.InitializationStrategy is InitializationStrategy.ParameterlessConstructor)
                 {
-                    typeDisplayString = GetTypeDisplayString(type);
-                    expressionForInit = $"new {typeDisplayString}()";
+                    expressionForInit = $"new {effectiveDisplayString}()";
                 }
                 else
                 {
-                    return;
+                    Debug.Assert(type.InitializationStrategy is InitializationStrategy.ParameterizedConstructor);
+                    string expressionForConfigSection = initKind is InitializationKind.Declaration ? Identifier.configuration : Identifier.section;
+                    string initMethodIdentifier = GetHelperMethodDisplayString(((ObjectSpec)type).InitializeMethodDisplayString);
+                    expressionForInit = $"{initMethodIdentifier}({expressionForConfigSection}, {Identifier.binderOptions});";
                 }
 
                 if (initKind == InitializationKind.Declaration)
@@ -1009,14 +1202,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
                 else if (initKind == InitializationKind.AssignmentWithNullCheck)
                 {
-                    ConstructionStrategy? collectionConstructionStratey = collectionType?.ConstructionStrategy;
-                    if (collectionConstructionStratey is ConstructionStrategy.ParameterizedConstructor)
-                    {
-                        _writer.WriteLine($"{expressionForMemberAccess} = {expressionForMemberAccess} is null ? {expressionForInit} : new {typeDisplayString}({expressionForMemberAccess});");
-                    }
-                    else if (collectionConstructionStratey is ConstructionStrategy.ToEnumerableMethod)
+                    if (collectionType is CollectionSpec
+                        {
+                            InitializationStrategy: InitializationStrategy.ParameterizedConstructor or InitializationStrategy.ToEnumerableMethod
+                        })
                     {
-                        _writer.WriteLine($"{expressionForMemberAccess} = {expressionForMemberAccess} is null ? {expressionForInit} : {expressionForMemberAccess}.{collectionType.ToEnumerableMethodCall!};");
+                        if (collectionType.InitializationStrategy is InitializationStrategy.ParameterizedConstructor)
+                        {
+                            _writer.WriteLine($"{expressionForMemberAccess} = {expressionForMemberAccess} is null ? {expressionForInit} : new {effectiveDisplayString}({expressionForMemberAccess});");
+                        }
+                        else
+                        {
+                            _writer.WriteLine($"{expressionForMemberAccess} = {expressionForMemberAccess} is null ? {expressionForInit} : {expressionForMemberAccess}.{collectionType.ToEnumerableMethodCall!};");
+                        }
                     }
                     else
                     {
@@ -1025,8 +1223,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
                 else
                 {
+                    Debug.Assert(initKind is InitializationKind.SimpleAssignment);
                     _writer.WriteLine($"{expressionForMemberAccess} = {expressionForInit};");
                 }
+
+                return true;
             }
 
             private void EmitCollectionCastIfRequired(CollectionSpec type, out string objIdentifier)
index c167824..020c110 100644 (file)
@@ -7,6 +7,7 @@ using System.Collections.Immutable;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
+using System.Runtime.CompilerServices;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.Operations;
 
@@ -19,7 +20,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             private readonly SourceProductionContext _context;
             private readonly KnownTypeSymbols _typeSymbols;
 
-            private readonly Dictionary<BinderMethodSpecifier, HashSet<TypeSpec>> _rootConfigTypes = new();
+            private readonly Dictionary<BinderMethodSpecifier, HashSet<TypeSpec>> _configTypes = new();
 
             private readonly HashSet<ITypeSymbol> _unsupportedTypes = new(SymbolEqualityComparer.Default);
             private readonly Dictionary<ITypeSymbol, TypeSpec?> _createdSpecs = new(SymbolEqualityComparer.Default);
@@ -28,6 +29,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             private readonly HashSet<string> _typeNamespaces = new()
             {
                 "System",
+                "System.Collections.Generic",
                 "System.Globalization",
                 "Microsoft.Extensions.Configuration"
             };
@@ -83,10 +85,10 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
 
                 return new SourceGenerationSpec(
-                    _rootConfigTypes,
+                    _configTypes,
                     _methodsToGen,
                     _primitivesForHelperGen,
-                    _typeNamespaces);
+                    _typeNamespaces.ToImmutableSortedSet());
             }
 
             private void ProcessBindCall(BinderInvocationOperation binderOperation)
@@ -318,23 +320,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
             }
 
-            private TypeSpec? AddRootConfigType(BinderMethodSpecifier methodGroup, BinderMethodSpecifier overload, ITypeSymbol type, Location? location)
+            private void AddRootConfigType(BinderMethodSpecifier methodGroup, BinderMethodSpecifier overload, ITypeSymbol type, Location? location)
             {
                 if (type is INamedTypeSymbol namedType && ContainsGenericParameters(namedType))
                 {
-                    return null;
+                    return;
                 }
 
-                TypeSpec? spec = GetOrCreateTypeSpec(type, location);
-                if (spec != null)
+                if (GetOrCreateTypeSpec(type, location) is TypeSpec spec)
                 {
-                    AddToRootConfigTypeCache(overload, spec);
-                    AddToRootConfigTypeCache(methodGroup, spec);
-
-                    _methodsToGen |= overload;
+                    RegisterConfigType(spec, overload);
+                    RegisterConfigType(spec, methodGroup, isMethodGroup: true);
                 }
-
-                return spec;
             }
 
             private TypeSpec? GetOrCreateTypeSpec(ITypeSymbol type, Location? location = null)
@@ -344,6 +341,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return spec;
                 }
 
+                bool canInitialize = true;
+
                 if (IsNullable(type, out ITypeSymbol? underlyingType))
                 {
                     spec = TryGetTypeSpec(underlyingType, ParserDiagnostics.NullableUnderlyingTypeNotSupported, out TypeSpec? underlyingTypeSpec)
@@ -397,29 +396,37 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     _typeNamespaces.Add(@namespace);
                 }
 
-                _createdSpecs[type] = spec;
-                return spec;
+                spec = canInitialize ? spec : null;
+                return _createdSpecs[type] = spec;
 
                 void RegisterBindCoreGenType(TypeSpec? spec)
                 {
                     if (spec is not null)
                     {
-                        AddToRootConfigTypeCache(BinderMethodSpecifier.BindCore, spec);
-                        _methodsToGen |= BinderMethodSpecifier.BindCore;
+                        if (spec.CanInitialize)
+                        {
+                            canInitialize = true;
+                            RegisterConfigType(spec, BinderMethodSpecifier.BindCore);
+                        }
                     }
                 }
             }
 
-            private void AddToRootConfigTypeCache(BinderMethodSpecifier method, TypeSpec spec)
+            private void RegisterConfigType(TypeSpec spec, BinderMethodSpecifier binderMethod, bool isMethodGroup = false)
             {
                 Debug.Assert(spec is not null);
 
-                if (!_rootConfigTypes.TryGetValue(method, out HashSet<TypeSpec> types))
+                if (!_configTypes.TryGetValue(binderMethod, out HashSet<TypeSpec> types))
                 {
-                    _rootConfigTypes[method] = types = new HashSet<TypeSpec>();
+                    _configTypes[binderMethod] = types = new HashSet<TypeSpec>();
                 }
 
                 types.Add(spec);
+
+                if (!isMethodGroup)
+                {
+                    _methodsToGen |= binderMethod;
+                }
             }
 
             private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType)
@@ -560,23 +567,22 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return null;
                 }
 
-                // We want a BindCore method for List<TElement> as a temp holder for the array values.
-                EnumerableSpec? listSpec = GetOrCreateTypeSpec(_typeSymbols.List.Construct(arrayType.ElementType)) as EnumerableSpec;
-                // We know the element type is supported.
-                Debug.Assert(listSpec != null);
-                if (listSpec is not null)
-                {
-                    AddToRootConfigTypeCache(BinderMethodSpecifier.BindCore, listSpec);
-                }
+                // We want a BindCore method for List<TElement> as a temp holder for the array values. We know the element type is supported.
+                EnumerableSpec listSpec = (GetOrCreateTypeSpec(_typeSymbols.List.Construct(arrayType.ElementType)) as EnumerableSpec)!;
+                RegisterConfigType(listSpec, BinderMethodSpecifier.BindCore);
 
-                return new EnumerableSpec(arrayType)
+                EnumerableSpec spec = new EnumerableSpec(arrayType)
                 {
                     Location = location,
                     ElementType = elementSpec,
                     ConcreteType = listSpec,
-                    PopulationStrategy = CollectionPopulationStrategy.Array,
+                    InitializationStrategy = InitializationStrategy.Array,
+                    PopulationStrategy = CollectionPopulationStrategy.Cast_Then_Add, // Using the concrete list type as a temp holder.
                     ToEnumerableMethodCall = null,
                 };
+
+                Debug.Assert(spec.CanInitialize);
+                return spec;
             }
 
             private bool IsSupportedArrayType(ITypeSymbol type, Location? location)
@@ -597,12 +603,23 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private CollectionSpec? CreateCollectionSpec(INamedTypeSymbol type, Location? location)
             {
+                CollectionSpec? spec;
                 if (IsCandidateDictionary(type, out ITypeSymbol keyType, out ITypeSymbol elementType))
                 {
-                    return CreateDictionarySpec(type, location, keyType, elementType);
+                    spec = CreateDictionarySpec(type, location, keyType, elementType);
+                    Debug.Assert(spec is null or DictionarySpec { KeyType: null or ParsableFromStringSpec });
+                }
+                else
+                {
+                    spec = CreateEnumerableSpec(type, location);
                 }
 
-                return CreateEnumerableSpec(type, location);
+                if (spec is not null)
+                {
+                    spec.InitExceptionMessage ??= spec.ElementType.InitExceptionMessage;
+                }
+
+                return spec;
             }
 
             private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, Location? location, ITypeSymbol keyType, ITypeSymbol elementType)
@@ -619,15 +636,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return null;
                 }
 
-                ConstructionStrategy constructionStrategy;
+                InitializationStrategy constructionStrategy;
                 CollectionPopulationStrategy populationStrategy;
                 INamedTypeSymbol? concreteType = null;
                 INamedTypeSymbol? populationCastType = null;
                 string? toEnumerableMethodCall = null;
 
-                if (HasPublicParameterlessCtor(type))
+                if (HasPublicParameterLessCtor(type))
                 {
-                    constructionStrategy = ConstructionStrategy.ParameterlessConstructor;
+                    constructionStrategy = InitializationStrategy.ParameterlessConstructor;
 
                     if (HasAddMethod(type, keyType, elementType))
                     {
@@ -647,14 +664,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 else if (IsInterfaceMatch(type, _typeSymbols.GenericIDictionary_Unbound) || IsInterfaceMatch(type, _typeSymbols.IDictionary))
                 {
                     concreteType = _typeSymbols.Dictionary;
-                    constructionStrategy = ConstructionStrategy.ParameterlessConstructor;
+                    constructionStrategy = InitializationStrategy.ParameterlessConstructor;
                     populationStrategy = CollectionPopulationStrategy.Add;
                 }
                 else if (IsInterfaceMatch(type, _typeSymbols.IReadOnlyDictionary_Unbound))
                 {
                     concreteType = _typeSymbols.Dictionary;
                     populationCastType = _typeSymbols.GenericIDictionary;
-                    constructionStrategy = ConstructionStrategy.ToEnumerableMethod;
+                    constructionStrategy = InitializationStrategy.ToEnumerableMethod;
                     populationStrategy = CollectionPopulationStrategy.Cast_Then_Add;
                     toEnumerableMethodCall = "ToDictionary(pair => pair.Key, pair => pair.Value)";
                     _typeNamespaces.Add("System.Linq");
@@ -670,14 +687,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     Location = location,
                     KeyType = (ParsableFromStringSpec)keySpec,
                     ElementType = elementSpec,
-                    ConstructionStrategy = constructionStrategy,
+                    InitializationStrategy = constructionStrategy,
                     PopulationStrategy = populationStrategy,
                     ToEnumerableMethodCall = toEnumerableMethodCall,
                 };
 
                 Debug.Assert(!(populationStrategy is CollectionPopulationStrategy.Cast_Then_Add && populationCastType is null));
-                spec.ConcreteType = ConstructGenericCollectionTypeSpec(concreteType, keyType, elementType);
-                spec.PopulationCastType = ConstructGenericCollectionTypeSpec(populationCastType, keyType, elementType);
+                spec.ConcreteType = ConstructGenericCollectionSpecIfRequired(concreteType, keyType, elementType);
+                spec.PopulationCastType = ConstructGenericCollectionSpecIfRequired(populationCastType, keyType, elementType);
 
                 return spec;
             }
@@ -690,14 +707,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return null;
                 }
 
-                ConstructionStrategy constructionStrategy;
+                InitializationStrategy constructionStrategy;
                 CollectionPopulationStrategy populationStrategy;
                 INamedTypeSymbol? concreteType = null;
                 INamedTypeSymbol? populationCastType = null;
 
-                if (HasPublicParameterlessCtor(type))
+                if (HasPublicParameterLessCtor(type))
                 {
-                    constructionStrategy = ConstructionStrategy.ParameterlessConstructor;
+                    constructionStrategy = InitializationStrategy.ParameterlessConstructor;
 
                     if (HasAddMethod(type, elementType))
                     {
@@ -718,34 +735,34 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     IsInterfaceMatch(type, _typeSymbols.GenericIList_Unbound))
                 {
                     concreteType = _typeSymbols.List;
-                    constructionStrategy = ConstructionStrategy.ParameterlessConstructor;
+                    constructionStrategy = InitializationStrategy.ParameterlessConstructor;
                     populationStrategy = CollectionPopulationStrategy.Add;
                 }
                 else if (IsInterfaceMatch(type, _typeSymbols.GenericIEnumerable_Unbound))
                 {
                     concreteType = _typeSymbols.List;
                     populationCastType = _typeSymbols.GenericICollection;
-                    constructionStrategy = ConstructionStrategy.ParameterizedConstructor;
+                    constructionStrategy = InitializationStrategy.ParameterizedConstructor;
                     populationStrategy = CollectionPopulationStrategy.Cast_Then_Add;
                 }
                 else if (IsInterfaceMatch(type, _typeSymbols.ISet_Unbound))
                 {
                     concreteType = _typeSymbols.HashSet;
-                    constructionStrategy = ConstructionStrategy.ParameterlessConstructor;
+                    constructionStrategy = InitializationStrategy.ParameterlessConstructor;
                     populationStrategy = CollectionPopulationStrategy.Add;
                 }
                 else if (IsInterfaceMatch(type, _typeSymbols.IReadOnlySet_Unbound))
                 {
                     concreteType = _typeSymbols.HashSet;
                     populationCastType = _typeSymbols.ISet;
-                    constructionStrategy = ConstructionStrategy.ParameterizedConstructor;
+                    constructionStrategy = InitializationStrategy.ParameterizedConstructor;
                     populationStrategy = CollectionPopulationStrategy.Cast_Then_Add;
                 }
                 else if (IsInterfaceMatch(type, _typeSymbols.IReadOnlyList_Unbound) || IsInterfaceMatch(type, _typeSymbols.IReadOnlyCollection_Unbound))
                 {
                     concreteType = _typeSymbols.List;
                     populationCastType = _typeSymbols.GenericICollection;
-                    constructionStrategy = ConstructionStrategy.ParameterizedConstructor;
+                    constructionStrategy = InitializationStrategy.ParameterizedConstructor;
                     populationStrategy = CollectionPopulationStrategy.Cast_Then_Add;
                 }
                 else
@@ -760,73 +777,174 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 {
                     Location = location,
                     ElementType = elementSpec,
-                    ConstructionStrategy = constructionStrategy,
+                    InitializationStrategy = constructionStrategy,
                     PopulationStrategy = populationStrategy,
                     ToEnumerableMethodCall = null,
                 };
 
                 Debug.Assert(!(populationStrategy is CollectionPopulationStrategy.Cast_Then_Add && populationCastType is null));
-                spec.ConcreteType = ConstructGenericCollectionTypeSpec(concreteType, elementType);
-                spec.PopulationCastType = ConstructGenericCollectionTypeSpec(populationCastType, elementType);
+                spec.ConcreteType = ConstructGenericCollectionSpecIfRequired(concreteType, elementType);
+                spec.PopulationCastType = ConstructGenericCollectionSpecIfRequired(populationCastType, elementType);
 
                 return spec;
             }
 
             private ObjectSpec? CreateObjectSpec(INamedTypeSymbol type, Location? location)
             {
-                Debug.Assert(!_createdSpecs.ContainsKey(type));
-
                 // Add spec to cache before traversing properties to avoid stack overflow.
-                if (!HasPublicParameterlessCtor(type))
+                ObjectSpec objectSpec = new(type) { Location = location };
+                _createdSpecs.Add(type, objectSpec);
+
+                string typeName = objectSpec.Name;
+                IMethodSymbol? ctor = null;
+                DiagnosticDescriptor? diagnosticDescriptor = null;
+
+                if (!(type.IsAbstract || type.TypeKind is TypeKind.Interface))
                 {
-                    ReportUnsupportedType(type, ParserDiagnostics.NeedPublicParameterlessConstructor, location);
-                    _createdSpecs.Add(type, null);
-                    return null;
+                    IMethodSymbol? parameterlessCtor = null;
+                    IMethodSymbol? parameterizedCtor = null;
+                    bool hasMultipleParameterizedCtors = false;
+
+                    foreach (IMethodSymbol candidate in type.InstanceConstructors)
+                    {
+                        if (candidate.DeclaredAccessibility is not Accessibility.Public)
+                        {
+                            continue;
+                        }
+
+                        if (candidate.Parameters.Length is 0)
+                        {
+                            parameterlessCtor = candidate;
+                        }
+                        else if (parameterizedCtor is not null)
+                        {
+                            hasMultipleParameterizedCtors = true;
+                        }
+                        else
+                        {
+                            parameterizedCtor = candidate;
+                        }
+                    }
+
+                    bool hasPublicParameterlessCtor = type.IsValueType || parameterlessCtor is not null;
+                    if (!hasPublicParameterlessCtor && hasMultipleParameterizedCtors)
+                    {
+                        diagnosticDescriptor = ParserDiagnostics.MultipleParameterizedConstructors;
+                        objectSpec.InitExceptionMessage = string.Format(ExceptionMessages.MultipleParameterizedConstructors, typeName);
+                    }
+
+                    ctor = type.IsValueType
+                        // Roslyn ctor fetching APIs include paramerterless ctors for structs, unlike System.Reflection.
+                        ? parameterizedCtor ?? parameterlessCtor
+                        : parameterlessCtor ?? parameterizedCtor;
+                }
+
+                objectSpec.InitializationStrategy = ctor?.Parameters.Length is 0 ? InitializationStrategy.ParameterlessConstructor : InitializationStrategy.ParameterizedConstructor;
+
+                if (ctor is null)
+                {
+                    diagnosticDescriptor = ParserDiagnostics.MissingPublicInstanceConstructor;
+                    objectSpec.InitExceptionMessage = string.Format(ExceptionMessages.MissingPublicInstanceConstructor, typeName);
+                }
+
+                if (diagnosticDescriptor is not null)
+                {
+                    Debug.Assert(objectSpec.InitExceptionMessage is not null);
+                    ReportUnsupportedType(type, diagnosticDescriptor);
+                    return objectSpec;
                 }
-                ObjectSpec objectSpec = new(type) { Location = location, ConstructionStrategy = ConstructionStrategy.ParameterlessConstructor };
-                _createdSpecs.Add(type, objectSpec);
 
                 INamedTypeSymbol current = type;
                 while (current is not null)
                 {
-                    foreach (ISymbol member in current.GetMembers())
+                    var members = current.GetMembers();
+                    foreach (ISymbol member in members)
                     {
-                        if (member is IPropertySymbol { IsIndexer: false } property)
+                        if (member is IPropertySymbol { IsIndexer: false, IsImplicitlyDeclared: false } property)
                         {
-                            if (property.Type is ITypeSymbol { } propertyType)
+                            AttributeData? attributeData = property.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.ConfigurationKeyNameAttribute));
+                            string propertyName = property.Name;
+                            string configKeyName = attributeData?.ConstructorArguments.FirstOrDefault().Value as string ?? propertyName;
+
+                            TypeSpec? propertyTypeSpec = GetOrCreateTypeSpec(property.Type);
+                            if (propertyTypeSpec is null)
+                            {
+                                _context.ReportDiagnostic(Diagnostic.Create(ParserDiagnostics.PropertyNotSupported, location, new string[] { propertyName, type.ToDisplayString() }));
+                            }
+                            else
                             {
-                                AttributeData? attributeData = property.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.ConfigurationKeyNameAttribute));
-                                string propertyName = property.Name;
-                                string configKeyName = attributeData?.ConstructorArguments.FirstOrDefault().Value as string ?? propertyName;
+                                PropertySpec spec = new(property) { Type = propertyTypeSpec, ConfigurationKeyName = configKeyName };
+                                objectSpec.Properties[propertyName] = spec;
+                                RegisterHasChildrenHelperForGenIfRequired(propertyTypeSpec);
+                            }
+                        }
+                    }
+                    current = current.BaseType;
+                }
 
-                                TypeSpec? propertyTypeSpec = GetOrCreateTypeSpec(propertyType);
-                                PropertySpec spec;
+                if (objectSpec.InitializationStrategy is InitializationStrategy.ParameterizedConstructor)
+                {
+                    List<string> missingParameters = new();
+                    List<string> invalidParameters = new();
 
-                                if (propertyTypeSpec is null)
-                                {
-                                    _context.ReportDiagnostic(Diagnostic.Create(ParserDiagnostics.PropertyNotSupported, location, new string[] { propertyName, type.ToDisplayString() }));
-                                }
-                                else
-                                {
-                                    RegisterHasChildrenHelperForGenIfRequired(propertyTypeSpec);
-                                }
+                    foreach (IParameterSymbol parameter in ctor.Parameters)
+                    {
+                        string parameterName = parameter.Name;
 
+                        if (!objectSpec.Properties.TryGetValue(parameterName, out PropertySpec? propertySpec))
+                        {
+                            missingParameters.Add(parameterName);
+                        }
+                        else if (parameter.RefKind is not RefKind.None)
+                        {
+                            invalidParameters.Add(parameterName);
+                        }
+                        else
+                        {
+                            ParameterSpec paramSpec = new ParameterSpec(parameter)
+                            {
+                                Type = propertySpec.Type,
+                                ConfigurationKeyName = propertySpec.ConfigurationKeyName,
+                            };
 
-                                spec = new PropertySpec(property) { Type = propertyTypeSpec, ConfigurationKeyName = configKeyName };
-                                objectSpec.Properties[configKeyName] = spec;
-                            }
+                            propertySpec.MatchingCtorParam = paramSpec;
+                            objectSpec.ConstructorParameters.Add(paramSpec);
                         }
                     }
-                    current = current.BaseType;
+
+                    if (invalidParameters.Count > 0)
+                    {
+                        objectSpec.InitExceptionMessage = string.Format(ExceptionMessages.CannotBindToConstructorParameter, typeName, FormatParams(invalidParameters));
+                    }
+                    else if (missingParameters.Count > 0)
+                    {
+                        if (type.IsValueType)
+                        {
+                            objectSpec.InitializationStrategy = InitializationStrategy.ParameterlessConstructor;
+                        }
+                        else
+                        {
+                            objectSpec.InitExceptionMessage = string.Format(ExceptionMessages.ConstructorParametersDoNotMatchProperties, typeName, FormatParams(missingParameters));
+                        }
+                    }
+
+                    if (objectSpec.CanInitialize)
+                    {
+                        RegisterConfigType(objectSpec, BinderMethodSpecifier.Initialize);
+                    }
+
+                    static string FormatParams(List<string> names) => string.Join(",", names);
                 }
 
+                Debug.Assert((objectSpec.CanInitialize && objectSpec.InitExceptionMessage is null) ||
+                    (!objectSpec.CanInitialize && objectSpec.InitExceptionMessage is not null));
+
                 return objectSpec;
             }
 
             private void RegisterHasChildrenHelperForGenIfRequired(TypeSpec type)
             {
                 if (type.SpecKind is TypeSpecKind.Object or
-                                        TypeSpecKind.Array or
                                         TypeSpecKind.Enumerable or
                                         TypeSpecKind.Dictionary)
                 {
@@ -921,28 +1039,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 return false;
             }
 
-            private static bool HasPublicParameterlessCtor(INamedTypeSymbol type)
-            {
-                if (type.IsAbstract || type.TypeKind == TypeKind.Interface)
-                {
-                    return false;
-                }
-
-                if (type is not INamedTypeSymbol namedType)
-                {
-                    return false;
-                }
-
-                foreach (IMethodSymbol ctor in namedType.InstanceConstructors)
-                {
-                    if (ctor.DeclaredAccessibility == Accessibility.Public && ctor.Parameters.Length == 0)
-                    {
-                        return true;
-                    }
-                }
-
-                return false;
-            }
+            private static bool HasPublicParameterLessCtor(INamedTypeSymbol type) =>
+                type.InstanceConstructors.SingleOrDefault(ctor => ctor.DeclaredAccessibility is Accessibility.Public && ctor.Parameters.Length is 0) is not null;
 
             private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol element)
             {
@@ -979,7 +1077,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private static bool IsEnum(ITypeSymbol type) => type is INamedTypeSymbol { EnumUnderlyingType: INamedTypeSymbol { } };
 
-            private CollectionSpec? ConstructGenericCollectionTypeSpec(INamedTypeSymbol? collectionType, params ITypeSymbol[] parameters) =>
+            private CollectionSpec? ConstructGenericCollectionSpecIfRequired(INamedTypeSymbol? collectionType, params ITypeSymbol[] parameters) =>
                 (collectionType is not null ? ConstructGenericCollectionSpec(collectionType, parameters) : null);
 
             private CollectionSpec? ConstructGenericCollectionSpec(INamedTypeSymbol type, params ITypeSymbol[] parameters)
index 7929036..b0d3756 100644 (file)
@@ -1,6 +1,10 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using Microsoft.CodeAnalysis;
+using System;
+using System.Diagnostics;
+
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     public sealed partial class ConfigurationBindingGenerator
@@ -39,7 +43,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             public static class Identifier
             {
                 public const string binderOptions = nameof(binderOptions);
-                public const string configureActions = nameof(configureActions);
+                public const string configureOptions = nameof(configureOptions);
                 public const string configuration = nameof(configuration);
                 public const string defaultValue = nameof(defaultValue);
                 public const string element = nameof(element);
@@ -79,6 +83,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 public const string GetValue = nameof(GetValue);
                 public const string GetValueCore = nameof(GetValueCore);
                 public const string HasChildren = nameof(HasChildren);
+                public const string HasConfig = nameof(HasConfig);
                 public const string HasValueOrChildren = nameof(HasValueOrChildren);
                 public const string HasValue = nameof(HasValue);
                 public const string Helpers = nameof(Helpers);
@@ -133,6 +138,21 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 _writer.WriteBlankLine();
             }
 
+            private bool EmitInitException(TypeSpec type)
+            {
+                Debug.Assert(type.InitializationStrategy is not InitializationStrategy.None);
+
+                if (!type.CanInitialize)
+                {
+                    _writer.WriteLine(GetInitException(type.InitExceptionMessage) + ";");
+                    return true;
+                }
+
+                return false;
+            }
+
+            private string GetInitException(string message) => $@"throw new {GetInvalidOperationDisplayName()}(""{message}"")";
+
             private string GetIncrementalVarName(string prefix) => $"{prefix}{_parseValueCount++}";
 
             private string GetTypeDisplayString(TypeSpec type) => _useFullyQualifiedNames ? type.FullyQualifiedDisplayString : type.MinimalDisplayString;
@@ -146,6 +166,22 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 return methodName;
             }
+
+            private static string GetExpressionForArgument(ParameterSpec parameter)
+            {
+                string name = parameter.Name + (parameter.HasExplicitDefaultValue ? string.Empty : $".{Identifier.Value}");
+
+                return parameter.RefKind switch
+                {
+                    RefKind.None => name,
+                    RefKind.Ref => $"ref {name}",
+                    RefKind.Out => "out _",
+                    RefKind.In => $"in {name}",
+                    _ => throw new InvalidOperationException()
+                };
+            }
+
+            private string GetInvalidOperationDisplayName() => _useFullyQualifiedNames ? FullyQualifiedDisplayName.InvalidOperationException : Identifier.InvalidOperationException;
         }
     }
 }
index a90f015..223c19c 100644 (file)
@@ -6,9 +6,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
     // Runtime exception messages; not localized so we keep them in source.
     internal static class ExceptionMessages
     {
+        public const string CannotBindToConstructorParameter = "Cannot create instance of type '{0}' because one or more parameters cannot be bound to. Constructor parameters cannot be declared as in, out, or ref. Invalid parameters are: '{1}'";
         public const string CannotSpecifyBindNonPublicProperties = "The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.";
+        public const string ConstructorParametersDoNotMatchProperties = "Cannot create instance of type '{0}' because one or more parameters cannot be bound to. Constructor parameters must have corresponding properties. Fields are not supported. Missing properties are: '{1}'";
         public const string FailedBinding = "Failed to convert configuration value at '{0}' to type '{1}'.";
         public const string MissingConfig = "'{0}' was set on the provided {1}, but the following properties were not found on the instance of {2}: {3}";
+        public const string MissingPublicInstanceConstructor = "Cannot create instance of type '{0}' because it is missing a public instance constructor.";
+        public const string MultipleParameterizedConstructors = "Cannot create instance of type '{0}' because it has multiple public parameterized constructors.";
+        public const string ParameterBeingBoundToIsUnnamed = "Cannot create instance of type '{0}' because one or more parameters are unnamed.";
+        public const string ParameterHasNoMatchingConfig = "Cannot create instance of type '{0}' because parameter '{1}' has no matching config. Each parameter in the constructor that does not have a default value must have a corresponding config entry.";
         public const string TypeNotDetectedAsInput = "Unable to bind to type '{0}': generator did not detect the type as input.";
         public const string TypeNotSupportedAsInput = "Unable to bind to type '{0}': generator does not support this type as input to this method.";
     }
index 279eea9..be26314 100644 (file)
@@ -9,10 +9,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
     internal static class ParserDiagnostics
     {
         public static DiagnosticDescriptor TypeNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.TypeNotSupported));
-        public static DiagnosticDescriptor NeedPublicParameterlessConstructor { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.NeedPublicParameterlessConstructor));
+        public static DiagnosticDescriptor MissingPublicInstanceConstructor { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.MissingPublicInstanceConstructor));
         public static DiagnosticDescriptor CollectionNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.CollectionNotSupported));
         public static DiagnosticDescriptor DictionaryKeyNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.DictionaryKeyNotSupported));
         public static DiagnosticDescriptor ElementTypeNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.ElementTypeNotSupported));
+        public static DiagnosticDescriptor MultipleParameterizedConstructors { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.MultipleParameterizedConstructors));
         public static DiagnosticDescriptor MultiDimArraysNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.MultiDimArraysNotSupported));
         public static DiagnosticDescriptor NullableUnderlyingTypeNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.NullableUnderlyingTypeNotSupported));
 
index 2f2e296..c3929ec 100644 (file)
     <Compile Include="$(CommonPath)\Roslyn\GetBestTypeByMetadataName.cs" Link="Common\Roslyn\GetBestTypeByMetadataName.cs" />
     <Compile Include="ConfigurationBindingGenerator.cs" />
     <Compile Include="ConfigurationBindingGenerator.Emitter.cs" />
-    <Compile Include="Helpers\Emitter.Helpers.cs" />
-    <Compile Include="Helpers\ParserDiagnostics.cs" />
     <Compile Include="ConfigurationBindingGenerator.Parser.cs" />
+    <Compile Include="Helpers\Emitter.Helpers.cs" />
     <Compile Include="Helpers\ExceptionMessages.cs" />
     <Compile Include="Helpers\KnownTypeSymbols.cs" />
+    <Compile Include="Helpers\ParserDiagnostics.cs" />
+    <Compile Include="Helpers\SourceWriter.cs" />
     <Compile Include="Model\BinderInvocationOperation.cs" />
     <Compile Include="Model\BinderMethodSpecifier.cs" />
-    <Compile Include="Model\SourceGenerationSpec.cs" />
-    <Compile Include="Helpers\SourceWriter.cs" />
     <Compile Include="Model\CollectionSpec.cs" />
-    <Compile Include="Model\ConstructionStrategy.cs" />
     <Compile Include="Model\ConfigurationSectionSpec.cs" />
+    <Compile Include="Model\InitializationStrategy.cs" />
     <Compile Include="Model\NullableSpec.cs" />
     <Compile Include="Model\ObjectSpec.cs" />
+    <Compile Include="Model\ParameterSpec.cs" />
     <Compile Include="Model\ParsableFromStringSpec.cs" />
     <Compile Include="Model\PropertySpec.cs" />
+    <Compile Include="Model\SourceGenerationSpec.cs" />
     <Compile Include="Model\TypeSpec.cs" />
   </ItemGroup>
 </Project>
index 37a39f7..33b2859 100644 (file)
@@ -74,6 +74,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
         // Binding helpers
         BindCore = 0x1000,
         HasChildren = 0x4000,
+        Initialize = 0x8000,
 
         // Method groups
         Bind = Bind_instance | Bind_instance_BinderOptions | Bind_key_instance,
index 7054a35..7c90701 100644 (file)
@@ -17,6 +17,10 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
         public required CollectionPopulationStrategy PopulationStrategy { get; init; }
 
+        public override bool CanInitialize => ConcreteType?.CanInitialize ?? CanInitCompexType;
+
+        public override required InitializationStrategy InitializationStrategy { get; set; }
+
         public required string? ToEnumerableMethodCall { get; init; }
     }
 
@@ -38,9 +42,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
     internal enum CollectionPopulationStrategy
     {
-        Unknown,
-        Array,
-        Add,
-        Cast_Then_Add,
+        Unknown = 0,
+        Add = 1,
+        Cast_Then_Add = 2,
     }
 }
@@ -3,11 +3,12 @@
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
-    internal enum ConstructionStrategy
+    internal enum InitializationStrategy
     {
         None = 0,
         ParameterlessConstructor = 1,
         ParameterizedConstructor = 2,
         ToEnumerableMethod = 3,
+        Array = 4,
     }
 }
index e79696f..c59e17e 100644 (file)
@@ -1,6 +1,7 @@
 // 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 Microsoft.CodeAnalysis;
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
@@ -12,5 +13,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
         public override TypeSpecKind SpecKind => TypeSpecKind.Nullable;
 
         public required TypeSpec UnderlyingType { get; init; }
+
+        public override string? InitExceptionMessage
+        {
+            get => UnderlyingType.InitExceptionMessage;
+            set => throw new InvalidOperationException();
+        }
     }
 }
index 046d521..4dbfc4a 100644 (file)
@@ -1,6 +1,7 @@
 // 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;
 using Microsoft.CodeAnalysis;
 
@@ -8,10 +9,21 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     internal sealed record ObjectSpec : TypeSpec
     {
-        public ObjectSpec(INamedTypeSymbol type) : base(type) { }
+        public ObjectSpec(INamedTypeSymbol type) : base(type)
+        {
+            InitializeMethodDisplayString = $"Initialize{type.Name.Replace(".", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty)}";
+        }
 
         public override TypeSpecKind SpecKind => TypeSpecKind.Object;
 
-        public Dictionary<string, PropertySpec?> Properties { get; } = new();
+        public override InitializationStrategy InitializationStrategy { get; set; }
+
+        public override bool CanInitialize => CanInitCompexType;
+
+        public Dictionary<string, PropertySpec> Properties { get; } = new(StringComparer.OrdinalIgnoreCase);
+
+        public List<ParameterSpec> ConstructorParameters { get; } = new();
+
+        public string? InitializeMethodDisplayString { get; }
     }
 }
diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParameterSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParameterSpec.cs
new file mode 100644 (file)
index 0000000..a62f608
--- /dev/null
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
+{
+    internal sealed record ParameterSpec
+    {
+        public ParameterSpec(IParameterSymbol parameter)
+        {
+            Name = parameter.Name;
+            RefKind = parameter.RefKind;
+
+            HasExplicitDefaultValue = parameter.HasExplicitDefaultValue;
+            if (HasExplicitDefaultValue)
+            {
+                string formatted = SymbolDisplay.FormatPrimitive(parameter.ExplicitDefaultValue, quoteStrings: true, useHexadecimalNumbers: false);
+                DefaultValue = formatted is "null" ? "default!" : formatted;
+            }
+        }
+
+        public required TypeSpec Type { get; init; }
+
+        public string Name { get; }
+
+        public required string ConfigurationKeyName { get; init; }
+
+        public RefKind RefKind { get; }
+
+        public bool HasExplicitDefaultValue { get; init; }
+
+        public string DefaultValue { get; } = "default!";
+    }
+}
index c307c9b..35d79a2 100644 (file)
@@ -11,25 +11,37 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
         {
             Name = property.Name;
             IsStatic = property.IsStatic;
-            CanGet = property.GetMethod is IMethodSymbol { DeclaredAccessibility: Accessibility.Public, IsInitOnly: false };
-            CanSet = property.SetMethod is IMethodSymbol { DeclaredAccessibility: Accessibility.Public, IsInitOnly: false };
+
+            bool setterIsPublic = property.SetMethod?.DeclaredAccessibility is Accessibility.Public;
+            IsInitOnly = property.SetMethod?.IsInitOnly == true;
+            IsRequired = property.IsRequired;
+            SetOnInit = setterIsPublic && (IsInitOnly || IsRequired);
+            CanSet = setterIsPublic && !IsInitOnly;
+            CanGet = property.GetMethod?.DeclaredAccessibility is Accessibility.Public;
         }
 
+        public required TypeSpec Type { get; init; }
+
+        public ParameterSpec? MatchingCtorParam { get; set; }
+
         public string Name { get; }
 
         public bool IsStatic { get; }
 
+        public bool IsRequired { get; }
+
+        public bool IsInitOnly { get; }
+
+        public bool SetOnInit { get; }
+
         public bool CanGet { get; }
 
         public bool CanSet { get; }
 
-        public required TypeSpec? Type { get; init; }
-
         public required string ConfigurationKeyName { get; init; }
 
         public bool ShouldBind() =>
             (CanGet || CanSet) &&
-            Type is not null &&
-            !(!CanSet && (Type as CollectionSpec)?.ConstructionStrategy is ConstructionStrategy.ParameterizedConstructor);
+            !(!CanSet && (Type as CollectionSpec)?.InitializationStrategy is InitializationStrategy.ParameterizedConstructor);
     }
 }
index db8e1aa..f33a298 100644 (file)
@@ -2,14 +2,15 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.Collections.Immutable;
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     internal sealed record SourceGenerationSpec(
-        Dictionary<BinderMethodSpecifier, HashSet<TypeSpec>> RootConfigTypes,
+        Dictionary<BinderMethodSpecifier, HashSet<TypeSpec>> ConfigTypes,
         BinderMethodSpecifier MethodsToGen,
         HashSet<ParsableFromStringSpec> PrimitivesForHelperGen,
-        HashSet<string> TypeNamespaces)
+        ImmutableSortedSet<string> TypeNamespaces)
     {
         public bool HasRootMethods() =>
             ShouldEmitMethods(BinderMethodSpecifier.Get | BinderMethodSpecifier.Bind | BinderMethodSpecifier.Configure | BinderMethodSpecifier.GetValue);
index 3b41673..f53b929 100644 (file)
@@ -15,12 +15,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
         public TypeSpec(ITypeSymbol type)
         {
+            IsValueType = type.IsValueType;
+            Namespace = type.ContainingNamespace?.ToDisplayString();
             FullyQualifiedDisplayString = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
             MinimalDisplayString = type.ToDisplayString(s_minimalDisplayFormat);
-            Namespace = type.ContainingNamespace?.ToDisplayString();
-            IsValueType = type.IsValueType;
+            Name = Namespace + "." + MinimalDisplayString.Replace(".", "+");
         }
 
+        public string Name { get; }
+
         public string FullyQualifiedDisplayString { get; }
 
         public string MinimalDisplayString { get; }
@@ -31,12 +34,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
         public abstract TypeSpecKind SpecKind { get; }
 
-        public virtual ConstructionStrategy ConstructionStrategy { get; init; }
+        public virtual InitializationStrategy InitializationStrategy { get; set; }
+
+        public virtual string? InitExceptionMessage { get; set; }
+
+        public virtual bool CanInitialize => true;
 
         /// <summary>
-        /// Where in the input compilation we picked up a call to Bind, Get, or Configure.
+        /// Location in the input compilation we picked up a call to Bind, Get, or Configure.
         /// </summary>
         public required Location? Location { get; init; }
+
+        protected bool CanInitCompexType => InitializationStrategy is not InitializationStrategy.None && InitExceptionMessage is null;
     }
 
     internal enum TypeSpecKind
@@ -44,10 +53,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
         Unknown = 0,
         ParsableFromString = 1,
         Object = 2,
-        Array = 3,
-        Enumerable = 4,
-        Dictionary = 5,
-        IConfigurationSection = 6,
-        Nullable = 7,
+        Enumerable = 3,
+        Dictionary = 4,
+        IConfigurationSection = 5,
+        Nullable = 6,
     }
 }
index 690b71d..9bf3877 100644 (file)
   <data name="LanguageVersionIsNotSupportedTitle" xml:space="preserve">
     <value>Language version is required to be at least C# 11</value>
   </data>
+  <data name="MissingPublicInstanceConstructor" xml:space="preserve">
+    <value>Cannot create instance of type '{0}' because it is missing a public instance constructor.</value>
+  </data>
   <data name="MultiDimArraysNotSupported" xml:space="preserve">
     <value>Multidimensional arrays are not supported: '{0}'.</value>
   </data>
-  <data name="NeedPublicParameterlessConstructor" xml:space="preserve">
-    <value>Only objects with public parameterless constructors are supported: '{0}'.</value>
+  <data name="MultipleParameterizedConstructors" xml:space="preserve">
+    <value>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</value>
   </data>
   <data name="NullableUnderlyingTypeNotSupported" xml:space="preserve">
     <value>Nullable underlying type is not supported: '{0}'.</value>
index 0c8b59d..67cc2e5 100644 (file)
         <target state="translated">Verze jazyka musí být alespoň C# 11</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">Multidimenzionální pole se nepodporují: {0}“.</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">Podporují se pouze objekty s veřejnými konstruktory bez parametrů: „{0}“.</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index adfcd49..9c6839a 100644 (file)
         <target state="translated">Die Sprachversion muss mindestens C# 11 sein</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">Mehrdimensionale Arrays werden nicht unterstützt: "{0}".</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">Nur Objekte mit öffentlichen parameterlosen Konstruktoren werden unterstützt: "{0}".</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index a344ff8..f876dcd 100644 (file)
         <target state="translated">La versión del lenguaje debe ser al menos C# 11</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">No se admiten las matrices multidimensionales: "{0}".</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">Solo se admiten objetos con constructores públicos sin parámetros: "{0}".</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index 1ff7787..cab1b66 100644 (file)
         <target state="translated">La version du langage doit être au moins C# 11</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">Les tableaux multidimensionnels ne sont pas pris en charge : ‘{0}‘.</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">Seuls les objets avec des constructeurs sans paramètre publics sont pris en charge : ‘{0}‘.</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index 957605d..a212bd8 100644 (file)
         <target state="translated">La versione del linguaggio deve essere almeno C# 11</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">Matrici multidimensionali non supportate: '{0}'.</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">Sono supportati solo oggetti con costruttori senza parametri pubblici: '{0}'.</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index 76d8514..b6c9407 100644 (file)
         <target state="translated">言語バージョンは少なくとも C# 11 である必要があります</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">多次元配列はサポートされていません: '{0}'.</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">パラメーターなしのパブリック コンストラクターを持つオブジェクトのみがサポートされています: '{0}'。</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index bcf4ea9..0bd5f2f 100644 (file)
         <target state="translated">언어 버전은 C# 11 이상이어야 합니다.</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">다차원 배열은 지원되지 않습니다: '{0}'.</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">공용 매개 변수 없는 생성자가 있는 개체만 지원됩니다: '{0}'.</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index 70462e0..aeb93e0 100644 (file)
         <target state="translated">Wymagana jest wersja językowa co najmniej C# 11</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">Tablice wielowymiarowe nie są obsługiwane: „{0}”.</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">Obsługiwane są tylko obiekty z publicznymi konstruktorami bez parametrów: „{0}”.</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index 74a4a19..8e8254c 100644 (file)
         <target state="translated">A versão do idioma deve ser pelo menos C# 11</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">Matrizes multidimensionais não são suportadas: '{0}'.</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">Somente objetos com construtores públicos sem parâmetros são suportados: '{0}'.</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index 7ae5c07..eba8abf 100644 (file)
         <target state="translated">Версия языка должна быть не ниже C# 11</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">Многомерные массивы не поддерживаются: "{0}".</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">Поддерживаются только объекты с общедоступными конструкторами без параметров: "{0}".</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index 2d44681..ecb3ba0 100644 (file)
         <target state="translated">Dil sürümünün en az C# 11 olması gerekir</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">Çok boyutlu diziler desteklenmiyor: '{0}'.</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">Yalnızca genel parametresiz oluşturucular içeren nesneler desteklenmiyor: '{0}'.</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index 15f0afb..6ca3d43 100644 (file)
         <target state="translated">语言版本必须至少为 C# 11</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">不支持多维数组: '{0}'。</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">仅支持具有公共无参数构造函数的对象: '{0}'。</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index c487ecd..64cde87 100644 (file)
         <target state="translated">語言版本要求至少為 C#11</target>
         <note />
       </trans-unit>
+      <trans-unit id="MissingPublicInstanceConstructor">
+        <source>Cannot create instance of type '{0}' because it is missing a public instance constructor.</source>
+        <target state="new">Cannot create instance of type '{0}' because it is missing a public instance constructor.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="MultiDimArraysNotSupported">
         <source>Multidimensional arrays are not supported: '{0}'.</source>
         <target state="translated">不支援多維陣列: '{0}'。</target>
         <note />
       </trans-unit>
-      <trans-unit id="NeedPublicParameterlessConstructor">
-        <source>Only objects with public parameterless constructors are supported: '{0}'.</source>
-        <target state="translated">僅支援具有公用無參數建構函式的物件: '{0}'。</target>
+      <trans-unit id="MultipleParameterizedConstructors">
+        <source>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</source>
+        <target state="new">Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</target>
         <note />
       </trans-unit>
       <trans-unit id="NullableUnderlyingTypeNotSupported">
index 56e03b5..6e30c74 100644 (file)
@@ -5,7 +5,6 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
-using System.Text.Json.Serialization;
 using Microsoft.Extensions.Configuration;
 using Xunit;
 
@@ -114,6 +113,11 @@ namespace Microsoft.Extensions
             }
         }
 
+        public class ClassWithPrimaryCtor(string color, int length)
+        {
+            public string Color { get; } = color;
+            public int Length { get; } = length;
+        }
 
         public record RecordTypeOptions(string Color, int Length);
 
index c84d33b..ec29f21 100644 (file)
@@ -7,7 +7,9 @@ using System.ComponentModel;
 using System.Globalization;
 using System.Linq;
 using System.Reflection;
+#if BUILDING_SOURCE_GENERATOR_TESTS
 using Microsoft.Extensions.Configuration;
+#endif
 using Microsoft.Extensions.Configuration.Test;
 using Xunit;
 
@@ -19,7 +21,7 @@ namespace Microsoft.Extensions
 {
     public partial class ConfigurationBinderTests
     {
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for records.
+        [Fact]
         public void BindWithNestedTypesWithReadOnlyProperties()
         {
             IConfiguration configuration = new ConfigurationBuilder()
@@ -42,6 +44,8 @@ namespace Microsoft.Extensions
             Assert.Equal("Dummy", result.Nested.MyProp);
         }
 
+        // Add test for type with parameterless ctor + init-only properties.
+
         [Fact]
         public void EnumBindCaseInsensitiveNotThrows()
         {
@@ -901,7 +905,7 @@ namespace Microsoft.Extensions
                 exception.Message);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Ensure exception messages are in sync
+        [Fact]
         public void ExceptionWhenTryingToBindClassWithoutParameterlessConstructor()
         {
             var input = new Dictionary<string, string>
@@ -920,8 +924,8 @@ namespace Microsoft.Extensions
                 exception.Message);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
-        public void ExceptionWhenTryingToBindClassWherePropertiesDoMatchConstructorParameters()
+        [Fact]
+        public void ExceptionWhenTryingToBindClassWherePropertiesDoNotMatchConstructorParameters()
         {
             var input = new Dictionary<string, string>
             {
@@ -941,7 +945,7 @@ namespace Microsoft.Extensions
                 exception.Message);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void ExceptionWhenTryingToBindToConstructorWithMissingConfig()
         {
             var input = new Dictionary<string, string>
@@ -961,7 +965,7 @@ namespace Microsoft.Extensions
                 exception.Message);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void ExceptionWhenTryingToBindConfigToClassWhereNoMatchingParameterIsFoundInConstructor()
         {
             var input = new Dictionary<string, string>
@@ -982,7 +986,7 @@ namespace Microsoft.Extensions
                 exception.Message);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void BindsToClassConstructorParametersWithDefaultValues()
         {
             var input = new Dictionary<string, string>
@@ -1003,7 +1007,7 @@ namespace Microsoft.Extensions
             Assert.Equal(42, testOptions.ClassWhereParametersHaveDefaultValueProperty.Age);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void FieldsNotSupported_ExceptionBindingToConstructorWithParameterMatchingAField()
         {
             var input = new Dictionary<string, string>
@@ -1025,7 +1029,7 @@ namespace Microsoft.Extensions
                 exception.Message);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void BindsToRecordPrimaryConstructorParametersWithDefaultValues()
         {
             var input = new Dictionary<string, string>
@@ -1101,7 +1105,7 @@ namespace Microsoft.Extensions
             Assert.Equal("hello world", options.MyString);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindImmutableClass()
         {
             var dic = new Dictionary<string, string>
@@ -1118,7 +1122,7 @@ namespace Microsoft.Extensions
             Assert.Equal("Green", options.Color);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindMutableClassWitNestedImmutableObject()
         {
             var dic = new Dictionary<string, string>
@@ -1139,7 +1143,7 @@ namespace Microsoft.Extensions
 
         // If the immutable type has multiple public parameterized constructors, then throw
         // an exception.
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindImmutableClass_ThrowsOnMultipleParameterizedConstructors()
         {
             var dic = new Dictionary<string, string>
@@ -1153,7 +1157,7 @@ namespace Microsoft.Extensions
             configurationBuilder.AddInMemoryCollection(dic);
             var config = configurationBuilder.Build();
 
-            string expectedMessage = SR.Format(SR.Error_MultipleParameterizedConstructors, "Microsoft.Extensions.Configuration.Binder.Tests.ConfigurationBinderTests+ImmutableClassWithMultipleParameterizedConstructors");
+            string expectedMessage = SR.Format(SR.Error_MultipleParameterizedConstructors, typeof(ImmutableClassWithMultipleParameterizedConstructors));
 
             var ex = Assert.Throws<InvalidOperationException>(() => config.Get<ImmutableClassWithMultipleParameterizedConstructors>());
 
@@ -1162,7 +1166,7 @@ namespace Microsoft.Extensions
 
         // If the immutable type has a parameterized constructor, then throw
         // that constructor has an 'in' parameter
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindImmutableClass_ThrowsOnParameterizedConstructorWithAnInParameter()
         {
             var dic = new Dictionary<string, string>
@@ -1176,7 +1180,7 @@ namespace Microsoft.Extensions
             configurationBuilder.AddInMemoryCollection(dic);
             var config = configurationBuilder.Build();
 
-            string expectedMessage = SR.Format(SR.Error_CannotBindToConstructorParameter, "Microsoft.Extensions.Configuration.Binder.Tests.ConfigurationBinderTests+ImmutableClassWithOneParameterizedConstructorButWithInParameter", "string1");
+            string expectedMessage = SR.Format(SR.Error_CannotBindToConstructorParameter, typeof(ImmutableClassWithOneParameterizedConstructorButWithInParameter), "string1");
 
             var ex = Assert.Throws<InvalidOperationException>(() => config.Get<ImmutableClassWithOneParameterizedConstructorButWithInParameter>());
 
@@ -1185,7 +1189,7 @@ namespace Microsoft.Extensions
 
         // If the immutable type has a parameterized constructors, then throw
         // that constructor has a 'ref' parameter
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindImmutableClass_ThrowsOnParameterizedConstructorWithARefParameter()
         {
             var dic = new Dictionary<string, string>
@@ -1199,7 +1203,7 @@ namespace Microsoft.Extensions
             configurationBuilder.AddInMemoryCollection(dic);
             var config = configurationBuilder.Build();
 
-            string expectedMessage = SR.Format(SR.Error_CannotBindToConstructorParameter, "Microsoft.Extensions.Configuration.Binder.Tests.ConfigurationBinderTests+ImmutableClassWithOneParameterizedConstructorButWithRefParameter", "int1");
+            string expectedMessage = SR.Format(SR.Error_CannotBindToConstructorParameter, typeof(ImmutableClassWithOneParameterizedConstructorButWithRefParameter), "int1");
 
             var ex = Assert.Throws<InvalidOperationException>(() => config.Get<ImmutableClassWithOneParameterizedConstructorButWithRefParameter>());
 
@@ -1208,7 +1212,7 @@ namespace Microsoft.Extensions
 
         // If the immutable type has a parameterized constructors, then throw
         // if the constructor has an 'out' parameter
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindImmutableClass_ThrowsOnParameterizedConstructorWithAnOutParameter()
         {
             var dic = new Dictionary<string, string>
@@ -1222,14 +1226,14 @@ namespace Microsoft.Extensions
             configurationBuilder.AddInMemoryCollection(dic);
             var config = configurationBuilder.Build();
 
-            string expectedMessage = SR.Format(SR.Error_CannotBindToConstructorParameter, "Microsoft.Extensions.Configuration.Binder.Tests.ConfigurationBinderTests+ImmutableClassWithOneParameterizedConstructorButWithOutParameter", "int2");
+            string expectedMessage = SR.Format(SR.Error_CannotBindToConstructorParameter, typeof(ImmutableClassWithOneParameterizedConstructorButWithOutParameter), "int2");
 
             var ex = Assert.Throws<InvalidOperationException>(() => config.Get<ImmutableClassWithOneParameterizedConstructorButWithOutParameter>());
 
             Assert.Equal(expectedMessage, ex.Message);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindMutableStruct_UnmatchedConstructorsAreIgnored()
         {
             var dic = new Dictionary<string, string>
@@ -1248,7 +1252,7 @@ namespace Microsoft.Extensions
 
         // If the immutable type has a public parameterized constructor,
         // then pick it.
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindImmutableClass_PicksParameterizedConstructorIfNoParameterlessConstructorExists()
         {
             var dic = new Dictionary<string, string>
@@ -1269,7 +1273,7 @@ namespace Microsoft.Extensions
             Assert.Equal(2, options.Int2);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindSemiImmutableClass()
         {
             var dic = new Dictionary<string, string>
@@ -1288,7 +1292,7 @@ namespace Microsoft.Extensions
             Assert.Equal(1.23m, options.Thickness);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindSemiImmutableClass_WithInitProperties()
         {
             var dic = new Dictionary<string, string>
@@ -1307,7 +1311,7 @@ namespace Microsoft.Extensions
             Assert.Equal(1.23m, options.Thickness);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindRecordOptions()
         {
             var dic = new Dictionary<string, string>
@@ -1324,7 +1328,24 @@ namespace Microsoft.Extensions
             Assert.Equal("Green", options.Color);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
+        public void CanBindClassWithPrimaryCtor()
+        {
+            var dic = new Dictionary<string, string>
+            {
+                {"Length", "42"},
+                {"Color", "Green"},
+            };
+            var configurationBuilder = new ConfigurationBuilder();
+            configurationBuilder.AddInMemoryCollection(dic);
+            var config = configurationBuilder.Build();
+
+            var options = config.Get<ClassWithPrimaryCtor>();
+            Assert.Equal(42, options.Length);
+            Assert.Equal("Green", options.Color);
+        }
+
+        [Fact]
         public void CanBindRecordStructOptions()
         {
             var dic = new Dictionary<string, string>
@@ -1341,7 +1362,7 @@ namespace Microsoft.Extensions
             Assert.Equal("Green", options.Color);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindNestedRecordOptions()
         {
             var dic = new Dictionary<string, string>
@@ -1364,7 +1385,7 @@ namespace Microsoft.Extensions
             Assert.Equal(24, options.Nested2.ValueB);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindOnParametersAndProperties_PropertiesAreSetAfterTheConstructor()
         {
             var dic = new Dictionary<string, string>
@@ -1381,7 +1402,7 @@ namespace Microsoft.Extensions
             Assert.Equal("the color is Green", options.Color);
         }
 
-        [ConditionalFact(typeof(TestHelpers), nameof(TestHelpers.NotSourceGenMode))] // Need support for parameterized ctors.
+        [Fact]
         public void CanBindReadonlyRecordStructOptions()
         {
             var dic = new Dictionary<string, string>
@@ -1805,5 +1826,51 @@ namespace Microsoft.Extensions
             Assert.Equal(TimeOnly.Parse("18:26:38.7327436"), obj.Prop22);
 #endif
         }
+
+        [Fact]
+        public void ForClasses_ParameterlessConstructorIsPickedOverParameterized()
+        {
+            string data = """
+                {
+                    "MyInt": 9,
+                }
+                """;
+
+            var configuration = TestHelpers.GetConfigurationFromJsonString(data);
+            var obj = configuration.Get<ClassWithParameterlessAndParameterizedCtor>();
+            Assert.Equal(1, obj.MyInt);
+        }
+
+        [Fact]
+        public void ForStructs_ParameterlessConstructorIsPickedOverParameterized()
+        {
+            string data = """
+                {
+                    "MyInt": 10,
+                }
+                """;
+
+            var configuration = TestHelpers.GetConfigurationFromJsonString(data);
+            var obj = configuration.Get<ClassWithParameterlessAndParameterizedCtor>();
+            Assert.Equal(1, obj.MyInt);
+        }
+
+        public class ClassWithParameterlessAndParameterizedCtor
+        {
+            public ClassWithParameterlessAndParameterizedCtor() => MyInt = 1;
+
+            public ClassWithParameterlessAndParameterizedCtor(int myInt) => MyInt = 10;
+
+            public int MyInt { get; }
+        }
+
+        public struct StructWithParameterlessAndParameterizedCtor
+        {
+            public StructWithParameterlessAndParameterizedCtor() => MyInt = 1;
+
+            public StructWithParameterlessAndParameterizedCtor(int myInt) => MyInt = 10;
+
+            public int MyInt { get; }
+        }
     }
 }
index b18fa84..53e5174 100644 (file)
@@ -5,17 +5,17 @@ internal static class GeneratedConfigurationBinder
 {
     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, binderOptions: null);
 
-    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureActions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj, global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetBinderOptions(configureActions));
+    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj, global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetBinderOptions(configureOptions));
 
     public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration.GetSection(key), ref obj, binderOptions: null);
 }
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
-    using System;
-    using System.Globalization;
     using Microsoft.Extensions.Configuration;
+    using System;
     using System.Collections.Generic;
+    using System.Globalization;
 
     internal static class Helpers
     {
@@ -56,6 +56,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
         public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
         {
+            if (obj is null)
+            {
+                throw new ArgumentNullException(nameof(obj));
+            }
+
         }
 
         public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions)
@@ -164,14 +169,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return false;
         }
 
-        public static BinderOptions? GetBinderOptions(System.Action<BinderOptions>? configureActions)
+        public static BinderOptions? GetBinderOptions(System.Action<BinderOptions>? configureOptions)
         {
-            if (configureActions is null)
+            if (configureOptions is null)
             {
                 return null;
             }
             BinderOptions binderOptions = new();
-            configureActions(binderOptions);
+            configureOptions(binderOptions);
             if (binderOptions.BindNonPublicProperties)
             {
                 throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
index cba992e..e10a7f4 100644 (file)
@@ -3,27 +3,27 @@
 
 internal static class GeneratedConfigurationBinder
 {
-    public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, typeof(T), configureActions: null) ?? default(T));
+    public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, typeof(T), configureOptions: null) ?? default(T));
 }
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
-    using System;
-    using System.Globalization;
     using Microsoft.Extensions.Configuration;
+    using System;
     using System.Collections.Generic;
+    using System.Globalization;
     using System.Linq;
 
     internal static class Helpers
     {
-        public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureActions)
+        public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
             {
                 throw new ArgumentNullException(nameof(configuration));
             }
 
-            BinderOptions? binderOptions = GetBinderOptions(configureActions);
+            BinderOptions? binderOptions = GetBinderOptions(configureOptions);
 
             if (!HasValueOrChildren(configuration))
             {
@@ -156,14 +156,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         }
                     }
                     break;
-                    case "ICustomDictionary":
-                    {
-                    }
-                    break;
-                    case "ICustomCollection":
-                    {
-                    }
-                    break;
                     case "IReadOnlyList":
                     {
                         if (HasChildren(section))
@@ -175,10 +167,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         }
                     }
                     break;
-                    case "UnsupportedIReadOnlyDictionaryUnsupported":
-                    {
-                    }
-                    break;
                     case "IReadOnlyDictionary":
                     {
                         if (HasChildren(section))
index fb0621d..e0bfd54 100644 (file)
@@ -29,10 +29,10 @@ internal static class GeneratedConfigurationBinder
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
-    using System;
-    using System.Globalization;
     using Microsoft.Extensions.Configuration;
+    using System;
     using System.Collections.Generic;
+    using System.Globalization;
 
     internal static class Helpers
     {
index 882ff2d..53be830 100644 (file)
@@ -3,32 +3,32 @@
 
 internal static class GeneratedConfigurationBinder
 {
-    public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, typeof(T), configureActions: null) ?? default(T));
+    public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, typeof(T), configureOptions: null) ?? default(T));
 
-    public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureActions) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, typeof(T), configureActions) ?? default(T));
+    public static T? Get<T>(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, typeof(T), configureOptions) ?? default(T));
 
-    public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, type, configureActions: null);
+    public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, type, configureOptions: null);
 
-    public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureActions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, type, configureActions);
+    public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, type, configureOptions);
 }
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
-    using System;
-    using System.Globalization;
     using Microsoft.Extensions.Configuration;
+    using System;
     using System.Collections.Generic;
+    using System.Globalization;
 
     internal static class Helpers
     {
-        public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureActions)
+        public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
             {
                 throw new ArgumentNullException(nameof(configuration));
             }
 
-            BinderOptions? binderOptions = GetBinderOptions(configureActions);
+            BinderOptions? binderOptions = GetBinderOptions(configureOptions);
 
             if (!HasValueOrChildren(configuration))
             {
@@ -234,14 +234,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return false;
         }
 
-        public static BinderOptions? GetBinderOptions(System.Action<BinderOptions>? configureActions)
+        public static BinderOptions? GetBinderOptions(System.Action<BinderOptions>? configureOptions)
         {
-            if (configureActions is null)
+            if (configureOptions is null)
             {
                 return null;
             }
             BinderOptions binderOptions = new();
-            configureActions(binderOptions);
+            configureOptions(binderOptions);
             if (binderOptions.BindNonPublicProperties)
             {
                 throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.");
index cbf8e6f..ed3c062 100644 (file)
@@ -14,9 +14,10 @@ internal static class GeneratedConfigurationBinder
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
+    using Microsoft.Extensions.Configuration;
     using System;
+    using System.Collections.Generic;
     using System.Globalization;
-    using Microsoft.Extensions.Configuration;
 
     internal static class Helpers
     {
index 0f0a493..7972cab 100644 (file)
@@ -8,9 +8,10 @@ internal static class GeneratedConfigurationBinder
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
+    using Microsoft.Extensions.Configuration;
     using System;
+    using System.Collections.Generic;
     using System.Globalization;
-    using Microsoft.Extensions.Configuration;
 
     internal static class Helpers
     {