diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index 75d252218f839..3c9a9afb9c419 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -270,3 +270,5 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL | Suppression ID | Suppressed Diagnostic ID | Description | | :----------------------- | :----------------------- | :---------- | | __`SYSLIBSUPPRESS0001`__ | CA1822 | Do not offer to make methods static when the methods need to be instance methods for a custom marshaller shape. | +| __`SYSLIBSUPPRESS0002`__ | IL2026 | ConfigurationBindingGenerator: suppress RequiresUnreferencedCode diagnostic for binding call that has been intercepted by a generated static variant. | +| __`SYSLIBSUPPRESS0003`__ | IL3050 | ConfigurationBindingGenerator: suppress RequiresDynamicCode diagnostic for binding call that has been intercepted by a generated static variant. | diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml index 74f6be96543a5..46dd7457d764a 100644 --- a/eng/SourceBuildPrebuiltBaseline.xml +++ b/eng/SourceBuildPrebuiltBaseline.xml @@ -10,6 +10,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs index a40cf2976b31f..756e10bc26b8d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Immutable; using System.Diagnostics; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; @@ -17,7 +18,6 @@ private sealed partial class Emitter private readonly SourceGenerationSpec _sourceGenSpec; private bool _emitBlankLineBeforeNextStatement; - private bool _useFullyQualifiedNames; private int _valueSuffixIndex; private static readonly Regex s_arrayBracketsRegex = new(Regex.Escape("[]")); @@ -32,7 +32,7 @@ public Emitter(SourceProductionContext context, SourceGenerationSpec sourceGenSp public void Emit() { - if (!ShouldEmitBinders()) + if (!ShouldEmitBindingExtensions()) { return; } @@ -42,17 +42,26 @@ public void Emit() #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. """); - _writer.WriteLine(); - _useFullyQualifiedNames = true; - EmitBinder_Extensions_IConfiguration(); - EmitBinder_Extensions_OptionsBuilder(); - EmitBinder_Extensions_IServiceCollection(); + EmitInterceptsLocationAttrDecl(); + + EmitStartBlock($"namespace {ProjectName}"); + EmitUsingStatements(); + + _writer.WriteLine(); + EmitStartBlock($$""" + {{Expression.GeneratedCodeAnnotation}} + file static class {{Identifier.BindingExtensions}} + """); + EmitBindingExtensions_IConfiguration(); + EmitBindingExtensions_OptionsBuilder(); + EmitBindingExtensions_IServiceCollection(); + EmitCoreBindingHelpers(); + EmitEndBlock(); // BindingExtensions class - _useFullyQualifiedNames = false; - Emit_CoreBindingHelper(); + EmitEndBlock(); // Binding namespace. - _context.AddSource($"{Identifier.GeneratedConfigurationBinder}.g.cs", _writer.ToSourceText()); + _context.AddSource($"{Identifier.BindingExtensions}.g.cs", _writer.ToSourceText()); } private void EmitBindCoreCall( @@ -74,7 +83,7 @@ private void EmitBindCoreCall( if (initKind is InitializationKind.AssignmentWithNullCheck) { Debug.Assert(!type.IsValueType); - _writer.WriteLine($"{type.MinimalDisplayString}? {tempIdentifier} = {memberAccessExpr};"); + _writer.WriteLine($"{type.DisplayString}? {tempIdentifier} = {memberAccessExpr};"); EmitBindCoreCall(tempIdentifier, InitializationKind.AssignmentWithNullCheck); } else if (initKind is InitializationKind.None && type.IsValueType) @@ -89,9 +98,7 @@ private void EmitBindCoreCall( void EmitBindCoreCall(string objExpression, InitializationKind initKind) { - string methodDisplayString = GetHelperMethodDisplayString(nameof(MethodsToGen_CoreBindingHelper.BindCore)); - string bindCoreCall = $@"{methodDisplayString}({configArgExpr}, ref {objExpression}, {Identifier.binderOptions});"; - + string bindCoreCall = $@"{nameof(MethodsToGen_CoreBindingHelper.BindCore)}({configArgExpr}, ref {objExpression}, {Identifier.binderOptions});"; EmitObjectInit(objExpression, initKind); _writer.WriteLine(bindCoreCall); writeOnSuccess?.Invoke(objExpression); @@ -127,12 +134,11 @@ private void EmitBindLogicFromString( } else if (typeKind is StringParsableTypeKind.Enum) { - parsedValueExpr = $"ParseEnum<{type.MinimalDisplayString}>({stringValueToParse_Expr}, () => {sectionPathExpr})"; + parsedValueExpr = $"ParseEnum<{type.DisplayString}>({stringValueToParse_Expr}, () => {sectionPathExpr})"; } else { - string helperMethodDisplayString = GetHelperMethodDisplayString(type.ParseMethodName); - parsedValueExpr = $"{helperMethodDisplayString}({stringValueToParse_Expr}, () => {sectionPathExpr})"; + parsedValueExpr = $"{type.ParseMethodName}({stringValueToParse_Expr}, () => {sectionPathExpr})"; } if (!checkForNullSectionValue) @@ -156,7 +162,7 @@ private bool EmitObjectInit(TypeSpec type, string memberAccessExpr, Initializati string initExpr; CollectionSpec? collectionType = type as CollectionSpec; - string effectiveDisplayString = GetTypeDisplayString(type); + string effectiveDisplayString = type.DisplayString; if (collectionType is not null) { if (collectionType is EnumerableSpec { InitializationStrategy: InitializationStrategy.Array }) @@ -165,7 +171,7 @@ private bool EmitObjectInit(TypeSpec type, string memberAccessExpr, Initializati } else { - effectiveDisplayString = GetTypeDisplayString(collectionType.ConcreteType ?? collectionType); + effectiveDisplayString = (collectionType.ConcreteType ?? collectionType).DisplayString; initExpr = $"new {effectiveDisplayString}()"; } } @@ -215,36 +221,41 @@ private bool EmitObjectInit(TypeSpec type, string memberAccessExpr, Initializati return true; } - private void EmitCastToIConfigurationSection() + private void EmitInterceptsLocationAttrDecl() { - string sectionTypeDisplayString; - string exceptionTypeDisplayString; - if (_useFullyQualifiedNames) - { - sectionTypeDisplayString = "global::Microsoft.Extensions.Configuration.IConfigurationSection"; - exceptionTypeDisplayString = FullyQualifiedDisplayString.InvalidOperationException; - } - else - { - sectionTypeDisplayString = Identifier.IConfigurationSection; - exceptionTypeDisplayString = nameof(InvalidOperationException); - } - + _writer.WriteLine(); _writer.WriteLine($$""" - if ({{Identifier.configuration}} is not {{sectionTypeDisplayString}} {{Identifier.section}}) + namespace System.Runtime.CompilerServices { - throw new {{exceptionTypeDisplayString}}(); + using System; + using System.CodeDom.Compiler; + + {{Expression.GeneratedCodeAnnotation}} + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } """); + _writer.WriteLine(); + } + + private void EmitUsingStatements() + { + foreach (string @namespace in _sourceGenSpec.Namespaces.ToImmutableSortedSet()) + { + _writer.WriteLine($"using {@namespace};"); + } } private void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn) { string returnPostfix = voidReturn ? string.Empty : " null"; - string methodDisplayString = GetHelperMethodDisplayString(Identifier.HasValueOrChildren); - _writer.WriteLine($$""" - if (!{{methodDisplayString}}({{Identifier.configuration}})) + if (!{{Identifier.HasValueOrChildren}}({{Identifier.configuration}})) { return{{returnPostfix}}; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs index b0f53ef074078..64db4eb58b1f7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -1,12 +1,14 @@ // 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 System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Operations; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -147,7 +149,7 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || { // List is used in generated code as a temp holder for formatting // an error for config properties that don't map to object properties. - _sourceGenSpec.TypeNamespaces.Add("System.Collections.Generic"); + _sourceGenSpec.Namespaces.Add("System.Collections.Generic"); spec = CreateObjectSpec(namedType); } @@ -169,32 +171,54 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || string @namespace = spec.Namespace; if (@namespace is not null and not "") { - _sourceGenSpec.TypeNamespaces.Add(@namespace); + _sourceGenSpec.Namespaces.Add(@namespace); } return _createdSpecs[type] = spec; } - private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type) + private void RegisterTypeForBindCoreMainGen(TypeSpec typeSpec) { - if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(method, out HashSet? types)) + if (typeSpec.NeedsMemberBinding) { - _sourceGenSpec.TypesForGen_CoreBindingHelper_Methods[method] = types = new HashSet(); + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreMain, typeSpec); + RegisterTypeForBindCoreGen(typeSpec); + _sourceGenSpec.MethodsToGen_CoreBindingHelper |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren; } - - types.Add(type); - _sourceGenSpec.MethodsToGen_CoreBindingHelper |= method; } - private void RegisterTypeForBindCoreUntypedGen(TypeSpec typeSpec) + private void RegisterTypeForBindCoreGen(TypeSpec typeSpec) { if (typeSpec.NeedsMemberBinding) { RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, typeSpec); - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreUntyped, typeSpec); } } + private void RegisterTypeForGetCoreGen(TypeSpec typeSpec) + { + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, typeSpec); + _sourceGenSpec.MethodsToGen_CoreBindingHelper |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren; + } + + private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type) + { + if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(method, out HashSet? types)) + { + _sourceGenSpec.TypesForGen_CoreBindingHelper_Methods[method] = types = new HashSet(); + } + + types.Add(type); + _sourceGenSpec.MethodsToGen_CoreBindingHelper |= method; + } + + /// + /// Registers interceptors for root binding methods, except for ConfigurationBinder.Bind, + /// which is handled by + /// + private void RegisterAsInterceptor(Enum method, IInvocationOperation operation) => + _sourceGenSpec.InterceptionInfo.RegisterCacheEntry(method, new InterceptorLocationInfo(operation)); + private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType) { if (type is INamedTypeSymbol { IsGenericType: true } genericType && @@ -335,7 +359,7 @@ private bool TryGetTypeSpec(ITypeSymbol type, DiagnosticDescriptor descriptor, o // We want a BindCore method for List 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)!; - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, listSpec); + RegisterTypeForBindCoreGen(listSpec); EnumerableSpec spec = new EnumerableSpec(arrayType) { @@ -347,7 +371,7 @@ private bool TryGetTypeSpec(ITypeSymbol type, DiagnosticDescriptor descriptor, o }; Debug.Assert(spec.CanInitialize); - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, spec); + RegisterTypeForBindCoreGen(spec); return spec; } @@ -383,7 +407,7 @@ private bool IsSupportedArrayType(ITypeSymbol type) if (spec is not null) { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, spec); + RegisterTypeForBindCoreGen(spec); spec.InitExceptionMessage ??= spec.ElementType.InitExceptionMessage; } @@ -442,7 +466,7 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k constructionStrategy = InitializationStrategy.ToEnumerableMethod; populationStrategy = CollectionPopulationStrategy.Cast_Then_Add; toEnumerableMethodCall = "ToDictionary(pair => pair.Key, pair => pair.Value)"; - _sourceGenSpec.TypeNamespaces.Add("System.Linq"); + _sourceGenSpec.Namespaces.Add("System.Linq"); } else { @@ -711,7 +735,7 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k if (objectSpec.NeedsMemberBinding) { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, objectSpec); + RegisterTypeForBindCoreGen(objectSpec); } return objectSpec; @@ -890,4 +914,19 @@ private void RegisterTypeDiagnostic(ITypeSymbol causingType, InvocationDiagnosti } } } + + public static class ParserExtensions + { + public static void RegisterCacheEntry(this Dictionary cache, TKey key, TEntry entry) + where TKey : notnull + where TValue : ICollection, new() + { + if (!cache.TryGetValue(key, out TValue? entryCollection)) + { + cache[key] = entryCollection = new TValue(); + } + + entryCollection.Add(entry); + } + } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Suppressor.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Suppressor.cs new file mode 100644 index 0000000000000..13158753c3f07 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Suppressor.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + public sealed partial class ConfigurationBindingGenerator + { + /// + /// Supresses false-positive diagnostics emitted by the linker + /// when analyzing binding invocations that we have intercepted. + /// Workaround for https://github.com/dotnet/roslyn/issues/68669. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class Suppressor : DiagnosticSuppressor + { + private const string Justification = "The target method has been intercepted by a generated static variant."; + + /// + /// Suppression descriptor for IL2026: Members attributed with RequiresUnreferencedCode may break when trimming. + /// + private static readonly SuppressionDescriptor RUCDiagnostic = new(id: "SYSLIBSUPPRESS0002", suppressedDiagnosticId: "IL2026", Justification); + + /// + /// Suppression descriptor for IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as native AOT. + /// + private static readonly SuppressionDescriptor RDCDiagnostic = new(id: "SYSLIBSUPPRESS0003", suppressedDiagnosticId: "IL3050", Justification); + + public override ImmutableArray SupportedSuppressions => ImmutableArray.Create(RUCDiagnostic, RDCDiagnostic); + + public override void ReportSuppressions(SuppressionAnalysisContext context) + { + foreach (Diagnostic diagnostic in context.ReportedDiagnostics) + { + string diagnosticId = diagnostic.Id; + + if (diagnosticId != RDCDiagnostic.SuppressedDiagnosticId && diagnosticId != RUCDiagnostic.SuppressedDiagnosticId) + { + continue; + } + + Location location = diagnostic.AdditionalLocations.Count > 0 + ? diagnostic.AdditionalLocations[0] + : diagnostic.Location; + + bool shouldSuppressDiagnostic = + location.SourceTree is SyntaxTree sourceTree && + sourceTree.GetRoot().FindNode(location.SourceSpan) is SyntaxNode syntaxNode && + BinderInvocation.IsCandidateSyntaxNode(syntaxNode) && + context.GetSemanticModel(sourceTree) + .GetOperation((InvocationExpressionSyntax)syntaxNode, context.CancellationToken) is IInvocationOperation operation && + BinderInvocation.IsBindingOperation(operation); + + if (shouldSuppressDiagnostic) + { + SuppressionDescriptor targetSuppression = diagnosticId == RUCDiagnostic.SuppressedDiagnosticId + ? RUCDiagnostic + : RDCDiagnostic; + context.ReportSuppression(Suppression.Create(targetSuppression, diagnostic)); + } + } + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs index 70da582dddf0c..fbca2dd3cfc50 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs @@ -5,7 +5,6 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -15,7 +14,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration [Generator] public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerator { - internal const string ProjectName = "Microsoft.Extensions.Configuration.Binder.SourceGeneration"; + private static readonly string ProjectName = Emitter.s_assemblyName.Name; public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -42,9 +41,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterSourceOutput(inputData, (spc, source) => Execute(source.Item1, source.Item2, spc)); } - /// - /// Generates source code to optimize binding with ConfigurationBinder. - /// private static void Execute(CompilationData compilationData, ImmutableArray inputCalls, SourceProductionContext context) { if (inputCalls.IsDefaultOrEmpty) @@ -73,7 +69,11 @@ private sealed record CompilationData public CompilationData(CSharpCompilation compilation) { - LanguageVersionIsSupported = compilation.LanguageVersion >= LanguageVersion.CSharp11; + // We don't have a CSharp21 value available yet. Polyfill the value here for forward compat, rather than use the LangugeVersion.Preview enum value. + // https://github.com/dotnet/roslyn/blob/168689931cb4e3150641ec2fb188a64ce4b3b790/src/Compilers/CSharp/Portable/LanguageVersion.cs#L218-L232 + const int LangVersion_CSharp12 = 1200; + LanguageVersionIsSupported = (int)compilation.LanguageVersion >= LangVersion_CSharp12; + if (LanguageVersionIsSupported) { TypeSymbols = new KnownTypeSymbols(compilation); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs index c10e607df75d0..64064887c7c70 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Microsoft.CodeAnalysis; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -14,91 +12,84 @@ private sealed partial class Emitter { private bool ShouldEmitMethods(MethodsToGen_ConfigurationBinder methods) => (_sourceGenSpec.MethodsToGen_ConfigurationBinder & methods) != 0; - private void EmitBinder_Extensions_IConfiguration() + private void EmitBindingExtensions_IConfiguration() { - Debug.Assert(_sourceGenSpec.TypesForGen_ConfigurationBinder_BindMethods.Count <= 3 && - !_sourceGenSpec.TypesForGen_ConfigurationBinder_BindMethods.Keys.Any(overload => (overload & MethodsToGen_ConfigurationBinder.Bind) is 0)); - if (!ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Any)) { return; } - _emitBlankLineBeforeNextStatement = false; - EmitRootBindingClassStartBlock(Identifier.GeneratedConfigurationBinder); - + EmitBindingExtStartRegion(Identifier.IConfiguration); EmitGetMethods(); EmitGetValueMethods(); EmitBindMethods_ConfigurationBinder(); - - EmitEndBlock(); - _emitBlankLineBeforeNextStatement = true; + EmitBindingExtEndRegion(); } private void EmitGetMethods() { - const string expressionForGetCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.GetCore)}"; + const string expressionForGetCore = nameof(MethodsToGen_CoreBindingHelper.GetCore); const string documentation = "Attempts to bind the configuration instance to a new instance of type T."; if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_T)) { - StartMethodDefinition(documentation); - _writer.WriteLine($"public static T? {Identifier.Get}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}) => " + + StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_T, documentation); + _writer.WriteLine($"public static T? {Identifier.Get}(this {Identifier.IConfiguration} {Identifier.configuration}) => " + $"(T?)({expressionForGetCore}({Identifier.configuration}, typeof(T), {Identifier.configureOptions}: null) ?? default(T));"); } if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_T_BinderOptions)) { - StartMethodDefinition(documentation); - _writer.WriteLine($"public static T? {Identifier.Get}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}) => " + + StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_T_BinderOptions, documentation); + _writer.WriteLine($"public static T? {Identifier.Get}(this {Identifier.IConfiguration} {Identifier.configuration}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}) => " + $"(T?)({expressionForGetCore}({Identifier.configuration}, typeof(T), {Identifier.configureOptions}) ?? default(T));"); } if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_TypeOf)) { - StartMethodDefinition(documentation); - _writer.WriteLine($"public static object? {Identifier.Get}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayString.Type} {Identifier.type}) => " + + StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_TypeOf, documentation); + _writer.WriteLine($"public static object? {Identifier.Get}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}) => " + $"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureOptions}: null);"); } if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_TypeOf_BinderOptions)) { - StartMethodDefinition(documentation); - _writer.WriteLine($"public static object? {Identifier.Get}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayString.Type} {Identifier.type}, {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}) => " + + StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_TypeOf_BinderOptions, documentation); + _writer.WriteLine($"public static object? {Identifier.Get}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}) => " + $"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureOptions});"); } } private void EmitGetValueMethods() { - const string expressionForGetValueCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.GetValueCore)}"; + const string expressionForGetValueCore = $"{Identifier.BindingExtensions}.{nameof(MethodsToGen_CoreBindingHelper.GetValueCore)}"; const string documentation = "Extracts the value with the specified key and converts it to the specified type."; if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_T_key)) { - StartMethodDefinition(documentation); - _writer.WriteLine($"public static T? {Identifier.GetValue}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, string {Identifier.key}) => " + + StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_T_key, documentation); + _writer.WriteLine($"public static T? {Identifier.GetValue}(this {Identifier.IConfiguration} {Identifier.configuration}, string {Identifier.key}) => " + $"(T?)({expressionForGetValueCore}({Identifier.configuration}, typeof(T), {Identifier.key}) ?? default(T));"); } if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_T_key_defaultValue)) { - StartMethodDefinition(documentation); - _writer.WriteLine($"public static T? {Identifier.GetValue}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, string {Identifier.key}, T {Identifier.defaultValue}) => " + + StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_T_key_defaultValue, documentation); + _writer.WriteLine($"public static T? {Identifier.GetValue}(this {Identifier.IConfiguration} {Identifier.configuration}, string {Identifier.key}, T {Identifier.defaultValue}) => " + $"(T?)({expressionForGetValueCore}({Identifier.configuration}, typeof(T), {Identifier.key}) ?? {Identifier.defaultValue});"); } if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key)) { - StartMethodDefinition(documentation); - _writer.WriteLine($"public static object? {Identifier.GetValue}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayString.Type} {Identifier.type}, string {Identifier.key}) => " + + StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key, documentation); + _writer.WriteLine($"public static object? {Identifier.GetValue}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key}) => " + $"{expressionForGetValueCore}({Identifier.configuration}, {Identifier.type}, {Identifier.key});"); } if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key_defaultValue)) { - StartMethodDefinition(documentation); - _writer.WriteLine($"public static object? {Identifier.GetValue}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayString.Type} {Identifier.type}, string {Identifier.key}, object? {Identifier.defaultValue}) => " + + StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key_defaultValue, documentation); + _writer.WriteLine($"public static object? {Identifier.GetValue}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key}, object? {Identifier.defaultValue}) => " + $"{expressionForGetValueCore}({Identifier.configuration}, {Identifier.type}, {Identifier.key}) ?? {Identifier.defaultValue};"); } } @@ -110,72 +101,71 @@ private void EmitBindMethods_ConfigurationBinder() return; } - Dictionary> types = _sourceGenSpec.TypesForGen_ConfigurationBinder_BindMethods; + string objParamExpr = $"object? {Identifier.obj}"; - if (types.TryGetValue(MethodsToGen_ConfigurationBinder.Bind_instance, out HashSet? typeSpecs)) + if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance)) { - foreach (TypeSpec type in typeSpecs) - { - EmitMethodImplementation( - type, - additionalParams: GetObjParameter(type), - configExpression: Identifier.configuration, - configureOptions: false); - } + EmitMethods( + MethodsToGen_ConfigurationBinder.Bind_instance, + additionalParams: objParamExpr, + configExpression: Identifier.configuration, + configureOptions: false); } - if (types.TryGetValue(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions, out typeSpecs)) + if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions)) { - foreach (TypeSpec type in typeSpecs) - { - EmitMethodImplementation( - type, - additionalParams: $"{GetObjParameter(type)}, {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}", - configExpression: Identifier.configuration, - configureOptions: true); - } + EmitMethods( + MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions, + additionalParams: $"{objParamExpr}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}", + configExpression: Identifier.configuration, + configureOptions: true); } - if (types.TryGetValue(MethodsToGen_ConfigurationBinder.Bind_key_instance, out typeSpecs)) + if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_key_instance)) { - foreach (TypeSpec type in typeSpecs) - { - EmitMethodImplementation( - type, - additionalParams: $"string {Identifier.key}, {GetObjParameter(type)}", - configExpression: $"{Expression.configurationGetSection}({Identifier.key})", - configureOptions: false); - } + EmitMethods( + MethodsToGen_ConfigurationBinder.Bind_key_instance, + additionalParams: $"string {Identifier.key}, {objParamExpr}", + configExpression: $"{Expression.configurationGetSection}({Identifier.key})", + configureOptions: false); } - void EmitMethodImplementation(TypeSpec type, string additionalParams, string configExpression, bool configureOptions) + void EmitMethods(MethodsToGen_ConfigurationBinder method, string additionalParams, string configExpression, bool configureOptions) { - string binderOptionsArg = configureOptions ? $"{Expression.GetBinderOptions}({Identifier.configureOptions})" : $"{Identifier.binderOptions}: null"; - - string returnExpression; - if (type.CanInitialize) + foreach (KeyValuePair> pair in _sourceGenSpec.InterceptionInfo_ConfigBinder.GetOverloadInfo(method)) { - returnExpression = type.NeedsMemberBinding - ? $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.BindCore)}({configExpression}, ref {Identifier.obj}, {binderOptionsArg})" - : "{ }"; + (TypeSpec type, List interceptorInfoList) = (pair.Key, pair.Value); + + EmitBlankLineIfRequired(); + _writer.WriteLine($"/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively."); + EmitInterceptsLocationAnnotations(interceptorInfoList); + EmitStartBlock($"public static void {Identifier.Bind}_{type.DisplayString.ToIdentifierSubstring()}(this {Identifier.IConfiguration} {Identifier.configuration}, {additionalParams})"); + + if (!EmitInitException(type) && type.NeedsMemberBinding) + { + string binderOptionsArg = configureOptions ? $"{Identifier.GetBinderOptions}({Identifier.configureOptions})" : $"{Identifier.binderOptions}: null"; + + EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); + if (!type.IsValueType) + { + EmitCheckForNullArgument_WithBlankLine(Identifier.obj); + } + _writer.WriteLine($$""" + var {{Identifier.typedObj}} = ({{type.EffectiveType.DisplayString}}){{Identifier.obj}}; + {{nameof(MethodsToGen_CoreBindingHelper.BindCore)}}({{configExpression}}, ref {{Identifier.typedObj}}, {{binderOptionsArg}}); + """); + } + + EmitEndBlock(); } - else - { - returnExpression = GetInitException(type.InitExceptionMessage); - } - - StartMethodDefinition("Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively."); - _writer.WriteLine($"public static void {Identifier.Bind}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {additionalParams}) => " - + $"{returnExpression};"); } - - string GetObjParameter(TypeSpec type) => $"{type.FullyQualifiedDisplayString} {Identifier.obj}"; } - private void StartMethodDefinition(string documentation) + private void StartMethodDefinition(MethodsToGen_ConfigurationBinder method, string documentation) { EmitBlankLineIfRequired(); _writer.WriteLine($"/// {documentation}"); + EmitInterceptsLocationAnnotations(method); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelpers.cs similarity index 83% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs rename to src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelpers.cs index a7b42b1329804..f30408fad596d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelpers.cs @@ -3,10 +3,10 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -16,41 +16,18 @@ private sealed partial class Emitter { private bool ShouldEmitMethods(MethodsToGen_CoreBindingHelper methods) => (_sourceGenSpec.MethodsToGen_CoreBindingHelper & methods) != 0; - private void Emit_CoreBindingHelper() + private void EmitCoreBindingHelpers() { Debug.Assert(_emitBlankLineBeforeNextStatement); - _writer.WriteLine(); - _emitBlankLineBeforeNextStatement = false; - - EmitStartBlock($"namespace {ProjectName}"); - EmitHelperUsingStatements(); - - _writer.WriteLine(); - - EmitStartBlock($$""" - /// Provide core binding logic. - {{GetGeneratedCodeAttributeSrc()}} - file static class {{Identifier.CoreBindingHelper}} - """); - + EmitBindingExtStartRegion("Core binding"); EmitConfigurationKeyCaches(); EmitGetCoreMethod(); EmitGetValueCoreMethod(); - EmitBindCoreUntypedMethod(); + EmitBindCoreMainMethod(); EmitBindCoreMethods(); EmitInitializeMethods(); EmitHelperMethods(); - - EmitEndBlock(); // End helper class. - EmitEndBlock(); // End namespace. - } - - private void EmitHelperUsingStatements() - { - foreach (string @namespace in _sourceGenSpec.TypeNamespaces.ToImmutableSortedSet()) - { - _writer.WriteLine($"using {@namespace};"); - } + EmitBindingExtEndRegion(); } private void EmitConfigurationKeyCaches() @@ -60,6 +37,8 @@ private void EmitConfigurationKeyCaches() return; } + EmitBlankLineIfRequired(); + foreach (TypeSpec type in targetTypes) { if (type is not ObjectSpec objectType) @@ -73,10 +52,8 @@ private void EmitConfigurationKeyCaches() string configKeysSource = string.Join(", ", keys); string fieldName = GetConfigKeyCacheFieldName(objectType); - _writer.WriteLine($@"private readonly static Lazy<{MinimalDisplayString.HashSetOfString}> {fieldName} = new(() => new {MinimalDisplayString.HashSetOfString}(StringComparer.OrdinalIgnoreCase) {{ {configKeysSource} }});"); + _writer.WriteLine($@"private readonly static Lazy<{TypeDisplayString.HashSetOfString}> {fieldName} = new(() => new {TypeDisplayString.HashSetOfString}(StringComparer.OrdinalIgnoreCase) {{ {configKeysSource} }});"); } - - _emitBlankLineBeforeNextStatement = true; } private void EmitGetCoreMethod() @@ -96,16 +73,24 @@ private void EmitGetCoreMethod() EmitIConfigurationHasValueOrChildrenCheck(voidReturn: false); + bool isFirstType = true; foreach (TypeSpec type in types) { TypeSpec effectiveType = type.EffectiveType; TypeSpecKind kind = effectiveType.SpecKind; + string conditionKindExpr = GetConditionKindExpr(ref isFirstType); - EmitStartBlock($"if (type == typeof({type.MinimalDisplayString}))"); + EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))"); if (effectiveType is ParsableFromStringSpec stringParsableType) { - EmitCastToIConfigurationSection(); + _writer.WriteLine($$""" + if ({{Identifier.configuration}} is not {{Identifier.IConfigurationSection}} {{Identifier.section}}) + { + throw new {{Identifier.InvalidOperationException}}(); + } + """); + EmitBindLogicFromString( stringParsableType, Expression.sectionValue, @@ -121,9 +106,9 @@ private void EmitGetCoreMethod() } EmitEndBlock(); - _writer.WriteLine(); } + _writer.WriteLine(); Emit_NotSupportedException_TypeNotDetectedAsInput(); EmitEndBlock(); _emitBlankLineBeforeNextStatement = true; @@ -152,9 +137,11 @@ private void EmitGetValueCoreMethod() _writer.WriteLine(); + bool isFirstType = true; foreach (TypeSpec type in targetTypes) { - EmitStartBlock($"if ({Identifier.type} == typeof({type.MinimalDisplayString}))"); + string conditionKindExpr = GetConditionKindExpr(ref isFirstType); + EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))"); EmitBindLogicFromString( (ParsableFromStringSpec)type.EffectiveType, @@ -165,51 +152,48 @@ private void EmitGetValueCoreMethod() useIncrementalStringValueIdentifier: false); EmitEndBlock(); - _writer.WriteLine(); } + _writer.WriteLine(); _writer.WriteLine("return null;"); EmitEndBlock(); _emitBlankLineBeforeNextStatement = true; } - private void EmitBindCoreUntypedMethod() + private void EmitBindCoreMainMethod() { - if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.BindCoreUntyped, out HashSet? targetTypes)) + if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.BindCoreMain, out HashSet? targetTypes)) { return; } EmitBlankLineIfRequired(); - - EmitStartBlock($"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped)}(this {Identifier.IConfiguration} {Identifier.configuration}, object {Identifier.obj}, Type {Identifier.type}, {MinimalDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions})"); - + EmitStartBlock($"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCoreMain)}({Identifier.IConfiguration} {Identifier.configuration}, object {Identifier.obj}, Type {Identifier.type}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions})"); EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); - + EmitCheckForNullArgument_WithBlankLine(Identifier.obj); + EmitIConfigurationHasValueOrChildrenCheck(voidReturn: true); _writer.WriteLine($"{Identifier.BinderOptions}? {Identifier.binderOptions} = {Identifier.GetBinderOptions}({Identifier.configureOptions});"); _writer.WriteLine(); - EmitIConfigurationHasValueOrChildrenCheck(voidReturn: true); - + bool isFirstType = true; foreach (TypeSpec type in targetTypes) { - EmitStartBlock($"if (type == typeof({type.MinimalDisplayString}))"); - TypeSpec effectiveType = type.EffectiveType; + string conditionKindExpr = GetConditionKindExpr(ref isFirstType); + + EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))"); if (!EmitInitException(effectiveType)) { - _writer.WriteLine($"var {Identifier.temp} = ({effectiveType.MinimalDisplayString}){Identifier.obj};"); + _writer.WriteLine($"var {Identifier.temp} = ({effectiveType.DisplayString}){Identifier.obj};"); EmitBindCoreCall(type, Identifier.temp, Identifier.configuration, InitializationKind.None); _writer.WriteLine($"return;"); } - EmitEndBlock(); - _writer.WriteLine(); } + _writer.WriteLine(); Emit_NotSupportedException_TypeNotDetectedAsInput(); EmitEndBlock(); - _emitBlankLineBeforeNextStatement = true; } private void EmitBindCoreMethods() @@ -231,11 +215,9 @@ private void EmitBindCoreMethod(TypeSpec type) { Debug.Assert(type.CanInitialize); - string objParameterExpression = $"ref {type.MinimalDisplayString} {Identifier.obj}"; + string objParameterExpression = $"ref {type.DisplayString} {Identifier.obj}"; EmitStartBlock(@$"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCore)}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); - EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType); - TypeSpec effectiveType = type.EffectiveType; if (effectiveType is EnumerableSpec enumerable) { @@ -281,9 +263,9 @@ private void EmitInitializeMethod(ObjectSpec type) List ctorParams = type.ConstructorParameters; IEnumerable initOnlyProps = type.Properties.Values.Where(prop => prop is { SetOnInit: true }); List ctorArgList = new(); - string displayString = type.MinimalDisplayString; + string displayString = type.DisplayString; - EmitStartBlock($"public static {type.MinimalDisplayString} {GetInitalizeMethodDisplayString(type)}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); + EmitStartBlock($"public static {type.DisplayString} {GetInitalizeMethodDisplayString(type)}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); _emitBlankLineBeforeNextStatement = false; foreach (ParameterSpec parameter in ctorParams) @@ -335,7 +317,7 @@ void EmitBindImplForMember(MemberSpec member) TypeSpec memberType = member.Type; bool errorOnFailedBinding = member.ErrorOnFailedBinding; - string parsedMemberIdentifierDeclarationPrefix = $"{memberType.MinimalDisplayString} {member.Name}"; + string parsedMemberIdentifierDeclarationPrefix = $"{memberType.DisplayString} {member.Name}"; string parsedMemberIdentifier; if (memberType is ParsableFromStringSpec { StringParsableTypeKind: StringParsableTypeKind.AssignFromSectionValue }) @@ -344,7 +326,7 @@ void EmitBindImplForMember(MemberSpec member) if (errorOnFailedBinding) { - string condition = $@" if ({Identifier.configuration}[""{member.ConfigurationKeyName}""] is not {memberType.MinimalDisplayString} {member.Name})"; + string condition = $@" if ({Identifier.configuration}[""{member.ConfigurationKeyName}""] is not {memberType.DisplayString} {member.Name})"; EmitThrowBlock(condition); _writer.WriteLine(); return; @@ -393,40 +375,42 @@ void EmitThrowBlock(string condition) => _writer.WriteLine($$""" {{condition}} { - throw new {{GetInvalidOperationDisplayName()}}("{{string.Format(ExceptionMessages.ParameterHasNoMatchingConfig, type.Name, member.Name)}}"); + throw new {{Identifier.InvalidOperationException}}("{{string.Format(ExceptionMessages.ParameterHasNoMatchingConfig, type.Name, member.Name)}}"); } """); } } + private void EmitHelperMethods() { + // Emitted if we are to bind objects with complex members, or if we're emitting BindCoreMain or GetCore methods. + bool emitAsConfigWithChildren = ShouldEmitMethods(MethodsToGen_CoreBindingHelper.AsConfigWithChildren); + if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCore)) { + EmitBlankLineIfRequired(); EmitValidateConfigurationKeysMethod(); } - if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreUntyped | MethodsToGen_CoreBindingHelper.GetCore)) + if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreMain | MethodsToGen_CoreBindingHelper.GetCore)) { - _writer.WriteLine(); + // HasValueOrChildren references this method. + Debug.Assert(emitAsConfigWithChildren); + EmitBlankLineIfRequired(); EmitHasValueOrChildrenMethod(); - _writer.WriteLine(); - EmitAsConfigWithChildrenMethod(); - _emitBlankLineBeforeNextStatement = true; } - else if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.AsConfigWithChildren)) + + if (emitAsConfigWithChildren) { - _writer.WriteLine(); + EmitBlankLineIfRequired(); EmitAsConfigWithChildrenMethod(); - _emitBlankLineBeforeNextStatement = true; } - if (ShouldEmitMethods( - MethodsToGen_CoreBindingHelper.BindCoreUntyped | MethodsToGen_CoreBindingHelper.GetCore) || + if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreMain | MethodsToGen_CoreBindingHelper.GetCore) || ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions)) { - _writer.WriteLine(); + EmitBlankLineIfRequired(); EmitGetBinderOptionsHelper(); - _emitBlankLineBeforeNextStatement = true; } bool enumTypeExists = false; @@ -458,17 +442,17 @@ private void EmitValidateConfigurationKeysMethod() EmitBlankLineIfRequired(); _writer.WriteLine($$""" /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void {{Identifier.ValidateConfigurationKeys}}(Type {{Identifier.type}}, {{MinimalDisplayString.LazyHashSetOfString}} {{keysIdentifier}}, {{Identifier.IConfiguration}} {{Identifier.configuration}}, {{Identifier.BinderOptions}}? {{Identifier.binderOptions}}) + public static void {{Identifier.ValidateConfigurationKeys}}(Type {{Identifier.type}}, {{TypeDisplayString.LazyHashSetOfString}} {{keysIdentifier}}, {{Identifier.IConfiguration}} {{Identifier.configuration}}, {{Identifier.BinderOptions}}? {{Identifier.binderOptions}}) { if ({{Identifier.binderOptions}}?.{{Identifier.ErrorOnUnknownConfiguration}} is true) { - {{MinimalDisplayString.ListOfString}}? {{Identifier.temp}} = null; + {{TypeDisplayString.ListOfString}}? {{Identifier.temp}} = null; foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}()) { if (!{{keysIdentifier}}.Value.Contains({{Expression.sectionKey}})) { - ({{Identifier.temp}} ??= new {{MinimalDisplayString.ListOfString}}()).Add($"'{{{Expression.sectionKey}}}'"); + ({{Identifier.temp}} ??= new {{TypeDisplayString.ListOfString}}()).Add($"'{{{Expression.sectionKey}}}'"); } } @@ -490,7 +474,7 @@ private void EmitHasValueOrChildrenMethod() { return true; } - return {{Identifier.AsConfigWithChildren}}({{Identifier.configuration}}) is not null; + return {{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}}({{Identifier.configuration}}) is not null; } """); } @@ -498,7 +482,7 @@ private void EmitHasValueOrChildrenMethod() private void EmitAsConfigWithChildrenMethod() { _writer.WriteLine($$""" - public static {{Identifier.IConfiguration}}? {{Identifier.AsConfigWithChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}}) + public static {{Identifier.IConfiguration}}? {{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}}) { foreach ({{Identifier.IConfigurationSection}} _ in {{Identifier.configuration}}.{{Identifier.GetChildren}}()) { @@ -512,7 +496,7 @@ private void EmitAsConfigWithChildrenMethod() private void EmitGetBinderOptionsHelper() { _writer.WriteLine($$""" - public static {{Identifier.BinderOptions}}? {{Identifier.GetBinderOptions}}({{MinimalDisplayString.NullableActionOfBinderOptions}} {{Identifier.configureOptions}}) + public static {{Identifier.BinderOptions}}? {{Identifier.GetBinderOptions}}({{TypeDisplayString.NullableActionOfBinderOptions}} {{Identifier.configureOptions}}) { if ({{Identifier.configureOptions}} is null) { @@ -524,7 +508,7 @@ private void EmitGetBinderOptionsHelper() if ({{Identifier.binderOptions}}.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"{{string.Format(ExceptionMessages.CannotSpecifyBindNonPublicProperties)}}"); + throw new NotSupportedException($"{{string.Format(ExceptionMessages.CannotSpecifyBindNonPublicProperties)}}"); } return {{Identifier.binderOptions}}; @@ -534,7 +518,6 @@ private void EmitGetBinderOptionsHelper() private void EmitEnumParseMethod() { - string innerExceptionTypeDisplayString = _useFullyQualifiedNames ? "global::System.Exception" : "Exception"; string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.getPath}()}}", $"{{typeof(T)}}"); _writer.WriteLine($$""" @@ -548,9 +531,9 @@ public static T ParseEnum(string value, Func getPath) where T : stru return Enum.Parse(value, ignoreCase: true); #endif } - catch ({{innerExceptionTypeDisplayString}} {{Identifier.exception}}) + catch ({{Identifier.Exception}} {{Identifier.exception}}) { - throw new {{GetInvalidOperationDisplayName()}}($"{{exceptionArg1}}", {{Identifier.exception}}); + throw new {{Identifier.InvalidOperationException}}($"{{exceptionArg1}}", {{Identifier.exception}}); } } """); @@ -558,29 +541,12 @@ public static T ParseEnum(string value, Func getPath) where T : stru private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) { - string innerExceptionTypeDisplayString; - string cultureInfoTypeDisplayString; - string numberStylesTypeDisplayString; - - if (_useFullyQualifiedNames) - { - innerExceptionTypeDisplayString = "global::System.Exception"; - cultureInfoTypeDisplayString = "global::System.Globalization.CultureInfo"; - numberStylesTypeDisplayString = "global::System.Globalization.NumberStyles"; - } - else - { - innerExceptionTypeDisplayString = "Exception"; - cultureInfoTypeDisplayString = "CultureInfo"; - numberStylesTypeDisplayString = "NumberStyles"; - } - StringParsableTypeKind typeKind = type.StringParsableTypeKind; - string typeDisplayString = type.MinimalDisplayString; - - string invariantCultureExpression = $"{cultureInfoTypeDisplayString}.InvariantCulture"; + string typeDisplayString = type.DisplayString; + string invariantCultureExpression = $"{Identifier.CultureInfo}.InvariantCulture"; string parsedValueExpr; + switch (typeKind) { case StringParsableTypeKind.Enum: @@ -592,12 +558,12 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) break; case StringParsableTypeKind.Integer: { - parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {numberStylesTypeDisplayString}.Integer, {invariantCultureExpression})"; + parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {Identifier.NumberStyles}.Integer, {invariantCultureExpression})"; } break; case StringParsableTypeKind.Float: { - parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {numberStylesTypeDisplayString}.Float, {invariantCultureExpression})"; + parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {Identifier.NumberStyles}.Float, {invariantCultureExpression})"; } break; case StringParsableTypeKind.Parse: @@ -612,7 +578,7 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) break; case StringParsableTypeKind.CultureInfo: { - parsedValueExpr = $"{cultureInfoTypeDisplayString}.GetCultureInfo({Identifier.value})"; + parsedValueExpr = $"{Identifier.CultureInfo}.GetCultureInfo({Identifier.value})"; } break; case StringParsableTypeKind.Uri: @@ -635,9 +601,9 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) { return {{parsedValueExpr}}; } - catch ({{innerExceptionTypeDisplayString}} {{Identifier.exception}}) + catch ({{Identifier.Exception}} {{Identifier.exception}}) { - throw new {{GetInvalidOperationDisplayName()}}($"{{exceptionArg1}}", {{Identifier.exception}}); + throw new {{Identifier.InvalidOperationException}}($"{{exceptionArg1}}", {{Identifier.exception}}); } """); } @@ -722,13 +688,13 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) if (keyType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) { // Save value to local to avoid parsing twice - during look-up and during add. - _writer.WriteLine($"{keyType.MinimalDisplayString} {Identifier.key} = {parsedKeyExpr};"); + _writer.WriteLine($"{keyType.DisplayString} {Identifier.key} = {parsedKeyExpr};"); parsedKeyExpr = Identifier.key; } bool isValueType = elementType.IsValueType; string expressionForElementIsNotNull = $"{Identifier.element} is not null"; - string elementTypeDisplayString = elementType.MinimalDisplayString + (elementType.IsValueType ? string.Empty : "?"); + string elementTypeDisplayString = elementType.DisplayString + (elementType.IsValueType ? string.Empty : "?"); string expressionForElementExists = $"{objIdentifier}.{Identifier.TryGetValue}({parsedKeyExpr}, out {elementTypeDisplayString} {Identifier.element})"; string conditionToUseExistingElement = expressionForElementExists; @@ -749,7 +715,7 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) // we need to copy its contents into a new instance & then append/bind to that. string initExpression = collectionSpec.InitializationStrategy is InitializationStrategy.ParameterizedConstructor - ? $"new {collectionSpec.ConcreteType.MinimalDisplayString}({Identifier.element})" + ? $"new {collectionSpec.ConcreteType.DisplayString}({Identifier.element})" : $"{Identifier.element}.{collectionSpec.ToEnumerableMethodCall!}"; _writer.WriteLine($$""" @@ -773,7 +739,7 @@ private void EmitBindCoreImplForObject(ObjectSpec type) Debug.Assert(type.NeedsMemberBinding); string keyCacheFieldName = GetConfigKeyCacheFieldName(type); - string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.MinimalDisplayString}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});"; + string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.DisplayString}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});"; _writer.WriteLine(validateMethodCallExpr); foreach (PropertySpec property in type.Properties.Values) @@ -781,7 +747,7 @@ private void EmitBindCoreImplForObject(ObjectSpec type) bool noSetter_And_IsReadonly = !property.CanSet && property.Type is CollectionSpec { InitializationStrategy: InitializationStrategy.ParameterizedConstructor }; if (property.ShouldBind() && !noSetter_And_IsReadonly) { - string containingTypeRef = property.IsStatic ? type.MinimalDisplayString : Identifier.obj; + string containingTypeRef = property.IsStatic ? type.DisplayString : Identifier.obj; EmitBindImplForMember( property, memberAccessExpr: $"{containingTypeRef}.{property.Name}", @@ -832,7 +798,7 @@ private bool EmitBindImplForMember( return true; } - string sectionValidationCall = $"{Identifier.AsConfigWithChildren}({sectionParseExpr})"; + string sectionValidationCall = $"{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}({sectionParseExpr})"; string sectionIdentifier = GetIncrementalIdentifier(Identifier.section); EmitStartBlock($"if ({sectionValidationCall} is {Identifier.IConfigurationSection} {sectionIdentifier})"); @@ -869,14 +835,14 @@ private void EmitBindCoreCallForMember( } Debug.Assert(canSet); - string effectiveMemberTypeDisplayString = effectiveMemberType.MinimalDisplayString; + string effectiveMemberTypeDisplayString = effectiveMemberType.DisplayString; initKind = InitializationKind.None; if (memberType.SpecKind is TypeSpecKind.Nullable) { string nullableTempIdentifier = GetIncrementalIdentifier(Identifier.temp); - _writer.WriteLine($"{memberType.MinimalDisplayString} {nullableTempIdentifier} = {memberAccessExpr};"); + _writer.WriteLine($"{memberType.DisplayString} {nullableTempIdentifier} = {memberAccessExpr};"); _writer.WriteLine( $"{effectiveMemberTypeDisplayString} {tempIdentifier} = {nullableTempIdentifier}.{Identifier.HasValue} ? {nullableTempIdentifier}.{Identifier.Value} : new {effectiveMemberTypeDisplayString}();"); @@ -924,7 +890,7 @@ private void EmitCollectionCastIfRequired(CollectionSpec type, out string objIde { objIdentifier = Identifier.temp; _writer.WriteLine($$""" - if ({{Identifier.obj}} is not {{type.PopulationCastType!.MinimalDisplayString}} {{objIdentifier}}) + if ({{Identifier.obj}} is not {{type.PopulationCastType!.DisplayString}} {{objIdentifier}}) { return; } @@ -936,20 +902,31 @@ private void EmitCollectionCastIfRequired(CollectionSpec type, out string objIde private void Emit_Foreach_Section_In_ConfigChildren_StartBlock() => EmitStartBlock($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())"); + private void Emit_NotSupportedException_TypeNotDetectedAsInput() => + _writer.WriteLine(@$"throw new NotSupportedException($""{string.Format(ExceptionMessages.TypeNotDetectedAsInput, "{type}")}"");"); + private static string GetSectionPathFromConfigurationExpression(string configurationKeyName) => $@"{GetSectionFromConfigurationExpression(configurationKeyName)}.{Identifier.Path}"; private static string GetSectionFromConfigurationExpression(string configurationKeyName, bool addQuotes = true) { string argExpr = addQuotes ? $@"""{configurationKeyName}""" : configurationKeyName; - return $@"{Expression.configurationGetSection}({argExpr})"; + return $@"{Identifier.configuration}.{Identifier.GetSection}({argExpr})"; } - private static string GetConfigKeyCacheFieldName(ObjectSpec type) => - $"s_configKeys_{type.DisplayStringWithoutSpecialCharacters}"; + private static string GetConditionKindExpr(ref bool isFirstType) + { + if (isFirstType) + { + isFirstType = false; + return "if"; + } - private void Emit_NotSupportedException_TypeNotDetectedAsInput() => - _writer.WriteLine(@$"throw new global::System.NotSupportedException($""{string.Format(ExceptionMessages.TypeNotDetectedAsInput, "{type}")}"");"); + return "else if"; + } + + private static string GetConfigKeyCacheFieldName(ObjectSpec type) => + $"s_configKeys_{type.DisplayString.ToIdentifierSubstring()}"; } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs index e0e6a36aabaa7..bad56b7ce3275 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs @@ -1,6 +1,8 @@ // 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 System.Diagnostics; using System.Reflection; @@ -10,7 +12,9 @@ public sealed partial class ConfigurationBindingGenerator { private sealed partial class Emitter { - private static readonly AssemblyName s_assemblyName = typeof(Emitter).Assembly.GetName(); + private string? _emittedExtsTargetType; + + internal static readonly AssemblyName s_assemblyName = typeof(ConfigurationBindingGenerator).Assembly.GetName(); private enum InitializationKind { @@ -26,29 +30,13 @@ private static class Expression public const string sectionPath = "section.Path"; public const string sectionValue = "section.Value"; - public const string GetBinderOptions = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{Identifier.GetBinderOptions}"; - } - - private static class FullyQualifiedDisplayString - { - public const string ActionOfBinderOptions = $"global::System.Action"; - public const string AddSingleton = $"{ServiceCollectionServiceExtensions}.AddSingleton"; - public const string ConfigurationChangeTokenSource = "global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource"; - public const string CoreBindingHelper = $"global::{ProjectName}.{Identifier.CoreBindingHelper}"; - public const string IConfiguration = "global::Microsoft.Extensions.Configuration.IConfiguration"; - public const string IConfigurationSection = IConfiguration + "Section"; - public const string IOptionsChangeTokenSource = "global::Microsoft.Extensions.Options.IOptionsChangeTokenSource"; - public const string InvalidOperationException = "global::System.InvalidOperationException"; - public const string IServiceCollection = "global::Microsoft.Extensions.DependencyInjection.IServiceCollection"; - public const string NotSupportedException = "global::System.NotSupportedException"; - public const string OptionsBuilderOfTOptions = $"global::Microsoft.Extensions.Options.OptionsBuilder<{Identifier.TOptions}>"; - public const string ServiceCollectionServiceExtensions = "global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions"; - public const string Type = $"global::System.Type"; + public static string GeneratedCodeAnnotation = $@"[GeneratedCode(""{s_assemblyName.Name}"", ""{s_assemblyName.Version}"")]"; } - private static class MinimalDisplayString + private static class TypeDisplayString { public const string NullableActionOfBinderOptions = "Action?"; + public const string OptionsBuilderOfTOptions = $"OptionsBuilder<{Identifier.TOptions}>"; public const string HashSetOfString = "HashSet"; public const string LazyHashSetOfString = "Lazy>"; public const string ListOfString = "List"; @@ -75,6 +63,7 @@ private static class Identifier public const string services = nameof(services); public const string temp = nameof(temp); public const string type = nameof(type); + public const string typedObj = nameof(typedObj); public const string validateKeys = nameof(validateKeys); public const string value = nameof(value); @@ -82,21 +71,19 @@ private static class Identifier public const string AddSingleton = nameof(AddSingleton); public const string Any = nameof(Any); public const string Array = nameof(Array); - public const string AsConfigWithChildren = nameof(AsConfigWithChildren); public const string Bind = nameof(Bind); public const string BinderOptions = nameof(BinderOptions); + public const string BindingExtensions = nameof(BindingExtensions); + public const string ConfigurationChangeTokenSource = nameof(ConfigurationChangeTokenSource); public const string Configure = nameof(Configure); public const string CopyTo = nameof(CopyTo); public const string ContainsKey = nameof(ContainsKey); - public const string CoreBindingHelper = nameof(CoreBindingHelper); public const string Count = nameof(Count); public const string CultureInfo = nameof(CultureInfo); public const string CultureNotFoundException = nameof(CultureNotFoundException); public const string Enum = nameof(Enum); public const string ErrorOnUnknownConfiguration = nameof(ErrorOnUnknownConfiguration); - public const string GeneratedConfigurationBinder = nameof(GeneratedConfigurationBinder); - public const string GeneratedOptionsBuilderBinder = nameof(GeneratedOptionsBuilderBinder); - public const string GeneratedServiceCollectionBinder = nameof(GeneratedServiceCollectionBinder); + public const string Exception = nameof(Exception); public const string Get = nameof(Get); public const string GetBinderOptions = nameof(GetBinderOptions); public const string GetChildren = nameof(GetChildren); @@ -108,9 +95,13 @@ private static class Identifier public const string IConfiguration = nameof(IConfiguration); public const string IConfigurationSection = nameof(IConfigurationSection); public const string Int32 = "int"; + public const string InterceptsLocation = nameof(InterceptsLocation); public const string InvalidOperationException = nameof(InvalidOperationException); public const string InvariantCulture = nameof(InvariantCulture); + public const string IOptionsChangeTokenSource = nameof(IOptionsChangeTokenSource); + public const string IServiceCollection = nameof(IServiceCollection); public const string Length = nameof(Length); + public const string NumberStyles = nameof(NumberStyles); public const string Parse = nameof(Parse); public const string Path = nameof(Path); public const string Resize = nameof(Resize); @@ -118,17 +109,66 @@ private static class Identifier public const string TOptions = nameof(TOptions); public const string TryCreate = nameof(TryCreate); public const string TryGetValue = nameof(TryGetValue); - public const string TryParse = nameof(TryParse); + public const string Type = nameof(Type); public const string Uri = nameof(Uri); public const string ValidateConfigurationKeys = nameof(ValidateConfigurationKeys); public const string Value = nameof(Value); } - private bool ShouldEmitBinders() => + private bool ShouldEmitBindingExtensions() => ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Any) || ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Any) || ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Any); + private void EmitInterceptsLocationAnnotations(Enum generatedBindingOverload) + { + // The only time a generated binding method won't have any locations to + // intercept is when either of these methods are used as helpers for + // other generated OptionsBuilder or ServiceCollection binding extensions. + bool interceptsCalls = _sourceGenSpec.InterceptionInfo.TryGetValue(generatedBindingOverload, out List? infoList); + Debug.Assert(interceptsCalls || + generatedBindingOverload is MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions || + generatedBindingOverload is MethodsToGen_Extensions_OptionsBuilder.Bind_T_BinderOptions); + + if (interceptsCalls) + { + EmitInterceptsLocationAnnotations(infoList); + } + } + + private void EmitInterceptsLocationAnnotations(List infoList) + { + foreach (InterceptorLocationInfo info in infoList) + { + _writer.WriteLine($@"[{Identifier.InterceptsLocation}Attribute(@""{info.FilePath}"", {info.LineNumber}, {info.CharacterNumber})]"); + } + } + + private void EmitBindingExtStartRegion(string targetType) + { + Debug.Assert(_emittedExtsTargetType is null); + + EmitBlankLineIfRequired(); + _emittedExtsTargetType = targetType; + EmitBindingExtRegionText(isStart: true); + _emitBlankLineBeforeNextStatement = false; + } + + private void EmitBindingExtEndRegion() + { + Debug.Assert(_emittedExtsTargetType is not null); + + EmitBindingExtRegionText(isStart: false); + _emittedExtsTargetType = null; + _emitBlankLineBeforeNextStatement = true; + } + + private void EmitBindingExtRegionText(bool isStart) + { + string endSource = isStart ? string.Empty : "end"; + _writer.WriteLine($"#{endSource}region {_emittedExtsTargetType} extensions."); + } + /// /// Starts a block of source code. /// @@ -171,24 +211,12 @@ private void EmitBlankLineIfRequired() _emitBlankLineBeforeNextStatement = true; } - private void EmitCheckForNullArgument_WithBlankLine_IfRequired(bool isValueType) - { - if (!isValueType) - { - EmitCheckForNullArgument_WithBlankLine(Identifier.obj); - } - } - private void EmitCheckForNullArgument_WithBlankLine(string paramName) { - string exceptionTypeDisplayString = _useFullyQualifiedNames - ? "global::System.ArgumentNullException" - : "ArgumentNullException"; - _writer.WriteLine($$""" if ({{paramName}} is null) { - throw new {{exceptionTypeDisplayString}}(nameof({{paramName}})); + throw new ArgumentNullException(nameof({{paramName}})); } """); @@ -201,51 +229,28 @@ private bool EmitInitException(TypeSpec type) if (!type.CanInitialize) { - _writer.WriteLine(GetInitException(type.InitExceptionMessage) + ";"); + _writer.WriteLine($@"throw new {Identifier.InvalidOperationException}(""{type.InitExceptionMessage}"");"); return true; } return false; } - private void EmitRootBindingClassStartBlock(string className) - { - EmitBlankLineIfRequired(); - EmitStartBlock($$""" - /// Generated helper providing an AOT and linking compatible implementation for configuration binding. - {{GetGeneratedCodeAttributeSrc()}} - internal static class {{className}} - """); - - _emitBlankLineBeforeNextStatement = false; - } - - private string GetGeneratedCodeAttributeSrc() - { - string attributeRefExpr = _useFullyQualifiedNames ? $"global::System.CodeDom.Compiler.GeneratedCodeAttribute" : "GeneratedCode"; - return $@"[{attributeRefExpr}(""{s_assemblyName.Name}"", ""{s_assemblyName.Version}"")]"; - } - - private string GetInitException(string message) => $@"throw new {GetInvalidOperationDisplayName()}(""{message}"")"; - private string GetIncrementalIdentifier(string prefix) => $"{prefix}{_valueSuffixIndex++}"; - private string GetInitalizeMethodDisplayString(ObjectSpec type) => - GetHelperMethodDisplayString($"{nameof(MethodsToGen_CoreBindingHelper.Initialize)}{type.DisplayStringWithoutSpecialCharacters}"); - - private string GetTypeDisplayString(TypeSpec type) => _useFullyQualifiedNames ? type.FullyQualifiedDisplayString : type.MinimalDisplayString; - - private string GetHelperMethodDisplayString(string methodName) - { - if (_useFullyQualifiedNames) - { - methodName = FullyQualifiedDisplayString.CoreBindingHelper + "." + methodName; - } - - return methodName; - } - - private string GetInvalidOperationDisplayName() => _useFullyQualifiedNames ? FullyQualifiedDisplayString.InvalidOperationException : Identifier.InvalidOperationException; + private static string GetInitalizeMethodDisplayString(ObjectSpec type) => + $"{nameof(MethodsToGen_CoreBindingHelper.Initialize)}{type.DisplayString.ToIdentifierSubstring()}"; } } + + internal static class EmitterExtensions + { + public static string ToIdentifierSubstring(this string typeDisplayName) => + typeDisplayName + .Replace("[]", nameof(Array)) + .Replace(", ", string.Empty) + .Replace(".", string.Empty) + .Replace("<", string.Empty) + .Replace(">", string.Empty); + } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsBuilderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsBuilderConfigurationExtensions.cs index 71d0b6989dd97..d49198196fd49 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsBuilderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsBuilderConfigurationExtensions.cs @@ -9,19 +9,17 @@ private sealed partial class Emitter { private bool ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder methods) => (_sourceGenSpec.MethodsToGen_OptionsBuilderExt & methods) != 0; - private void EmitBinder_Extensions_OptionsBuilder() + private void EmitBindingExtensions_OptionsBuilder() { if (!ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Any)) { return; } - EmitRootBindingClassStartBlock(Identifier.GeneratedOptionsBuilderBinder); - + EmitBindingExtStartRegion(TypeDisplayString.OptionsBuilderOfTOptions); EmitBindMethods_Extensions_OptionsBuilder(); EmitBindConfigurationMethod(); - - EmitEndBlock(); + EmitBindingExtEndRegion(); } private void EmitBindMethods_Extensions_OptionsBuilder() @@ -32,24 +30,25 @@ private void EmitBindMethods_Extensions_OptionsBuilder() } const string documentation = @"/// Registers a configuration instance which will bind against."; - const string paramList = $"{FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}"; + const string paramList = $"{Identifier.IConfiguration} {Identifier.configuration}"; if (ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Bind_T)) { - EmitMethodStartBlock("Bind", paramList, documentation); - _writer.WriteLine($"return global::{Identifier.GeneratedOptionsBuilderBinder}.Bind({Identifier.optionsBuilder}, {Identifier.configuration}, {Identifier.configureOptions}: null);"); + EmitMethodStartBlock(MethodsToGen_Extensions_OptionsBuilder.Bind_T, "Bind", paramList, documentation); + _writer.WriteLine($"return Bind({Identifier.optionsBuilder}, {Identifier.configuration}, {Identifier.configureOptions}: null);"); EmitEndBlock(); } EmitMethodStartBlock( + MethodsToGen_Extensions_OptionsBuilder.Bind_T_BinderOptions, "Bind", - paramList + $", {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}", + paramList + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}", documentation); EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder); _writer.WriteLine($$""" - global::{{Identifier.GeneratedServiceCollectionBinder}}.{{Identifier.Configure}}<{{Identifier.TOptions}}>({{Identifier.optionsBuilder}}.{{Identifier.Services}}, {{Identifier.optionsBuilder}}.Name, {{Identifier.configuration}}, {{Identifier.configureOptions}}); + {{Identifier.Configure}}<{{Identifier.TOptions}}>({{Identifier.optionsBuilder}}.{{Identifier.Services}}, {{Identifier.optionsBuilder}}.Name, {{Identifier.configuration}}, {{Identifier.configureOptions}}); return {{Identifier.optionsBuilder}}; """); @@ -63,19 +62,20 @@ private void EmitBindConfigurationMethod() return; } - const string documentation = $@"/// Registers the dependency injection container to bind against the obtained from the DI service provider."; - string paramList = $"string {Identifier.configSectionPath}, {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions} = null"; + const string documentation = $@"/// Registers the dependency injection container to bind against the obtained from the DI service provider."; + string paramList = $"string {Identifier.configSectionPath}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions} = null"; - EmitMethodStartBlock("BindConfiguration", paramList, documentation); + EmitMethodStartBlock(MethodsToGen_Extensions_OptionsBuilder.BindConfiguration, "BindConfiguration", paramList, documentation); EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder); EmitCheckForNullArgument_WithBlankLine(Identifier.configSectionPath); - EmitStartBlock($"{Identifier.optionsBuilder}.{Identifier.Configure}<{FullyQualifiedDisplayString.IConfiguration}>(({Identifier.obj}, {Identifier.configuration}) =>"); - + EmitStartBlock($"{Identifier.optionsBuilder}.{Identifier.Configure}<{Identifier.IConfiguration}>(({Identifier.obj}, {Identifier.configuration}) =>"); + EmitCheckForNullArgument_WithBlankLine(Identifier.obj); + EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); _writer.WriteLine($$""" - {{FullyQualifiedDisplayString.IConfiguration}} {{Identifier.section}} = string.Equals(string.Empty, {{Identifier.configSectionPath}}, global::System.StringComparison.OrdinalIgnoreCase) ? {{Identifier.configuration}} : {{Identifier.configuration}}.{{Identifier.GetSection}}({{Identifier.configSectionPath}}); - {{FullyQualifiedDisplayString.CoreBindingHelper}}.{{nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped)}}({{Identifier.section}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}), {{Identifier.configureOptions}}); + {{Identifier.IConfiguration}} {{Identifier.section}} = string.Equals(string.Empty, {{Identifier.configSectionPath}}, StringComparison.OrdinalIgnoreCase) ? {{Identifier.configuration}} : {{Identifier.configuration}}.{{Identifier.GetSection}}({{Identifier.configSectionPath}}); + {{nameof(MethodsToGen_CoreBindingHelper.BindCoreMain)}}({{Identifier.section}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}), {{Identifier.configureOptions}}); """); EmitEndBlock(endBraceTrailingSource: ");"); @@ -83,20 +83,20 @@ private void EmitBindConfigurationMethod() _writer.WriteLine(); _writer.WriteLine($$""" - {{FullyQualifiedDisplayString.AddSingleton}}<{{FullyQualifiedDisplayString.IOptionsChangeTokenSource}}<{{Identifier.TOptions}}>, {{FullyQualifiedDisplayString.ConfigurationChangeTokenSource}}<{{Identifier.TOptions}}>>({{Identifier.optionsBuilder}}.{{Identifier.Services}}); + {{Identifier.optionsBuilder}}.{{Identifier.Services}}.{{Identifier.AddSingleton}}<{{Identifier.IOptionsChangeTokenSource}}<{{Identifier.TOptions}}>, {{Identifier.ConfigurationChangeTokenSource}}<{{Identifier.TOptions}}>>(); return {{Identifier.optionsBuilder}}; """); EmitEndBlock(); } - private void EmitMethodStartBlock(string methodName, string paramList, string documentation) + private void EmitMethodStartBlock(MethodsToGen_Extensions_OptionsBuilder method, string methodName, string paramList, string documentation) { - paramList = $"this {FullyQualifiedDisplayString.OptionsBuilderOfTOptions} {Identifier.optionsBuilder}, {paramList}"; - + paramList = $"this {TypeDisplayString.OptionsBuilderOfTOptions} {Identifier.optionsBuilder}, {paramList}"; EmitBlankLineIfRequired(); _writer.WriteLine(documentation); - EmitStartBlock($"public static {FullyQualifiedDisplayString.OptionsBuilderOfTOptions} {methodName}<{Identifier.TOptions}>({paramList}) where {Identifier.TOptions} : class"); + EmitInterceptsLocationAnnotations(method); + EmitStartBlock($"public static {TypeDisplayString.OptionsBuilderOfTOptions} {methodName}<{Identifier.TOptions}>({paramList}) where {Identifier.TOptions} : class"); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsConfigurationServiceCollectionExtensions.cs index f4cd4800df123..0348eb5047e97 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsConfigurationServiceCollectionExtensions.cs @@ -9,71 +9,72 @@ private sealed partial class Emitter { private bool ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection methods) => (_sourceGenSpec.MethodsToGen_ServiceCollectionExt & methods) != 0; - private void EmitBinder_Extensions_IServiceCollection() + private void EmitBindingExtensions_IServiceCollection() { if (!ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Any)) { return; } - EmitRootBindingClassStartBlock(Identifier.GeneratedServiceCollectionBinder); + EmitBindingExtStartRegion(Identifier.IServiceCollection); + EmitConfigureMethods(); + EmitBindingExtEndRegion(); + } + private void EmitConfigureMethods() + { const string defaultNameExpr = "string.Empty"; - const string configureMethodString = $"global::{Identifier.GeneratedServiceCollectionBinder}.{Identifier.Configure}"; - string configParam = $"{FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}"; + string configParam = $"{Identifier.IConfiguration} {Identifier.configuration}"; if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T)) { - EmitStartMethod(configParam); - _writer.WriteLine($"return {configureMethodString}<{Identifier.TOptions}>({Identifier.services}, {defaultNameExpr}, {Identifier.configuration}, {Identifier.configureOptions}: null);"); + EmitStartMethod(MethodsToGen_Extensions_ServiceCollection.Configure_T, configParam); + _writer.WriteLine($"return {Identifier.Configure}<{Identifier.TOptions}>({Identifier.services}, {defaultNameExpr}, {Identifier.configuration}, {Identifier.configureOptions}: null);"); EmitEndBlock(); } if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T_name)) { EmitStartMethod( + MethodsToGen_Extensions_ServiceCollection.Configure_T_name, paramList: $"string? {Identifier.name}, " + configParam); - _writer.WriteLine($"return {configureMethodString}<{Identifier.TOptions}>({Identifier.services}, {Identifier.name}, {Identifier.configuration}, {Identifier.configureOptions}: null);"); + _writer.WriteLine($"return {Identifier.Configure}<{Identifier.TOptions}>({Identifier.services}, {Identifier.name}, {Identifier.configuration}, {Identifier.configureOptions}: null);"); EmitEndBlock(); } if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T_BinderOptions)) { EmitStartMethod( - paramList: configParam + $", {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}"); - _writer.WriteLine($"return {configureMethodString}<{Identifier.TOptions}>({Identifier.services}, {defaultNameExpr}, {Identifier.configuration}, {Identifier.configureOptions});"); + MethodsToGen_Extensions_ServiceCollection.Configure_T_BinderOptions, + paramList: configParam + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}"); + _writer.WriteLine($"return {Identifier.Configure}<{Identifier.TOptions}>({Identifier.services}, {defaultNameExpr}, {Identifier.configuration}, {Identifier.configureOptions});"); EmitEndBlock(); } // Core Configure method that the other overloads call. // Like the others, it is public API that could be called directly by users. // So, it is always generated whenever a Configure overload is called. - string optionsNamespaceName = "global::Microsoft.Extensions.Options"; - string bindCoreUntypedDisplayString = GetHelperMethodDisplayString(nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped)); + string optionsNamespaceName = "Microsoft.Extensions.Options"; - EmitStartMethod(paramList: $"string? {Identifier.name}, " + configParam + $", {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}"); + EmitStartMethod(MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions, paramList: $"string? {Identifier.name}, " + configParam + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}"); EmitCheckForNullArgument_WithBlankLine(Identifier.services); EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); _writer.WriteLine($$""" - global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions({{Identifier.services}}); - {{FullyQualifiedDisplayString.AddSingleton}}<{{FullyQualifiedDisplayString.IOptionsChangeTokenSource}}<{{Identifier.TOptions}}>>({{Identifier.services}}, new {{FullyQualifiedDisplayString.ConfigurationChangeTokenSource}}<{{Identifier.TOptions}}>({{Identifier.name}}, {{Identifier.configuration}})); - return {{FullyQualifiedDisplayString.AddSingleton}}<{{optionsNamespaceName}}.IConfigureOptions<{{Identifier.TOptions}}>>({{Identifier.services}}, new {{optionsNamespaceName}}.ConfigureNamedOptions<{{Identifier.TOptions}}>({{Identifier.name}}, {{Identifier.obj}} => {{bindCoreUntypedDisplayString}}({{Identifier.configuration}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}), {{Identifier.configureOptions}}))); + OptionsServiceCollectionExtensions.AddOptions({{Identifier.services}}); + {{Identifier.services}}.{{Identifier.AddSingleton}}<{{Identifier.IOptionsChangeTokenSource}}<{{Identifier.TOptions}}>>(new {{Identifier.ConfigurationChangeTokenSource}}<{{Identifier.TOptions}}>({{Identifier.name}}, {{Identifier.configuration}})); + return {{Identifier.services}}.{{Identifier.AddSingleton}}<{{optionsNamespaceName}}.IConfigureOptions<{{Identifier.TOptions}}>>(new {{optionsNamespaceName}}.ConfigureNamedOptions<{{Identifier.TOptions}}>({{Identifier.name}}, {{Identifier.obj}} => {{nameof(MethodsToGen_CoreBindingHelper.BindCoreMain)}}({{Identifier.configuration}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}){{Identifier.configureOptions}}))); """); EmitEndBlock(); - - EmitEndBlock(); - _emitBlankLineBeforeNextStatement = true; } - private void EmitStartMethod(string paramList) + private void EmitStartMethod(MethodsToGen_Extensions_ServiceCollection overload, string paramList) { - paramList = $"this {FullyQualifiedDisplayString.IServiceCollection} {Identifier.services}, {paramList}"; + paramList = $"this {Identifier.IServiceCollection} {Identifier.services}, {paramList}"; EmitBlankLineIfRequired(); - EmitStartBlock($$""" - /// Registers a configuration instance which TOptions will bind against. - public static {{FullyQualifiedDisplayString.IServiceCollection}} {{Identifier.Configure}}<{{Identifier.TOptions}}>({{paramList}}) where {{Identifier.TOptions}} : class - """); + _writer.WriteLine("/// Registers a configuration instance which TOptions will bind against."); + EmitInterceptsLocationAnnotations(overload); + EmitStartBlock($"public static {Identifier.IServiceCollection} {Identifier.Configure}<{Identifier.TOptions}>({paramList}) where {Identifier.TOptions} : class"); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/InterceptorLocationInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/InterceptorLocationInfo.cs new file mode 100644 index 0000000000000..d1dc4f4afa7e7 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/InterceptorLocationInfo.cs @@ -0,0 +1,90 @@ +// 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; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + internal sealed record InterceptorLocationInfo + { + public InterceptorLocationInfo(IInvocationOperation operation) + { + SyntaxNode operationSyntax = operation.Syntax; + TextSpan operationSpan = operationSyntax.Span; + SyntaxTree operationSyntaxTree = operationSyntax.SyntaxTree; + + FilePath = GetInterceptorFilePath(operationSyntaxTree, operation.SemanticModel?.Compilation.Options.SourceReferenceResolver); + + FileLinePositionSpan span = operationSyntaxTree.GetLineSpan(operationSpan); + LineNumber = span.StartLinePosition.Line + 1; + + // Calculate the character offset to the end of the binding invocation detected. + int invocationLength = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)operationSyntax).Expression).Expression.Span.Length; + CharacterNumber = span.StartLinePosition.Character + invocationLength + 2; + } + + public string FilePath { get; } + public int LineNumber { get; } + public int CharacterNumber { get; } + + // Utilize the same logic used by the interceptors API for resolving the source mapped value of a path. + // https://github.com/dotnet/roslyn/blob/f290437fcc75dad50a38c09e0977cce13a64f5ba/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs#L1063-L1064 + private static string GetInterceptorFilePath(SyntaxTree tree, SourceReferenceResolver? resolver) => + resolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath; + } + + internal sealed record ConfigurationBinderInterceptorInfo + { + private OverloadInterceptorInfo? _bind_Instance; + private OverloadInterceptorInfo? _bind_instance_BinderOptions; + private OverloadInterceptorInfo? _bind_key_instance; + + public void RegisterOverloadInfo(MethodsToGen_ConfigurationBinder overload, TypeSpec type, IInvocationOperation operation) + { + OverloadInterceptorInfo overloadInfo = DetermineOverload(overload, initIfNull: true); + overloadInfo.RegisterLocationInfo(type, operation); + } + + public OverloadInterceptorInfo GetOverloadInfo(MethodsToGen_ConfigurationBinder overload) => + DetermineOverload(overload, initIfNull: false) ?? throw new ArgumentNullException(nameof(overload)); + + private OverloadInterceptorInfo? DetermineOverload(MethodsToGen_ConfigurationBinder overload, bool initIfNull) + { + return overload switch + { + MethodsToGen_ConfigurationBinder.Bind_instance => InitIfNull(ref _bind_Instance), + MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions => InitIfNull(ref _bind_instance_BinderOptions), + MethodsToGen_ConfigurationBinder.Bind_key_instance => InitIfNull(ref _bind_key_instance), + _ => throw new InvalidOperationException(nameof(overload)), + }; + + OverloadInterceptorInfo InitIfNull(ref OverloadInterceptorInfo? info) + { + if (initIfNull) + { + info ??= new OverloadInterceptorInfo(); + } + + return info; + } + } + } + + internal sealed record OverloadInterceptorInfo : IEnumerable>> + { + private readonly Dictionary> _typeInterceptionInfo = new(); + + public void RegisterLocationInfo(TypeSpec type, IInvocationOperation operation) => + _typeInterceptionInfo.RegisterCacheEntry(type, new InterceptorLocationInfo(operation)); + + public IEnumerator>> GetEnumerator() => _typeInterceptionInfo.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/MethodsToGen.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/MethodsToGen.cs index 7b40f198e08f2..2c582b20e8ebd 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/MethodsToGen.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/MethodsToGen.cs @@ -10,11 +10,12 @@ public enum MethodsToGen_CoreBindingHelper { None = 0x0, BindCore = 0x1, - BindCoreUntyped = 0x2, - GetCore = 0x4, - GetValueCore = 0x8, + GetCore = 0x2, + GetValueCore = 0x4, + BindCoreMain = 0x8, Initialize = 0x10, - AsConfigWithChildren = 0x20, + HasValueOrChildren = 0x20, + AsConfigWithChildren = 0x40, } /// diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/BinderInvocation.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/BinderInvocation.cs index 554786589c8c8..ad7c4c09204d4 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/BinderInvocation.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/BinderInvocation.cs @@ -40,7 +40,7 @@ static bool IsCandidateBindingMethodName(string name) => IsValidMethodName_OptionsConfigurationServiceCollectionExtensions(name); } - private static bool IsBindingOperation(IInvocationOperation operation) + public static bool IsBindingOperation(IInvocationOperation operation) { if (operation.TargetMethod is not IMethodSymbol { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/ConfigurationBinder.cs index a663c441c55ce..e24ce11fe4374 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/ConfigurationBinder.cs @@ -102,15 +102,7 @@ private void RegisterBindInvocation(BinderInvocation invocation) if (GetTargetTypeForRootInvocationCore(type, invocation.Location) is TypeSpec typeSpec) { - Dictionary> types = _sourceGenSpec.TypesForGen_ConfigurationBinder_BindMethods; - - if (!types.TryGetValue(overload, out HashSet? typeSpecs)) - { - types[overload] = typeSpecs = new HashSet(); - } - - _sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; - typeSpecs.Add(typeSpec); + RegisterAsInterceptor_ConfigBinder_BindMethod(overload, typeSpec, invocation.Operation); } static ITypeSymbol? ResolveType(IOperation conversionOperation) => @@ -184,8 +176,8 @@ private void RegisterGetInvocation(BinderInvocation invocation) if (GetTargetTypeForRootInvocation(type, invocation.Location) is TypeSpec typeSpec) { - _sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, typeSpec); + RegisterAsInterceptor_ConfigBinder(overload, invocation.Operation); + RegisterTypeForGetCoreGen(typeSpec); } } @@ -253,10 +245,27 @@ private void RegisterGetValueInvocation(BinderInvocation invocation) if (IsParsableFromString(effectiveType, out _) && GetTargetTypeForRootInvocationCore(type, invocation.Location) is TypeSpec typeSpec) { - _sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; + RegisterAsInterceptor_ConfigBinder(overload, invocation.Operation); RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, typeSpec); } } + + private void RegisterAsInterceptor_ConfigBinder(MethodsToGen_ConfigurationBinder overload, IInvocationOperation operation) + { + _sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; + RegisterAsInterceptor(overload, operation); + } + + /// + /// Registers generated Bind methods as interceptors. This is done differently from other root + /// methods because we need to + /// explicitly account for the type to bind, to avoid type-check issues for polymorphic objects. + /// + private void RegisterAsInterceptor_ConfigBinder_BindMethod(MethodsToGen_ConfigurationBinder overload, TypeSpec typeSpec, IInvocationOperation operation) + { + _sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; + _sourceGenSpec.InterceptionInfo_ConfigBinder.RegisterOverloadInfo(overload, typeSpec, operation); + } } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/OptionsBuilderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/OptionsBuilderConfigurationExtensions.cs index d01e80d708ca1..a62e63c0d90d5 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/OptionsBuilderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/OptionsBuilderConfigurationExtensions.cs @@ -34,9 +34,6 @@ private void RegisterMethodInvocation_OptionsBuilderExt(BinderInvocation invocat return; } - // We are going to emit calls to APIs on IServiceCollection. - _sourceGenSpec.TypeNamespaces.Add("Microsoft.Extensions.DependencyInjection"); - if (targetMethod.Name is "Bind") { RegisterBindInvocation(invocation, typeSpec); @@ -61,20 +58,19 @@ private void RegisterBindInvocation(BinderInvocation invocation, TypeSpec typeSp return; } - if (paramCount is 2) - { - _sourceGenSpec.MethodsToGen_OptionsBuilderExt |= MethodsToGen_Extensions_OptionsBuilder.Bind_T; - } - else if (paramCount is 3 && SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type)) + MethodsToGen_Extensions_OptionsBuilder overload = paramCount switch { - _sourceGenSpec.MethodsToGen_OptionsBuilderExt |= MethodsToGen_Extensions_OptionsBuilder.Bind_T_BinderOptions; - } - else + 2 => MethodsToGen_Extensions_OptionsBuilder.Bind_T, + 3 when SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type) => + MethodsToGen_Extensions_OptionsBuilder.Bind_T_BinderOptions, + _ => MethodsToGen_Extensions_OptionsBuilder.None + }; + + if (overload is not MethodsToGen_Extensions_OptionsBuilder.None) { - return; + RegisterAsInterceptor_OptionsBuilder(overload, operation); + RegisterTypeForMethodGen(MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions, typeSpec); } - - RegisterTypeForMethodGen(MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions, typeSpec); } private void ParseBindConfigurationInvocation(BinderInvocation invocation, TypeSpec typeSpec) @@ -85,12 +81,26 @@ private void ParseBindConfigurationInvocation(BinderInvocation invocation, TypeS int paramCount = @params.Length; Debug.Assert(paramCount >= 2); - if (paramCount is 3 && @params[1].Type.SpecialType is SpecialType.System_String && SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type)) + if (paramCount is 3 && + @params[1].Type.SpecialType is SpecialType.System_String && + SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type)) { - _sourceGenSpec.MethodsToGen_OptionsBuilderExt |= MethodsToGen_Extensions_OptionsBuilder.BindConfiguration_T_path_BinderOptions; - RegisterTypeForBindCoreUntypedGen(typeSpec); + RegisterAsInterceptor_OptionsBuilder(MethodsToGen_Extensions_OptionsBuilder.BindConfiguration_T_path_BinderOptions, invocation.Operation); + RegisterTypeForBindCoreMainGen(typeSpec); } } + + private void RegisterAsInterceptor_OptionsBuilder(MethodsToGen_Extensions_OptionsBuilder overload, IInvocationOperation operation) + { + _sourceGenSpec.MethodsToGen_OptionsBuilderExt |= overload; + RegisterAsInterceptor(overload, operation); + + // Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource. + _sourceGenSpec.Namespaces.Add("Microsoft.Extensions.Options"); + + // Emitting refs to OptionsBuilder. + _sourceGenSpec.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); + } } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/OptionsConfigurationServiceCollectionExtensions.cs index 02c75b4ab653b..c356b29a69eff 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/OptionsConfigurationServiceCollectionExtensions.cs @@ -79,12 +79,14 @@ @params[1].Type.SpecialType is SpecialType.System_String && } RegisterTypeForMethodGen(overload, typeSpec); + RegisterAsInterceptor(overload, operation); } private void RegisterTypeForMethodGen(MethodsToGen_Extensions_ServiceCollection overload, TypeSpec typeSpec) { _sourceGenSpec.MethodsToGen_ServiceCollectionExt |= overload; - RegisterTypeForBindCoreUntypedGen(typeSpec); + _sourceGenSpec.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); + RegisterTypeForBindCoreMainGen(typeSpec); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj index 785a18c5c0978..e895d18c5556c 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj @@ -10,7 +10,7 @@ - + @@ -26,12 +26,14 @@ + - + + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ObjectSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ObjectSpec.cs index 1696ee099fe46..8cc0ba68b4938 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ObjectSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ObjectSpec.cs @@ -22,10 +22,6 @@ public ObjectSpec(INamedTypeSymbol type) : base(type) { } public List ConstructorParameters { get; } = new(); - private string _displayStringWithoutSpecialCharacters; - public string DisplayStringWithoutSpecialCharacters => - _displayStringWithoutSpecialCharacters ??= $"{MinimalDisplayString.Replace(".", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty)}"; - public override bool NeedsMemberBinding => CanInitialize && Properties.Values.Count > 0 && Properties.Values.Any(p => p.ShouldBind()); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParsableFromStringSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParsableFromStringSpec.cs index 6b5bb5b61ea37..e19e3e61d210f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParsableFromStringSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParsableFromStringSpec.cs @@ -24,7 +24,7 @@ public string ParseMethodName _parseMethodName ??= StringParsableTypeKind is StringParsableTypeKind.ByteArray ? "ParseByteArray" // MinimalDisplayString.Length is certainly > 2. - : $"Parse{(char.ToUpper(MinimalDisplayString[0]) + MinimalDisplayString.Substring(1)).Replace(".", "")}"; + : $"Parse{(char.ToUpper(DisplayString[0]) + DisplayString.Substring(1)).Replace(".", "")}"; return _parseMethodName; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/SourceGenerationSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/SourceGenerationSpec.cs index 88c4b24f57a5e..760d57b1dcc88 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/SourceGenerationSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/SourceGenerationSpec.cs @@ -1,21 +1,25 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { internal sealed record SourceGenerationSpec { + public Dictionary> InterceptionInfo { get; } = new(); + public ConfigurationBinderInterceptorInfo InterceptionInfo_ConfigBinder { get; } = new(); + public Dictionary> TypesForGen_CoreBindingHelper_Methods { get; } = new(); - public Dictionary> TypesForGen_ConfigurationBinder_BindMethods { get; } = new(); public HashSet PrimitivesForHelperGen { get; } = new(); - public HashSet TypeNamespaces { get; } = new() + public HashSet Namespaces { get; } = new() { "System", "System.CodeDom.Compiler", "System.Globalization", + "System.Runtime.CompilerServices", "Microsoft.Extensions.Configuration", }; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs index 6a6292b7ebd0b..6f5e26bf3f6d3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs @@ -17,17 +17,14 @@ public TypeSpec(ITypeSymbol type) { IsValueType = type.IsValueType; Namespace = type.ContainingNamespace?.ToDisplayString(); - FullyQualifiedDisplayString = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - MinimalDisplayString = type.ToDisplayString(s_minimalDisplayFormat); - Name = Namespace + "." + MinimalDisplayString.Replace(".", "+"); + DisplayString = type.ToDisplayString(s_minimalDisplayFormat); + Name = Namespace + "." + DisplayString.Replace(".", "+"); IsInterface = type.TypeKind is TypeKind.Interface; } public string Name { get; } - public string FullyQualifiedDisplayString { get; } - - public string MinimalDisplayString { get; } + public string DisplayString { get; } public string? Namespace { get; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/Strings.resx index 301913987d7c7..3978cbaac6ce4 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/Strings.resx +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/Strings.resx @@ -133,10 +133,10 @@ The collection element type is not supported: '{0}'. - The project's language version has to be at least 'C# 11'. + The project's language version has to be at least 'C# 12'. - Language version is required to be at least C# 11 + Language version is required to be at least C# 12 Cannot create instance of type '{0}' because it is missing a public instance constructor. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.cs.xlf index e248c54626865..c6672eaff0bcd 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.cs.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - Jazyková verze projektu musí být alespoň C# 11 + The project's language version has to be at least 'C# 12'. + Jazyková verze projektu musí být alespoň C# 11 - Language version is required to be at least C# 11 - Verze jazyka musí být alespoň C# 11 + Language version is required to be at least C# 12 + Verze jazyka musí být alespoň C# 11 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.de.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.de.xlf index 1fa847592bd02..5f353065a5f51 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.de.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - Die Sprachversion des Projekts muss mindestens „C# 11“ sein + The project's language version has to be at least 'C# 12'. + Die Sprachversion des Projekts muss mindestens „C# 11“ sein - Language version is required to be at least C# 11 - Die Sprachversion muss mindestens C# 11 sein + Language version is required to be at least C# 12 + Die Sprachversion muss mindestens C# 11 sein diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.es.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.es.xlf index c52b2317ceade..cd54149e66c2f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.es.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - La versión del lenguaje del proyecto debe ser al menos "C# 11". + The project's language version has to be at least 'C# 12'. + La versión del lenguaje del proyecto debe ser al menos "C# 11". - Language version is required to be at least C# 11 - La versión del lenguaje debe ser al menos C# 11 + Language version is required to be at least C# 12 + La versión del lenguaje debe ser al menos C# 11 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.fr.xlf index 19362d7336208..b1c0753a49e8a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.fr.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - La version de langage du projet doit être au moins « C# 11 ». + The project's language version has to be at least 'C# 12'. + La version de langage du projet doit être au moins « C# 11 ». - Language version is required to be at least C# 11 - La version du langage doit être au moins C# 11 + Language version is required to be at least C# 12 + La version du langage doit être au moins C# 11 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.it.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.it.xlf index f418a83d0d422..27d10a7ee5f9f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.it.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - La versione del linguaggio del progetto deve essere almeno 'C# 11'. + The project's language version has to be at least 'C# 12'. + La versione del linguaggio del progetto deve essere almeno 'C# 11'. - Language version is required to be at least C# 11 - La versione del linguaggio deve essere almeno C# 11 + Language version is required to be at least C# 12 + La versione del linguaggio deve essere almeno C# 11 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ja.xlf index ba59cfba40a89..24621bcc8b3d2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ja.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - プロジェクトの言語バージョンは少なくとも 'C# 11' である必要があります。 + The project's language version has to be at least 'C# 12'. + プロジェクトの言語バージョンは少なくとも 'C# 11' である必要があります。 - Language version is required to be at least C# 11 - 言語バージョンは少なくとも C# 11 である必要があります + Language version is required to be at least C# 12 + 言語バージョンは少なくとも C# 11 である必要があります diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ko.xlf index 10b9b107c4aad..217a0cd9f8e54 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ko.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - 프로젝트의 언어 버전은 'C# 11' 이상이어야 합니다. + The project's language version has to be at least 'C# 12'. + 프로젝트의 언어 버전은 'C# 11' 이상이어야 합니다. - Language version is required to be at least C# 11 - 언어 버전은 C# 11 이상이어야 합니다. + Language version is required to be at least C# 12 + 언어 버전은 C# 11 이상이어야 합니다. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pl.xlf index 2b558c588ebfb..0b24813a77a80 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pl.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - Wersja językowa projektu musi mieć wartość co najmniej „C# 11”. + The project's language version has to be at least 'C# 12'. + Wersja językowa projektu musi mieć wartość co najmniej „C# 11”. - Language version is required to be at least C# 11 - Wymagana jest wersja językowa co najmniej C# 11 + Language version is required to be at least C# 12 + Wymagana jest wersja językowa co najmniej C# 11 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pt-BR.xlf index 9d2a51c6aa9c9..0ad700e64e9eb 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pt-BR.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - A versão do idioma do projeto deve ser no mínimo 'C# 11'. + The project's language version has to be at least 'C# 12'. + A versão do idioma do projeto deve ser no mínimo 'C# 11'. - Language version is required to be at least C# 11 - A versão do idioma deve ser pelo menos C# 11 + Language version is required to be at least C# 12 + A versão do idioma deve ser pelo menos C# 11 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ru.xlf index 1ed03c55891a9..5e53330060c30 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ru.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - Версия языка проекта должна быть не ниже "C# 11". + The project's language version has to be at least 'C# 12'. + Версия языка проекта должна быть не ниже "C# 11". - Language version is required to be at least C# 11 - Версия языка должна быть не ниже C# 11 + Language version is required to be at least C# 12 + Версия языка должна быть не ниже C# 11 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.tr.xlf index 8a6dbf76bab7f..cfaea488fd195 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.tr.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - Projenin dil sürümü en az 'C# 11' olmalıdır. + The project's language version has to be at least 'C# 12'. + Projenin dil sürümü en az 'C# 11' olmalıdır. - Language version is required to be at least C# 11 - Dil sürümünün en az C# 11 olması gerekir + Language version is required to be at least C# 12 + Dil sürümünün en az C# 11 olması gerekir diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hans.xlf index 9d0c0eb3a5d6d..3dfb711b39b4e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - 项目的语言版本必须至少为 "C# 11"。 + The project's language version has to be at least 'C# 12'. + 项目的语言版本必须至少为 "C# 11"。 - Language version is required to be at least C# 11 - 语言版本必须至少为 C# 11 + Language version is required to be at least C# 12 + 语言版本必须至少为 C# 11 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hant.xlf index dc6ded618c8e9..9917b18949880 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -28,13 +28,13 @@ - The project's language version has to be at least 'C# 11'. - 專案的語言版本必須至少為 'C# 11'。 + The project's language version has to be at least 'C# 12'. + 專案的語言版本必須至少為 'C# 11'。 - Language version is required to be at least C# 11 - 語言版本要求至少為 C#11 + Language version is required to be at least C# 12 + 語言版本要求至少為 C#11 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs index 56dddc6f8bc83..eea01092667e8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -8,7 +8,6 @@ using System.Globalization; using System.Linq; using System.Reflection; -using System.Text; #if BUILDING_SOURCE_GENERATOR_TESTS using Microsoft.Extensions.Configuration; #endif @@ -2037,6 +2036,7 @@ public void ComplexObj_As_Enumerable_Element() ValidateGeolocation(obj); } +#if !BUILDING_SOURCE_GENERATOR_TESTS [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] public void TraceSwitchTest() { @@ -2056,6 +2056,7 @@ public void TraceSwitchTest() Assert.Equal("Info", ts.Value); #endif // NETCOREAPP } +#endif private void ValidateGeolocation(IGeolocation location) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt index 2149dcaaa0714..528847cfeb3a3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the configuration instance to a new instance of type T. - public static T? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -18,11 +25,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.Collections.Generic; using System.Globalization; using System.Linq; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocationAttribute(@"src-0.cs", 12, 17)] + public static T? Get(this IConfiguration configuration) => (T?)(GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); + #endregion IConfiguration extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClassWithCustomCollections = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "CustomDictionary", "CustomList", "IReadOnlyList", "IReadOnlyDictionary" }); public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) @@ -46,16 +60,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return obj; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref Program.CustomDictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -67,11 +76,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.CustomList obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -83,11 +87,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -99,11 +98,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref ICollection obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -115,11 +109,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref IReadOnlyList obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - if (obj is not ICollection temp) { return; @@ -136,11 +125,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -152,11 +136,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref IDictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -168,11 +147,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref IReadOnlyDictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - if (obj is not IDictionary temp) { return; @@ -189,11 +163,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClassWithCustomCollections obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClassWithCustomCollections), s_configKeys_ProgramMyClassWithCustomCollections, configuration, binderOptions); if (AsConfigWithChildren(configuration.GetSection("CustomDictionary")) is IConfigurationSection section1) @@ -229,6 +198,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -281,7 +251,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -298,5 +268,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind.generated.txt index 406e8db671677..36ac12fd31f83 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind.generated.txt @@ -2,18 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. - public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null); - - /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. - public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj, global::System.Action? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetBinderOptions(configureOptions)); + using System; + using System.CodeDom.Compiler; - /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. - public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration.GetSection(key), ref obj, binderOptions: null); + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -23,20 +24,72 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocationAttribute(@"src-0.cs", 13, 18)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? obj) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + var typedObj = (Program.MyClass)obj; + BindCore(configuration, ref typedObj, binderOptions: null); + } + + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocationAttribute(@"src-0.cs", 14, 24)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? obj, Action? configureOptions) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + var typedObj = (Program.MyClass)obj; + BindCore(configuration, ref typedObj, GetBinderOptions(configureOptions)); + } + + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocationAttribute(@"src-0.cs", 15, 24)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, string key, object? obj) { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + if (obj is null) { throw new ArgumentNullException(nameof(obj)); } + var typedObj = (Program.MyClass)obj; + BindCore(configuration.GetSection(key), ref typedObj, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -48,11 +101,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -64,11 +112,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) @@ -81,11 +124,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -120,6 +158,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -163,7 +202,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -180,5 +219,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance.generated.txt index 106e01795369e..02fb06957bb3d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. - public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -17,20 +24,36 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); - - public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocationAttribute(@"src-0.cs", 12, 20)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? obj) { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + if (obj is null) { throw new ArgumentNullException(nameof(obj)); } + var typedObj = (Program.MyClass)obj; + BindCore(configuration, ref typedObj, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -42,11 +65,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -58,11 +76,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) @@ -75,11 +88,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -114,6 +122,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -156,5 +165,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt index a1cb7d6b93b5d..4703980996b88 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. - public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj, global::System.Action? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetBinderOptions(configureOptions)); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -17,20 +24,36 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); - - public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocationAttribute(@"src-0.cs", 12, 20)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? obj, Action? configureOptions) { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + if (obj is null) { throw new ArgumentNullException(nameof(obj)); } + var typedObj = (Program.MyClass)obj; + BindCore(configuration, ref typedObj, GetBinderOptions(configureOptions)); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -42,11 +65,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -58,11 +76,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) @@ -75,11 +88,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -114,6 +122,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -157,7 +166,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -174,5 +183,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Key_Instance.generated.txt index f3ee8a9ff4384..9937129699716 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Key_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Key_Instance.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. - public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration.GetSection(key), ref obj, binderOptions: null); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -17,20 +24,36 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); - - public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocationAttribute(@"src-0.cs", 12, 20)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, string key, object? obj) { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + if (obj is null) { throw new ArgumentNullException(nameof(obj)); } + var typedObj = (Program.MyClass)obj; + BindCore(configuration.GetSection(key), ref typedObj, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -42,11 +65,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -58,11 +76,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) @@ -75,11 +88,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -114,6 +122,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -156,5 +165,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get.generated.txt index fb71c70b4dd3b..3e6ce1459b289 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get.generated.txt @@ -2,21 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the configuration instance to a new instance of type T. - public static T? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); - - /// Attempts to bind the configuration instance to a new instance of type T. - public static T? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, typeof(T), configureOptions) ?? default(T)); - - /// Attempts to bind the configuration instance to a new instance of type T. - public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, type, configureOptions: null); + using System; + using System.CodeDom.Compiler; - /// Attempts to bind the configuration instance to a new instance of type T. - public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, global::System.Action? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, type, configureOptions); + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -26,11 +24,30 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocationAttribute(@"src-0.cs", 12, 38)] + public static T? Get(this IConfiguration configuration) => (T?)(GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); + + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocationAttribute(@"src-0.cs", 14, 36)] + public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocationAttribute(@"src-0.cs", 13, 36)] + public static object? Get(this IConfiguration configuration, Type type) => GetCore(configuration, type, configureOptions: null); + + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocationAttribute(@"src-0.cs", 15, 36)] + public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + #endregion IConfiguration extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" }); private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); @@ -54,24 +71,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration BindCore(configuration, ref obj, binderOptions); return obj; } - - if (type == typeof(Program.MyClass2)) + else if (type == typeof(Program.MyClass2)) { var obj = new Program.MyClass2(); BindCore(configuration, ref obj, binderOptions); return obj; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -83,11 +94,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - var temp2 = new List(); BindCore(configuration, ref temp2, binderOptions); int originalCount = obj.Length; @@ -97,11 +103,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -113,11 +114,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -154,11 +150,6 @@ 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)); - } - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); if (configuration["MyInt"] is string value15) @@ -167,6 +158,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -219,7 +211,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -236,5 +228,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue.generated.txt index c9d49faa93724..e4bcaf6a9b7c9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue.generated.txt @@ -2,21 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Extracts the value with the specified key and converts it to the specified type. - public static T? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, typeof(T), key) ?? default(T)); - - /// Extracts the value with the specified key and converts it to the specified type. - public static T? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, T defaultValue) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, typeof(T), key) ?? defaultValue); - - /// Extracts the value with the specified key and converts it to the specified type. - public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, type, key); + using System; + using System.CodeDom.Compiler; - /// Extracts the value with the specified key and converts it to the specified type. - public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key, object? defaultValue) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, type, key) ?? defaultValue; + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -25,11 +23,30 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System; using System.CodeDom.Compiler; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocationAttribute(@"src-0.cs", 13, 18)] + public static T? GetValue(this IConfiguration configuration, string key) => (T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? default(T)); + + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocationAttribute(@"src-0.cs", 16, 24)] + public static T? GetValue(this IConfiguration configuration, string key, T defaultValue) => (T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? defaultValue); + + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocationAttribute(@"src-0.cs", 14, 24)] + public static object? GetValue(this IConfiguration configuration, Type type, string key) => BindingExtensions.GetValueCore(configuration, type, key); + + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocationAttribute(@"src-0.cs", 17, 24)] + public static object? GetValue(this IConfiguration configuration, Type type, string key, object? defaultValue) => BindingExtensions.GetValueCore(configuration, type, key) ?? defaultValue; + #endregion IConfiguration extensions. + + #region Core binding extensions. public static object? GetValueCore(this IConfiguration configuration, Type type, string key) { if (configuration is null) @@ -48,18 +65,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return ParseInt(value, () => section.Path); } - - if (type == typeof(bool?)) + else if (type == typeof(bool?)) { return ParseBool(value, () => section.Path); } - - if (type == typeof(byte[])) + else if (type == typeof(byte[])) { return ParseByteArray(value, () => section.Path); } - - if (type == typeof(CultureInfo)) + else if (type == typeof(CultureInfo)) { return ParseCultureInfo(value, () => section.Path); } @@ -114,5 +128,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key.generated.txt index 17c963bd980a7..2438cf530ca4c 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Extracts the value with the specified key and converts it to the specified type. - public static T? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, typeof(T), key) ?? default(T)); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -16,11 +23,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System; using System.CodeDom.Compiler; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocationAttribute(@"src-0.cs", 10, 20)] + public static T? GetValue(this IConfiguration configuration, string key) => (T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? default(T)); + #endregion IConfiguration extensions. + + #region Core binding extensions. public static object? GetValueCore(this IConfiguration configuration, Type type, string key) { if (configuration is null) @@ -54,5 +68,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt index 1148109b9f5a8..e6db24d522f3c 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Extracts the value with the specified key and converts it to the specified type. - public static T? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, T defaultValue) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, typeof(T), key) ?? defaultValue); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -16,11 +23,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System; using System.CodeDom.Compiler; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocationAttribute(@"src-0.cs", 12, 20)] + public static T? GetValue(this IConfiguration configuration, string key, T defaultValue) => (T?)(BindingExtensions.GetValueCore(configuration, typeof(T), key) ?? defaultValue); + #endregion IConfiguration extensions. + + #region Core binding extensions. public static object? GetValueCore(this IConfiguration configuration, Type type, string key) { if (configuration is null) @@ -54,5 +68,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt index c833b20f18dcf..a36f9fafebcff 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Extracts the value with the specified key and converts it to the specified type. - public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, type, key); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -16,11 +23,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System; using System.CodeDom.Compiler; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocationAttribute(@"src-0.cs", 10, 20)] + public static object? GetValue(this IConfiguration configuration, Type type, string key) => BindingExtensions.GetValueCore(configuration, type, key); + #endregion IConfiguration extensions. + + #region Core binding extensions. public static object? GetValueCore(this IConfiguration configuration, Type type, string key) { if (configuration is null) @@ -54,5 +68,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt index f773f79ce6c2c..356f6bb1a933e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Extracts the value with the specified key and converts it to the specified type. - public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key, object? defaultValue) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetValueCore(configuration, type, key) ?? defaultValue; + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -16,11 +23,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System; using System.CodeDom.Compiler; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Extracts the value with the specified key and converts it to the specified type. + [InterceptsLocationAttribute(@"src-0.cs", 11, 20)] + public static object? GetValue(this IConfiguration configuration, Type type, string key, object? defaultValue) => BindingExtensions.GetValueCore(configuration, type, key) ?? defaultValue; + #endregion IConfiguration extensions. + + #region Core binding extensions. public static object? GetValueCore(this IConfiguration configuration, Type type, string key) { if (configuration is null) @@ -54,5 +68,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt index de8201fe6fed2..85d0901de3ff6 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the configuration instance to a new instance of type T. - public static T? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -17,11 +24,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocationAttribute(@"src-0.cs", 11, 40)] + public static T? Get(this IConfiguration configuration) => (T?)(GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); + #endregion IConfiguration extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" }); public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) @@ -45,16 +59,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return obj; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -66,11 +75,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - var temp1 = new List(); BindCore(configuration, ref temp1, binderOptions); int originalCount = obj.Length; @@ -80,11 +84,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -96,11 +95,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -135,6 +129,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -187,7 +182,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -204,5 +199,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T_BinderOptions.generated.txt index 34fadacace146..d394cc7b269f7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T_BinderOptions.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the configuration instance to a new instance of type T. - public static T? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -17,11 +24,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocationAttribute(@"src-0.cs", 11, 40)] + public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + #endregion IConfiguration extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" }); public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) @@ -45,16 +59,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return obj; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -66,11 +75,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - var temp1 = new List(); BindCore(configuration, ref temp1, binderOptions); int originalCount = obj.Length; @@ -80,11 +84,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -96,11 +95,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -135,6 +129,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -187,7 +182,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -204,5 +199,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf.generated.txt index 16a98c931a705..83cd88561310a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the configuration instance to a new instance of type T. - public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, type, configureOptions: null); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -17,11 +24,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocationAttribute(@"src-0.cs", 11, 40)] + public static object? Get(this IConfiguration configuration, Type type) => GetCore(configuration, type, configureOptions: null); + #endregion IConfiguration extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) @@ -45,16 +59,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return obj; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); if (configuration["MyInt"] is string value1) @@ -63,6 +72,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -115,7 +125,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -132,5 +142,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt index 8d1ee9ed3cd9a..91714c80cd013 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the configuration instance to a new instance of type T. - public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, global::System.Action? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.GetCore(configuration, type, configureOptions); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -17,11 +24,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocationAttribute(@"src-0.cs", 11, 20)] + public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + #endregion IConfiguration extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) @@ -45,16 +59,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return obj; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); if (configuration["MyInt"] is string value1) @@ -63,6 +72,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -115,7 +125,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -132,5 +142,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/BindConfiguration.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/BindConfiguration.generated.txt index a74dbfdd04b5b..88b35037c22c3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/BindConfiguration.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/BindConfiguration.generated.txt @@ -2,31 +2,18 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedOptionsBuilderBinder +namespace System.Runtime.CompilerServices { - /// Registers the dependency injection container to bind against the obtained from the DI service provider. - public static global::Microsoft.Extensions.Options.OptionsBuilder BindConfiguration(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, string configSectionPath, global::System.Action? configureOptions = null) where TOptions : class - { - if (optionsBuilder is null) - { - throw new global::System.ArgumentNullException(nameof(optionsBuilder)); - } + using System; + using System.CodeDom.Compiler; - if (configSectionPath is null) + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) { - throw new global::System.ArgumentNullException(nameof(configSectionPath)); } - - optionsBuilder.Configure((obj, configuration) => - { - global::Microsoft.Extensions.Configuration.IConfiguration section = string.Equals(string.Empty, configSectionPath, global::System.StringComparison.OrdinalIgnoreCase) ? configuration : configuration.GetSection(configSectionPath); - global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(section, obj, typeof(TOptions), configureOptions); - }); - - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton, global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource>(optionsBuilder.Services); - return optionsBuilder; } } @@ -34,31 +21,74 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region OptionsBuilder extensions. + /// Registers the dependency injection container to bind against the obtained from the DI service provider. + [InterceptsLocationAttribute(@"src-0.cs", 12, 24)] + public static OptionsBuilder BindConfiguration(this OptionsBuilder optionsBuilder, string configSectionPath, Action? configureOptions = null) where TOptions : class + { + if (optionsBuilder is null) + { + throw new ArgumentNullException(nameof(optionsBuilder)); + } + + if (configSectionPath is null) + { + throw new ArgumentNullException(nameof(configSectionPath)); + } + + optionsBuilder.Configure((obj, configuration) => + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + IConfiguration section = string.Equals(string.Empty, configSectionPath, StringComparison.OrdinalIgnoreCase) ? configuration : configuration.GetSection(configSectionPath); + BindCoreMain(section, obj, typeof(TOptions), configureOptions); + }); + + optionsBuilder.Services.AddSingleton, ConfigurationChangeTokenSource>(); + return optionsBuilder; + } + #endregion OptionsBuilder extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) { throw new ArgumentNullException(nameof(configuration)); } - BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } if (!HasValueOrChildren(configuration)) { return; } + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (type == typeof(Program.MyClass)) { var temp = (Program.MyClass)obj; @@ -66,16 +96,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -87,11 +112,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -110,6 +130,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -162,7 +183,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -179,5 +200,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T.generated.txt index ac53b58f24da2..633196e7a742d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T.generated.txt @@ -2,49 +2,18 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedOptionsBuilderBinder +namespace System.Runtime.CompilerServices { - /// Registers a configuration instance which will bind against. - public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, global::Microsoft.Extensions.Configuration.IConfiguration configuration) where TOptions : class - { - return global::GeneratedOptionsBuilderBinder.Bind(optionsBuilder, configuration, configureOptions: null); - } - - /// Registers a configuration instance which will bind against. - public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) where TOptions : class - { - if (optionsBuilder is null) - { - throw new global::System.ArgumentNullException(nameof(optionsBuilder)); - } - - global::GeneratedServiceCollectionBinder.Configure(optionsBuilder.Services, optionsBuilder.Name, configuration, configureOptions); - return optionsBuilder; - } -} + using System; + using System.CodeDom.Compiler; -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedServiceCollectionBinder -{ - /// Registers a configuration instance which TOptions will bind against. - public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) where TOptions : class + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute { - if (services is null) - { - throw new global::System.ArgumentNullException(nameof(services)); - } - - if (configuration is null) + public InterceptsLocationAttribute(string filePath, int line, int column) { - throw new global::System.ArgumentNullException(nameof(configuration)); } - - global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource(name, configuration)); - return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions))); } } @@ -52,31 +21,79 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region OptionsBuilder extensions. + /// Registers a configuration instance which will bind against. + [InterceptsLocationAttribute(@"src-0.cs", 15, 24)] + public static OptionsBuilder Bind(this OptionsBuilder optionsBuilder, IConfiguration configuration) where TOptions : class + { + return Bind(optionsBuilder, configuration, configureOptions: null); + } + + /// Registers a configuration instance which will bind against. + public static OptionsBuilder Bind(this OptionsBuilder optionsBuilder, IConfiguration configuration, Action? configureOptions) where TOptions : class + { + if (optionsBuilder is null) + { + throw new ArgumentNullException(nameof(optionsBuilder)); + } + + Configure(optionsBuilder.Services, optionsBuilder.Name, configuration, configureOptions); + return optionsBuilder; + } + #endregion OptionsBuilder extensions. + + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration configuration, Action? configureOptions) where TOptions : class + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, configuration)); + return services.AddSingleton>(new Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) { throw new ArgumentNullException(nameof(configuration)); } - BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } if (!HasValueOrChildren(configuration)) { return; } + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (type == typeof(Program.MyClass)) { var temp = (Program.MyClass)obj; @@ -84,16 +101,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -105,11 +117,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -128,6 +135,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -180,7 +188,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -197,5 +205,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T_BinderOptions.generated.txt index fd3ec70a8a328..fb5b4b4ad721d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T_BinderOptions.generated.txt @@ -2,43 +2,18 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedOptionsBuilderBinder +namespace System.Runtime.CompilerServices { - /// Registers a configuration instance which will bind against. - public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) where TOptions : class - { - if (optionsBuilder is null) - { - throw new global::System.ArgumentNullException(nameof(optionsBuilder)); - } - - global::GeneratedServiceCollectionBinder.Configure(optionsBuilder.Services, optionsBuilder.Name, configuration, configureOptions); - return optionsBuilder; - } -} + using System; + using System.CodeDom.Compiler; -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedServiceCollectionBinder -{ - /// Registers a configuration instance which TOptions will bind against. - public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) where TOptions : class + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute { - if (services is null) - { - throw new global::System.ArgumentNullException(nameof(services)); - } - - if (configuration is null) + public InterceptsLocationAttribute(string filePath, int line, int column) { - throw new global::System.ArgumentNullException(nameof(configuration)); } - - global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource(name, configuration)); - return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions))); } } @@ -46,31 +21,73 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region OptionsBuilder extensions. + /// Registers a configuration instance which will bind against. + [InterceptsLocationAttribute(@"src-0.cs", 15, 24)] + public static OptionsBuilder Bind(this OptionsBuilder optionsBuilder, IConfiguration configuration, Action? configureOptions) where TOptions : class + { + if (optionsBuilder is null) + { + throw new ArgumentNullException(nameof(optionsBuilder)); + } + + Configure(optionsBuilder.Services, optionsBuilder.Name, configuration, configureOptions); + return optionsBuilder; + } + #endregion OptionsBuilder extensions. + + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration configuration, Action? configureOptions) where TOptions : class + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, configuration)); + return services.AddSingleton>(new Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) { throw new ArgumentNullException(nameof(configuration)); } - BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } if (!HasValueOrChildren(configuration)) { return; } + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (type == typeof(Program.MyClass)) { var temp = (Program.MyClass)obj; @@ -78,16 +95,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -99,11 +111,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -122,6 +129,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -174,7 +182,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -191,5 +199,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt index 5d30288a21e78..1bf110454ec7b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt @@ -2,12 +2,19 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedConfigurationBinder +namespace System.Runtime.CompilerServices { - /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. - public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null); + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -17,20 +24,36 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Prop0", "Prop1", "Prop2", "Prop3", "Prop4", "Prop5", "Prop6", "Prop8", "Prop9", "Prop10", "Prop13", "Prop14", "Prop15", "Prop16", "Prop17", "Prop19", "Prop20", "Prop21", "Prop23", "Prop24", "Prop25", "Prop26", "Prop27", "Prop7", "Prop11", "Prop12", "Prop18", "Prop22" }); - - public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocationAttribute(@"src-0.cs", 13, 16)] + public static void Bind_ProgramMyClass(this IConfiguration configuration, object? obj) { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + if (obj is null) { throw new ArgumentNullException(nameof(obj)); } + var typedObj = (Program.MyClass)obj; + BindCore(configuration, ref typedObj, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Prop0", "Prop1", "Prop2", "Prop3", "Prop4", "Prop5", "Prop6", "Prop8", "Prop9", "Prop10", "Prop13", "Prop14", "Prop15", "Prop16", "Prop17", "Prop19", "Prop20", "Prop21", "Prop23", "Prop24", "Prop25", "Prop26", "Prop27", "Prop7", "Prop11", "Prop12", "Prop18", "Prop22" }); + + public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) + { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); if (configuration["Prop0"] is string value0) @@ -168,6 +191,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -517,5 +541,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T.generated.txt index 461f6050e2fdf..7f626f0e27c52 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T.generated.txt @@ -2,64 +2,84 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedServiceCollectionBinder +namespace System.Runtime.CompilerServices { - /// Registers a configuration instance which TOptions will bind against. - public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration) where TOptions : class - { - return global::GeneratedServiceCollectionBinder.Configure(services, string.Empty, configuration, configureOptions: null); - } + using System; + using System.CodeDom.Compiler; - /// Registers a configuration instance which TOptions will bind against. - public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) where TOptions : class + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute { - if (services is null) - { - throw new global::System.ArgumentNullException(nameof(services)); - } - - if (configuration is null) + public InterceptsLocationAttribute(string filePath, int line, int column) { - throw new global::System.ArgumentNullException(nameof(configuration)); } - - global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource(name, configuration)); - return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions))); } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + [InterceptsLocationAttribute(@"src-0.cs", 14, 18)] + public static IServiceCollection Configure(this IServiceCollection services, IConfiguration configuration) where TOptions : class + { + return Configure(services, string.Empty, configuration, configureOptions: null); + } + + /// Registers a configuration instance which TOptions will bind against. + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration configuration, Action? configureOptions) where TOptions : class + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, configuration)); + return services.AddSingleton>(new Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) { throw new ArgumentNullException(nameof(configuration)); } - BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } if (!HasValueOrChildren(configuration)) { return; } + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (type == typeof(Program.MyClass)) { var temp = (Program.MyClass)obj; @@ -67,16 +87,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -88,11 +103,6 @@ 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)); - } - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); if (configuration["MyInt"] is string value1) @@ -103,11 +113,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { var value = new Program.MyClass2(); @@ -118,11 +123,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -134,11 +134,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -173,6 +168,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -225,7 +221,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -242,5 +238,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_BinderOptions.generated.txt index a57f652720bc8..5848c2412f9b7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_BinderOptions.generated.txt @@ -2,64 +2,84 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedServiceCollectionBinder +namespace System.Runtime.CompilerServices { - /// Registers a configuration instance which TOptions will bind against. - public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) where TOptions : class - { - return global::GeneratedServiceCollectionBinder.Configure(services, string.Empty, configuration, configureOptions); - } + using System; + using System.CodeDom.Compiler; - /// Registers a configuration instance which TOptions will bind against. - public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) where TOptions : class + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute { - if (services is null) - { - throw new global::System.ArgumentNullException(nameof(services)); - } - - if (configuration is null) + public InterceptsLocationAttribute(string filePath, int line, int column) { - throw new global::System.ArgumentNullException(nameof(configuration)); } - - global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource(name, configuration)); - return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions))); } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + [InterceptsLocationAttribute(@"src-0.cs", 14, 18)] + public static IServiceCollection Configure(this IServiceCollection services, IConfiguration configuration, Action? configureOptions) where TOptions : class + { + return Configure(services, string.Empty, configuration, configureOptions); + } + + /// Registers a configuration instance which TOptions will bind against. + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration configuration, Action? configureOptions) where TOptions : class + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, configuration)); + return services.AddSingleton>(new Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) { throw new ArgumentNullException(nameof(configuration)); } - BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } if (!HasValueOrChildren(configuration)) { return; } + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (type == typeof(Program.MyClass)) { var temp = (Program.MyClass)obj; @@ -67,16 +87,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -88,11 +103,6 @@ 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)); - } - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); if (configuration["MyInt"] is string value1) @@ -103,11 +113,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { var value = new Program.MyClass2(); @@ -118,11 +123,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -134,11 +134,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -173,6 +168,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -225,7 +221,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -242,5 +238,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name.generated.txt index 66975c3164d74..91226d730166f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name.generated.txt @@ -2,64 +2,84 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedServiceCollectionBinder +namespace System.Runtime.CompilerServices { - /// Registers a configuration instance which TOptions will bind against. - public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration) where TOptions : class - { - return global::GeneratedServiceCollectionBinder.Configure(services, name, configuration, configureOptions: null); - } + using System; + using System.CodeDom.Compiler; - /// Registers a configuration instance which TOptions will bind against. - public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) where TOptions : class + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute { - if (services is null) - { - throw new global::System.ArgumentNullException(nameof(services)); - } - - if (configuration is null) + public InterceptsLocationAttribute(string filePath, int line, int column) { - throw new global::System.ArgumentNullException(nameof(configuration)); } - - global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource(name, configuration)); - return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions))); } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + [InterceptsLocationAttribute(@"src-0.cs", 14, 18)] + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration configuration) where TOptions : class + { + return Configure(services, name, configuration, configureOptions: null); + } + + /// Registers a configuration instance which TOptions will bind against. + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration configuration, Action? configureOptions) where TOptions : class + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, configuration)); + return services.AddSingleton>(new Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) { throw new ArgumentNullException(nameof(configuration)); } - BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } if (!HasValueOrChildren(configuration)) { return; } + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (type == typeof(Program.MyClass)) { var temp = (Program.MyClass)obj; @@ -67,16 +87,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -88,11 +103,6 @@ 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)); - } - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); if (configuration["MyInt"] is string value1) @@ -103,11 +113,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { var value = new Program.MyClass2(); @@ -118,11 +123,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -134,11 +134,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -173,6 +168,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -225,7 +221,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -242,5 +238,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name_BinderOptions.generated.txt index 0263ef1217940..8c9ccaa71a779 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name_BinderOptions.generated.txt @@ -2,58 +2,78 @@ #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. -/// Generated helper providing an AOT and linking compatible implementation for configuration binding. -[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] -internal static class GeneratedServiceCollectionBinder +namespace System.Runtime.CompilerServices { - /// Registers a configuration instance which TOptions will bind against. - public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) where TOptions : class - { - if (services is null) - { - throw new global::System.ArgumentNullException(nameof(services)); - } + using System; + using System.CodeDom.Compiler; - if (configuration is null) + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) { - throw new global::System.ArgumentNullException(nameof(configuration)); } - - global::Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(services); - global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource(name, configuration)); - return global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddSingleton>(services, new global::Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCoreUntyped(configuration, obj, typeof(TOptions), configureOptions))); } } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; + using System.Runtime.CompilerServices; - /// Provide core binding logic. [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] - file static class CoreBindingHelper + file static class BindingExtensions { + #region IServiceCollection extensions. + /// Registers a configuration instance which TOptions will bind against. + [InterceptsLocationAttribute(@"src-0.cs", 14, 18)] + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration configuration, Action? configureOptions) where TOptions : class + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + OptionsServiceCollectionExtensions.AddOptions(services); + services.AddSingleton>(new ConfigurationChangeTokenSource(name, configuration)); + return services.AddSingleton>(new Microsoft.Extensions.Options.ConfigureNamedOptions(name, obj => BindCoreMain(configuration, obj, typeof(TOptions)configureOptions))); + } + #endregion IServiceCollection extensions. + + #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object obj, Type type, Action? configureOptions) { if (configuration is null) { throw new ArgumentNullException(nameof(configuration)); } - BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } if (!HasValueOrChildren(configuration)) { return; } + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + if (type == typeof(Program.MyClass)) { var temp = (Program.MyClass)obj; @@ -61,16 +81,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return; } - throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -82,11 +97,6 @@ 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)); - } - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); if (configuration["MyInt"] is string value1) @@ -97,11 +107,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { var value = new Program.MyClass2(); @@ -112,11 +117,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) @@ -128,11 +128,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) { - if (obj is null) - { - throw new ArgumentNullException(nameof(obj)); - } - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); obj.MyString = configuration["MyString"]!; @@ -167,6 +162,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { @@ -219,7 +215,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (binderOptions.BindNonPublicProperties) { - throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); } return binderOptions; @@ -236,5 +232,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } + #endregion Core binding extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.Options.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.Options.cs index 8807dfb096220..c9eb9c70927f9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.Options.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.Options.cs @@ -8,6 +8,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests { public partial class ConfigurationBindingGeneratorTests { + #region IServiceCollection extensions. private string GetConfigureSource(string paramList) => $$""" using System.Collections.Generic; using Microsoft.Extensions.Configuration; @@ -40,7 +41,9 @@ public class MyClass2 } } """; + #endregion IServiceCollection extensions. + #region OptionsBuilder extensions. [Fact] public async Task Configure_T() => await VerifyAgainstBaselineUsingFile("Configure_T.generated.txt", GetConfigureSource("section"), extType: ExtensionClassType.ServiceCollection); @@ -126,5 +129,6 @@ public class MyClass await VerifyAgainstBaselineUsingFile("BindConfiguration.generated.txt", GetSource(), extType: ExtensionClassType.OptionsBuilder); await VerifyAgainstBaselineUsingFile("BindConfiguration.generated.txt", GetSource(@", _ => { }"), extType: ExtensionClassType.OptionsBuilder); } + #endregion OptionsBuilder extensions. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.cs index aba2a9f6184f2..b5c3fb49c5e7e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -44,7 +43,6 @@ public class MyClass2 { } [Theory] [InlineData(LanguageVersion.Preview)] - [InlineData(LanguageVersion.CSharp11)] public async Task Bind(LanguageVersion langVersion) => await VerifyAgainstBaselineUsingFile("Bind.generated.txt", BindCallSampleCode, langVersion, extType: ExtensionClassType.ConfigurationBinder); @@ -651,7 +649,7 @@ public interface ICustomSet : ISet await VerifyAgainstBaselineUsingFile("Collections.generated.txt", source, assessDiagnostics: (d) => { - Console.WriteLine((d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count() , d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count())); + Console.WriteLine((d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count(), d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count())); Assert.Equal(3, d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); Assert.Equal(6, d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count()); }); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs index 5bc5145739daa..a512c5efe495b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs @@ -52,15 +52,17 @@ private enum ExtensionClassType ServiceCollection, } - [Fact] - public async Task LangVersionMustBeCharp11OrHigher() + [Theory] + [InlineData(LanguageVersion.CSharp11)] + [InlineData(LanguageVersion.CSharp10)] + public async Task LangVersionMustBeCharp12OrHigher(LanguageVersion langVersion) { - var (d, r) = await RunGenerator(BindCallSampleCode, LanguageVersion.CSharp10); + var (d, r) = await RunGenerator(BindCallSampleCode, langVersion); Assert.Empty(r); Diagnostic diagnostic = Assert.Single(d); Assert.True(diagnostic.Id == "SYSLIB1102"); - Assert.Contains("C# 11", diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); + Assert.Contains("C# 12", diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); } @@ -250,9 +252,9 @@ public class MyClass2 { } Assert.Single(r); string generatedSource = string.Join('\n', r[0].SourceText.Lines.Select(x => x.ToString())); - Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass0 obj) => {{ }};", generatedSource); - Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass1 obj, global::System.Action? configureOptions) => {{ }};", generatedSource); - Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, global::Program.MyClass2 obj) => {{ }};", generatedSource); + Assert.Contains("public static void Bind_ProgramMyClass0(this IConfiguration configuration, object? obj)", generatedSource); + Assert.Contains("public static void Bind_ProgramMyClass1(this IConfiguration configuration, object? obj, Action? configureOptions)", generatedSource); + Assert.Contains("public static void Bind_ProgramMyClass2(this IConfiguration configuration, string key, object? obj)", generatedSource); Assert.Empty(d); } @@ -395,7 +397,7 @@ private static async Task VerifyAgainstBaselineUsingFile( private static async Task<(ImmutableArray, ImmutableArray)> RunGenerator( string testSourceCode, - LanguageVersion langVersion = LanguageVersion.CSharp11, + LanguageVersion langVersion = LanguageVersion.Preview, IEnumerable? references = null) => await RoslynTestUtils.RunGenerator( new ConfigurationBindingGenerator(), diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj index 2108bc2574ed2..cfd45c365d42a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj @@ -4,7 +4,7 @@ true SYSLIB1100,SYSLIB1101 - + $(Features);InterceptorsPreview true diff --git a/src/libraries/Microsoft.Extensions.Logging.Configuration/src/Microsoft.Extensions.Logging.Configuration.csproj b/src/libraries/Microsoft.Extensions.Logging.Configuration/src/Microsoft.Extensions.Logging.Configuration.csproj index 545e2867e4de6..c0074144e78d9 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Configuration/src/Microsoft.Extensions.Logging.Configuration.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Configuration/src/Microsoft.Extensions.Logging.Configuration.csproj @@ -3,6 +3,7 @@ $(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true + $(Features);InterceptorsPreview true true Configuration support for Microsoft.Extensions.Logging. diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj b/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj index abc5c9d9792ee..0dceab438f82f 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj @@ -7,7 +7,9 @@ $(DefineConstants);NO_SUPPRESS_GC_TRANSITION true true + $(Features);InterceptorsPreview true + true Console logger provider implementation for Microsoft.Extensions.Logging. diff --git a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/tests/SourceGenerationTests/Microsoft.Extensions.Options.ConfigurationExtensions.SourceGeneration.Tests.csproj b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/tests/SourceGenerationTests/Microsoft.Extensions.Options.ConfigurationExtensions.SourceGeneration.Tests.csproj index f1843ebff94a2..0676fddc28982 100644 --- a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/tests/SourceGenerationTests/Microsoft.Extensions.Options.ConfigurationExtensions.SourceGeneration.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/tests/SourceGenerationTests/Microsoft.Extensions.Options.ConfigurationExtensions.SourceGeneration.Tests.csproj @@ -2,9 +2,9 @@ enable $(NetCoreAppCurrent);$(NetFrameworkMinimum) - true $(DefineConstants);BUILDING_SOURCE_GENERATOR_TESTS;ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER - + $(Features);InterceptorsPreview + true true