From 4f6f853497f4b7976b08adbd3c102524b406e9e8 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Thu, 20 Jul 2023 10:18:31 -0700 Subject: [PATCH 01/11] Make config binding gen incremental --- .../src/SourceGenerators/DiagnosticInfo.cs | 62 ++++++++++++++ .../ImmutableEquatableArray.cs | 84 +++++++++++++++++++ .../SourceGenerators/SourceWriterTests.cs | 2 +- .../ConfigurationBindingGenerator.Emitter.cs | 8 +- .../ConfigurationBindingGenerator.Parser.cs | 66 ++++++++++++++- .../gen/ConfigurationBindingGenerator.cs | 38 ++++----- .../gen/Emitter/ConfigurationBinder.cs | 2 +- .../gen/Emitter/CoreBindingHelpers.cs | 81 ++++++++---------- .../gen/Emitter/ExceptionMessages.cs | 2 +- .../gen/Emitter/Helpers.cs | 3 +- ...nfiguration.Binder.SourceGeneration.csproj | 12 +-- .../gen/Parser/BinderInvocation.cs | 4 +- .../gen/Parser/ConfigurationBinder.cs | 13 +-- ...iagnostics.cs => DiagnosticDescriptors.cs} | 2 +- ...onfigurationServiceCollectionExtensions.cs | 2 + .../gen/Specs/Members/MemberSpec.cs | 4 +- .../gen/Specs/MethodsToGen.cs | 4 +- .../gen/Specs/SourceGenerationSpec.cs | 22 ++++- .../gen/Specs/Types/CollectionSpec.cs | 1 + .../gen/Specs/Types/NullableSpec.cs | 8 +- .../gen/Specs/Types/ObjectSpec.cs | 4 +- .../gen/Specs/Types/SimpleTypeSpec.cs | 3 +- .../GeneratorTests.Baselines.cs | 1 + .../SourceGenerationTests/GeneratorTests.cs | 5 +- 24 files changed, 332 insertions(+), 101 deletions(-) create mode 100644 src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs create mode 100644 src/libraries/Common/src/SourceGenerators/ImmutableEquatableArray.cs rename src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/{Diagnostics.cs => DiagnosticDescriptors.cs} (99%) diff --git a/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs b/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs new file mode 100644 index 0000000000000..92c6eb14a7bb7 --- /dev/null +++ b/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs @@ -0,0 +1,62 @@ +// 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.Linq; +using System.Numerics.Hashing; +using Microsoft.CodeAnalysis; + +namespace SourceGenerators; + +/// +/// Descriptor for diagnostic instances using structural equality comparison. +/// Provides a work-around for https://github.com/dotnet/roslyn/issues/68291. +/// +internal readonly struct DiagnosticInfo : IEquatable +{ + public required DiagnosticDescriptor Descriptor { get; init; } + public required object?[] MessageArgs { get; init; } + public required Location? Location { get; init; } + + public Diagnostic CreateDiagnostic() + => Diagnostic.Create(Descriptor, Location, MessageArgs); + + public override readonly bool Equals(object? obj) => obj is DiagnosticInfo info && Equals(info); + + public readonly bool Equals(DiagnosticInfo other) + { + return Descriptor.Equals(other.Descriptor) && + MessageArgs.SequenceEqual(other.MessageArgs) && + Location == other.Location; + } + + public override readonly int GetHashCode() + { + int hashCode = Descriptor.GetHashCode(); + foreach (object? messageArg in MessageArgs) + { + hashCode = HashHelpers.Combine(hashCode, messageArg?.GetHashCode() ?? 0); + } + + hashCode = HashHelpers.Combine(hashCode, Location?.GetHashCode() ?? 0); + return hashCode; + } +} + +internal static class DiagnosticInfoExtensions +{ + public static void Register(this List diagnostics, DiagnosticDescriptor descriptor, Location location, params object?[]? messageArgs) => + diagnostics.Add(new DiagnosticInfo + { + Descriptor = descriptor, + Location = location.GetTrimmedLocation(), + MessageArgs = messageArgs ?? Array.Empty(), + }); + + /// + /// Creates a copy of the Location instance that does not capture a reference to Compilation. + /// + private static Location GetTrimmedLocation(this Location location) + => Location.Create(location.SourceTree?.FilePath ?? "", location.SourceSpan, location.GetLineSpan().Span); +} diff --git a/src/libraries/Common/src/SourceGenerators/ImmutableEquatableArray.cs b/src/libraries/Common/src/SourceGenerators/ImmutableEquatableArray.cs new file mode 100644 index 0000000000000..f1ab196357518 --- /dev/null +++ b/src/libraries/Common/src/SourceGenerators/ImmutableEquatableArray.cs @@ -0,0 +1,84 @@ +// 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 System.Linq; +using System.Numerics.Hashing; + +namespace SourceGenerators +{ + /// + /// Provides an immutable list implementation which implements sequence equality. + /// + public sealed class ImmutableEquatableArray : IEquatable>, IReadOnlyList + where T : IEquatable + { + public static ImmutableEquatableArray Empty { get; } = new ImmutableEquatableArray(Array.Empty()); + + private readonly T[] _values; + public T this[int index] => _values[index]; + public int Count => _values.Length; + + public ImmutableEquatableArray(IEnumerable values) + => _values = values.ToArray(); + + public bool Equals(ImmutableEquatableArray? other) + => other != null && ((ReadOnlySpan)_values).SequenceEqual(other._values); + + public override bool Equals(object? obj) + => obj is ImmutableEquatableArray other && Equals(other); + + public override int GetHashCode() + { + int hash = 0; + foreach (T value in _values) + { + hash = HashHelpers.Combine(hash, value is null ? 0 : value.GetHashCode()); + } + + return hash; + } + + public Enumerator GetEnumerator() => new Enumerator(_values); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_values).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); + + public struct Enumerator + { + private readonly T[] _values; + private int _index; + + internal Enumerator(T[] values) + { + _values = values; + _index = -1; + } + + public bool MoveNext() + { + int newIndex = _index + 1; + + if ((uint)newIndex < (uint)_values.Length) + { + _index = newIndex; + return true; + } + + return false; + } + + public readonly T Current => _values[_index]; + } + } + + internal static class ImmutableEquatableArray + { + public static ImmutableEquatableArray Empty() where T : IEquatable + => ImmutableEquatableArray.Empty; + + public static ImmutableEquatableArray ToImmutableEquatableArray(this IEnumerable values) where T : IEquatable + => new(values); + } +} diff --git a/src/libraries/Common/tests/Tests/SourceGenerators/SourceWriterTests.cs b/src/libraries/Common/tests/Tests/SourceGenerators/SourceWriterTests.cs index ab0e53b07d526..6560d5fede9f0 100644 --- a/src/libraries/Common/tests/Tests/SourceGenerators/SourceWriterTests.cs +++ b/src/libraries/Common/tests/Tests/SourceGenerators/SourceWriterTests.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using SourceGenerators; +namespace DotnetRuntime.SourceGenerators using Xunit; namespace Common.Tests 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 7206d54904114..e6df339f02667 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs @@ -15,13 +15,9 @@ private sealed partial class Emitter private readonly SourceGenerationSpec _sourceGenSpec; private readonly SourceWriter _writer = new(); - public Emitter(SourceProductionContext context, SourceGenerationSpec sourceGenSpec) - { - _context = context; - _sourceGenSpec = sourceGenSpec; - } + public Emitter(SourceGenerationSpec sourceGenSpec) => _sourceGenSpec = sourceGenSpec; - public void Emit() + public void Emit(SourceProductionContext context) { if (!ShouldEmitBindingExtensions()) { 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 2a6f5d2126e8c..7397a9e824ba8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -64,7 +64,69 @@ public Parser(SourceProductionContext context, KnownTypeSymbols typeSymbols, Imm } } - return _sourceGenSpec; + return CreateIncrementalGenerationSpec(); + } + + private SourceGenerationSpec CreateIncrementalGenerationSpec() + { + Debug.Assert(_typesForGen_ConfigurationBinder_BindMethods.Count <= 3); + + return new SourceGenerationSpec + { + // Type index. + TypeNamespaces = _typeNamespaces.ToImmutableEquatableArray(), + // TODO: add ImmutableEquatableDictionary to give the emitter (via the generation spec). + // https://github.com/dotnet/runtime/issues/89318 + TypeList = GetTypesForGen(_typeIndex.Values), + + // ConfigurationBinder invocation info. + MethodsToGen_ConfigurationBinder = _methodsToGen_ConfigurationBinder, + TypesForGen_ConfigurationBinder_Bind_instance = GetTypesForGen_ConfigurationBinder_Bind(MethodsToGen_ConfigurationBinder.Bind_instance), + TypesForGen_ConfigurationBinder_Bind_instance_BinderOptions = GetTypesForGen_ConfigurationBinder_Bind(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions), + TypesForGen_ConfigurationBinder_Bind_key_instance = GetTypesForGen_ConfigurationBinder_Bind(MethodsToGen_ConfigurationBinder.Bind_key_instance), + + // OptionsBuilderExt and ServiceCollection invocation info. + MethodsToGen_OptionsBuilderExt = _methodsToGen_OptionsBuilderExt, + MethodsToGen_ServiceCollectionExt = _methodsToGen_ServiceCollectionExt, + + // Core binding helper emit info. + GraphContainsEnum = _graphContainsEnum, + MethodsToGen_CoreBindingHelper = _methodsToGen_CoreBindingHelper, + TypesForGen_CoreBindingHelper_GetCore = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.GetCore), + TypesForGen_CoreBindingHelper_BindCoreUntyped = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.BindCoreUntyped), + TypesForGen_CoreBindingHelper_GetValueCore = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.GetValueCore), + TypesForGen_CoreBindingHelper_BindCore = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.BindCore), + TypesForGen_CoreBindingHelper_Initialize = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.Initialize), + TypesForGen_CoreBindingHelper_ParsePrimitive = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.ParsePrimitive), + }; + + ImmutableEquatableArray GetTypesForGen_ConfigurationBinder_Bind(MethodsToGen_ConfigurationBinder overload) + { + Debug.Assert(overload is MethodsToGen_ConfigurationBinder.Bind_instance or MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions or MethodsToGen_ConfigurationBinder.Bind_key_instance); + _typesForGen_ConfigurationBinder_BindMethods.TryGetValue(overload, out HashSet? types); + return types is null ? ImmutableEquatableArray.Empty : GetTypesForGen(types); + } + + ImmutableEquatableArray GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper overload) + where TSpec : TypeSpec, IEquatable + { + _typesForGen_CoreBindingHelper.TryGetValue(overload, out HashSet? typesAsBase); + + if (typesAsBase is null) + { + return ImmutableEquatableArray.Empty; + } + + IEnumerable types = typeof(TSpec) == typeof(TypeSpec) + ? (HashSet)(object)typesAsBase + : typesAsBase.Select(t => (TSpec)t); + + return GetTypesForGen(types); + } + + static ImmutableEquatableArray GetTypesForGen(IEnumerable types) + where TSpec : TypeSpec, IEquatable => + types.OrderBy(t => t.TypeRef.FullyQualifiedDisplayString).ToImmutableEquatableArray(); } private bool IsValidRootConfigType(ITypeSymbol? type) @@ -229,7 +291,7 @@ private void Register_AsConfigWithChildren_HelperForGen_IfRequired(TypeSpec poss /// which is handled by /// private void RegisterInterceptor(Enum method, IInvocationOperation operation) => - _sourceGenSpec.InterceptionInfo.RegisterCacheEntry(method, new InterceptorLocationInfo(operation)); + _interceptionInfo.RegisterCacheEntry(method, new InterceptorLocationInfo(operation)); private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs index fbca2dd3cfc50..f2300a2c3d2fc 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -//#define LAUNCH_DEBUGGER using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -30,35 +29,36 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ? new CompilationData((CSharpCompilation)compilation) : null); - IncrementalValuesProvider inputCalls = context.SyntaxProvider + IncrementalValueProvider<(SourceGenerationSpec?, ImmutableEquatableArray)> genSpec = context.SyntaxProvider .CreateSyntaxProvider( (node, _) => BinderInvocation.IsCandidateSyntaxNode(node), BinderInvocation.Create) - .Where(invocation => invocation is not null); - - IncrementalValueProvider<(CompilationData?, ImmutableArray)> inputData = compilationData.Combine(inputCalls.Collect()); + .Where(invocation => invocation is not null) + .Collect() + .Combine(compilationData) + .Select((tuple, cancellationToken) => + { + Parser parser = new(tuple.Right); + SourceGenerationSpec spec = parser.ParseSourceGenerationSpec(tuple.Left, cancellationToken); + ImmutableEquatableArray diagnostics = parser.Diagnostics.ToImmutableEquatableArray(); + return (spec, diagnostics); + }) + .WithTrackingName(nameof(SourceGenerationSpec)); - context.RegisterSourceOutput(inputData, (spc, source) => Execute(source.Item1, source.Item2, spc)); + context.RegisterSourceOutput(genSpec, ReportDiagnosticsAndEmitSource); } - private static void Execute(CompilationData compilationData, ImmutableArray inputCalls, SourceProductionContext context) + private static void ReportDiagnosticsAndEmitSource(SourceProductionContext sourceProductionContext, (SourceGenerationSpec? SourceGenerationSpec, ImmutableEquatableArray Diagnostics) input) { - if (inputCalls.IsDefaultOrEmpty) - { - return; - } - - if (compilationData?.LanguageVersionIsSupported is not true) + foreach (DiagnosticInfo diagnostic in input.Diagnostics) { - context.ReportDiagnostic(Diagnostic.Create(Parser.Diagnostics.LanguageVersionNotSupported, location: null)); - return; + sourceProductionContext.ReportDiagnostic(diagnostic.CreateDiagnostic()); } - Parser parser = new(context, compilationData.TypeSymbols!, inputCalls); - if (parser.GetSourceGenerationSpec() is SourceGenerationSpec spec) + if (input.SourceGenerationSpec is SourceGenerationSpec spec) { - Emitter emitter = new(context, spec); - emitter.Emit(); + Emitter emitter = new(spec); + emitter.Emit(sourceProductionContext); } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs index f1c7d5f7ff215..7d548e384da18 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs @@ -132,7 +132,7 @@ private void EmitBindMethods_ConfigurationBinder() void EmitMethods(MethodsToGen_ConfigurationBinder method, string additionalParams, string configExpression, bool configureOptions) { - foreach ((ComplexTypeSpec type, List interceptorInfoList) in _sourceGenSpec.InterceptionInfo_ConfigBinder.GetOverloadInfo(method)) + foreach ((ComplexTypeSpec type, List interceptorInfoList) in _interceptionInfo_ConfigBinder.GetOverloadInfo(method)) { EmitBlankLineIfRequired(); _writer.WriteLine($"/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively."); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs index 90531efe1b0c1..e103abc2cb10e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; +using DotnetRuntime.SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -51,7 +52,7 @@ private void EmitConfigurationKeyCaches() } HashSet keys = new(objectType.ConstructorParameters.Select(m => GetCacheElement(m))); - keys.UnionWith(objectType.Properties.Values.Select(m => GetCacheElement(m))); + keys.UnionWith(objectType.Properties.Select(m => GetCacheElement(m))); static string GetCacheElement(MemberSpec member) => $@"""{member.ConfigurationKeyName}"""; string configKeysSource = string.Join(", ", keys); @@ -62,7 +63,7 @@ private void EmitConfigurationKeyCaches() private void EmitGetCoreMethod() { - if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.GetCore, out HashSet? types)) + if (_sourceGenSpec.TypesForGen_CoreBindingHelper_GetCore.Count is 0) { return; } @@ -80,7 +81,8 @@ private void EmitGetCoreMethod() bool isFirstType = true; foreach (TypeSpec type in types) { - TypeSpec effectiveType = type.EffectiveType; + TypeRef effectiveTypeRef = type.EffectiveTypeRef; + TypeSpec effectiveType = GetTypeSpec(effectiveTypeRef); TypeSpecKind kind = effectiveType.SpecKind; string conditionKindExpr = GetConditionKindExpr(ref isFirstType); @@ -141,7 +143,7 @@ void EmitCastToIConfigurationSection() => private void EmitGetValueCoreMethod() { - if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.GetValueCore, out HashSet? targetTypes)) + if (_sourceGenSpec.TypesForGen_CoreBindingHelper_GetValueCore.Count is 0) { return; } @@ -221,7 +223,7 @@ private void EmitBindCoreMainMethod() private void EmitBindCoreMethods() { - if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.BindCore, out HashSet? targetTypes)) + foreach (TypeWithChildrenSpec type in _sourceGenSpec.TypesForGen_CoreBindingHelper_BindCore) { return; } @@ -244,21 +246,20 @@ private void EmitBindCoreMethod(ComplexTypeSpec type) { if (effectiveType.InstantiationStrategy is InstantiationStrategy.Array) { - Debug.Assert(type == effectiveType); - EmitPopulationImplForArray((EnumerableSpec)type); + EmitPopulateImplForArray(enumerableType.ElementTypeRef); } else { - EmitPopulationImplForEnumerableWithAdd(enumerable); + EmitPopulateImplForEnumerableWithAdd(enumerableType); } } - else if (effectiveType is DictionarySpec dictionary) + else if (type is DictionarySpec dictionary) { EmitBindCoreImplForDictionary(dictionary); } else { - EmitBindCoreImplForObject((ObjectSpec)effectiveType); + EmitBindCoreImplForObject((ObjectSpec)type); } EmitEndBlock(); @@ -266,12 +267,7 @@ private void EmitBindCoreMethod(ComplexTypeSpec type) private void EmitInitializeMethods() { - if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.Initialize, out HashSet? targetTypes)) - { - return; - } - - foreach (ObjectSpec type in targetTypes) + foreach (ObjectSpec type in _sourceGenSpec.TypesForGen_CoreBindingHelper_Initialize) { EmitBlankLineIfRequired(); EmitInitializeMethod(type); @@ -289,7 +285,7 @@ private void EmitInitializeMethod(ObjectSpec type) EmitStartBlock($"public static {type.DisplayString} {GetInitalizeMethodDisplayString(type)}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); _emitBlankLineBeforeNextStatement = false; - foreach (ParameterSpec parameter in ctorParams) + foreach (ParameterSpec parameter in type.ConstructorParameters) { string name = parameter.Name; string argExpr = parameter.RefKind switch @@ -433,24 +429,16 @@ private void EmitHelperMethods() EmitGetBinderOptionsHelper(); } - bool enumTypeExists = false; - - foreach (ParsableFromStringSpec type in _sourceGenSpec.PrimitivesForHelperGen) + if (_sourceGenSpec.GraphContainsEnum) { EmitBlankLineIfRequired(); + EmitEnumParseMethod(); + } - if (type.StringParsableTypeKind == StringParsableTypeKind.Enum) - { - if (!enumTypeExists) - { - EmitEnumParseMethod(); - enumTypeExists = true; - } - } - else - { - EmitPrimitiveParseMethod(type); - } + foreach (ParsableFromStringSpec type in _sourceGenSpec.TypesForGen_CoreBindingHelper_ParsePrimitive) + { + EmitBlankLineIfRequired(); + EmitPrimitiveParseMethod(type); } } @@ -615,7 +603,7 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.getPath}()}}", $"{{typeof({typeDisplayString})}}"); - EmitStartBlock($"public static {typeDisplayString} {type.ParseMethodName}(string {Identifier.value}, Func {Identifier.getPath})"); + EmitStartBlock($"public static {typeDisplayString} {type.ToParseMethodName()}(string {Identifier.value}, Func {Identifier.getPath})"); EmitEndBlock($$""" try { @@ -628,7 +616,7 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) """); } - private void EmitPopulationImplForArray(EnumerableSpec type) + private void EmitPopulateImplForArray(TypeRef elementTypeRef) { EnumerableSpec typeToInstantiate = (EnumerableSpec)type.TypeToInstantiate; @@ -637,6 +625,7 @@ private void EmitPopulationImplForArray(EnumerableSpec type) EmitBindingLogic(typeToInstantiate, tempIdentifier, Identifier.configuration, InitializationKind.Declaration, ValueDefaulting.None); // Resize array and add binded elements. + _writer.WriteLine(); _writer.WriteLine($$""" {{Identifier.Int32}} {{Identifier.originalCount}} = {{Identifier.instance}}.{{Identifier.Length}}; {{Identifier.Array}}.{{Identifier.Resize}}(ref {{Identifier.instance}}, {{Identifier.originalCount}} + {{tempIdentifier}}.{{Identifier.Count}}); @@ -644,10 +633,12 @@ private void EmitPopulationImplForArray(EnumerableSpec type) """); } - private void EmitPopulationImplForEnumerableWithAdd(EnumerableSpec type) + private void EmitPopulateImplForEnumerableWithAdd(EnumerableSpec type) { EmitCollectionCastIfRequired(type, out string instanceIdentifier); + private void EmitPopulateImplForEnumerableWithAddCore(string bindTargetIdentifier, TypeRef elementTypeRef) + { Emit_Foreach_Section_In_ConfigChildren_StartBlock(); string addExpr = $"{instanceIdentifier}.{Identifier.Add}"; @@ -688,8 +679,8 @@ private void EmitBindCoreImplForDictionary(DictionarySpec type) Emit_Foreach_Section_In_ConfigChildren_StartBlock(); - ParsableFromStringSpec keyType = type.KeyType; - TypeSpec elementType = type.ElementType; + ParsableFromStringSpec effectiveKeyType = (ParsableFromStringSpec)GetEffectiveTypeSpec(type.KeyTypeRef); + TypeSpec effectiveElementType = GetEffectiveTypeSpec(type.ElementTypeRef); // Parse key EmitBindingLogic( @@ -764,11 +755,12 @@ private void EmitBindCoreImplForObject(ObjectSpec type) { Debug.Assert(type.HasBindableMembers); + string displayString = type.TypeRef.MinimalDisplayString; string keyCacheFieldName = GetConfigKeyCacheFieldName(type); string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.DisplayString}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});"; _writer.WriteLine(validateMethodCallExpr); - foreach (PropertySpec property in type.Properties.Values) + foreach (PropertySpec property in type.Properties) { bool noSetter_And_IsReadonly = !property.CanSet && property.Type is CollectionSpec { InstantiationStrategy: InstantiationStrategy.ParameterizedConstructor }; if (property.ShouldBindTo && !noSetter_And_IsReadonly) @@ -784,6 +776,10 @@ private void EmitBindCoreImplForObject(ObjectSpec type) } } + /// + /// Emits binding logic for an object property of ctor parameter. + /// + /// True if no exception was thrown, otherwise, false. private bool EmitBindImplForMember( MemberSpec member, string memberAccessExpr, @@ -861,7 +857,7 @@ private void EmitBindingLogicForComplexMember( InitializationKind initKind; string targetObjAccessExpr; - if (effectiveMemberType.IsValueType) + if (effectiveMemberType.TypeRef.IsValueType) { if (!canSet) { @@ -888,15 +884,10 @@ private void EmitBindingLogicForComplexMember( targetObjAccessExpr = tempIdentifier; } - else if (member.CanGet) - { - targetObjAccessExpr = memberAccessExpr; - initKind = InitializationKind.AssignmentWithNullCheck; - } else { targetObjAccessExpr = memberAccessExpr; - initKind = InitializationKind.SimpleAssignment; + initKind = canGet ? InitializationKind.AssignmentWithNullCheck : InitializationKind.SimpleAssignment; } Action? writeOnSuccess = !canSet diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs index 696af27d69737..788a15d94e5ac 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs @@ -18,7 +18,7 @@ internal static class ExceptionMessages public const string MissingPublicInstanceConstructor = "Cannot create instance of type '{0}' because it is missing a public instance constructor."; public const string MultipleParameterizedConstructors = "Cannot create instance of type '{0}' because it has multiple public parameterized constructors."; public const string ParameterHasNoMatchingConfig = "Cannot create instance of type '{0}' because parameter '{1}' has no matching config. Each parameter in the constructor that does not have a default value must have a corresponding config entry."; - public const string TypeNotDetectedAsInput = "Unable to bind to type '{0}': generator did not detect the type as input."; + public const string TypeNotSupportedAsInput = "Unable to bind to type '{0}': generator did not detect the type as input, or the type is not supported for binding."; } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs index a7db2fb516397..1879e06276149 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -145,7 +146,7 @@ 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); + bool interceptsCalls = _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); 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 de629b1a641ac..ec9cd28569443 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 @@ -14,14 +14,16 @@ - - - - + + + + + + @@ -36,8 +38,8 @@ - + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/BinderInvocation.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/BinderInvocation.cs index ad7c4c09204d4..cb978b56091b7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/BinderInvocation.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/BinderInvocation.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; @@ -85,4 +85,4 @@ private static bool IsCandidateMethodName_OptionsBuilderConfigurationExtensions( private static bool IsValidMethodName_OptionsConfigurationServiceCollectionExtensions(string name) => name is nameof(MethodsToGen_Extensions_ServiceCollection.Configure); } -} +} \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs index 3996142adf908..895f79b170225 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis; +using DotnetRuntime.SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -87,13 +89,13 @@ private void ParseBindInvocation_ConfigurationBinder(BinderInvocation invocation if (!IsValidRootConfigType(type)) { - _context.ReportDiagnostic(Diagnostic.Create(Diagnostics.CouldNotDetermineTypeInfo, invocation.Location)); + Diagnostics.Register(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); return; } if (type!.IsValueType) { - _context.ReportDiagnostic(Diagnostic.Create(Diagnostics.ValueTypesInvalidForBind, invocation.Location, type)); + Diagnostics.Register(DiagnosticDescriptors.InvalidBindInput, invocation.Location, type); return; } @@ -248,12 +250,11 @@ private void ParseGetValueInvocation(BinderInvocation invocation) if (!IsValidRootConfigType(type)) { - _context.ReportDiagnostic(Diagnostic.Create(Diagnostics.CouldNotDetermineTypeInfo, invocation.Location)); + Diagnostics.Register(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); return; } - if (IsParsableFromString(effectiveType, out _) && - GetTargetTypeForRootInvocationCore(type, invocation.Location) is TypeSpec typeSpec) + if (IsParsableFromString(effectiveType, out _)) { RegisterInvocation(overload, invocation.Operation); RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, typeSpec); @@ -274,7 +275,7 @@ private void RegisterInvocation(MethodsToGen_ConfigurationBinder overload, IInvo private void RegisterInterceptor(MethodsToGen_ConfigurationBinder overload, TypeSpec typeSpec, IInvocationOperation operation) { _sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; - _sourceGenSpec.InterceptionInfo_ConfigBinder.RegisterOverloadInfo(overload, typeSpec, operation); + _interceptionInfo_ConfigBinder.RegisterOverloadInfo(overload, typeSpec, operation); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Diagnostics.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs similarity index 99% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Diagnostics.cs rename to src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs index d6d816545bcd0..f2e61930efa5f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Diagnostics.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs @@ -11,7 +11,7 @@ public sealed partial class ConfigurationBindingGenerator { private sealed partial class Parser { - internal static class Diagnostics + private static class DiagnosticDescriptors { public static DiagnosticDescriptor TypeNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.TypeNotSupported)); public static DiagnosticDescriptor MissingPublicInstanceConstructor { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.MissingPublicInstanceConstructor)); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs index e86231f32e42a..11b81c6b6c801 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs @@ -1,10 +1,12 @@ // 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.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; +using DotnetRuntime.SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs index effd550482595..0e85b0b8bd138 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs @@ -3,9 +3,11 @@ using System.Diagnostics; using Microsoft.CodeAnalysis; +using DotnetRuntime.SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { + [DebuggerDisplay("Name={Name}, Type={TypeRef.Name}")] internal abstract record MemberSpec { public MemberSpec(ISymbol member) @@ -18,7 +20,7 @@ public MemberSpec(ISymbol member) public string Name { get; } public string DefaultValueExpr { get; protected set; } - public required TypeSpec Type { get; init; } + public required TypeRef TypeRef { get; init; } public required string ConfigurationKeyName { get; init; } public abstract bool CanGet { get; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs index 6165a3e6d46dc..197264f13d2d8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs @@ -6,7 +6,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { [Flags] - public enum MethodsToGen_CoreBindingHelper + internal enum MethodsToGen_CoreBindingHelper { None = 0x0, BindCore = 0x1, @@ -121,7 +121,7 @@ internal enum MethodsToGen_Extensions_OptionsBuilder /// Methods on Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions /// [Flags] - public enum MethodsToGen_Extensions_ServiceCollection + internal enum MethodsToGen_Extensions_ServiceCollection { None = 0x0, diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs index 760d57b1dcc88..6f594a55f623d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs @@ -1,8 +1,9 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -27,5 +28,24 @@ internal sealed record SourceGenerationSpec public MethodsToGen_ConfigurationBinder MethodsToGen_ConfigurationBinder { get; set; } public MethodsToGen_Extensions_OptionsBuilder MethodsToGen_OptionsBuilderExt { get; set; } public MethodsToGen_Extensions_ServiceCollection MethodsToGen_ServiceCollectionExt { get; set; } + + public required ImmutableEquatableArray TypeNamespaces { get; init; } + public required ImmutableEquatableArray TypeList { get; init; } + + public required ImmutableEquatableArray TypesForGen_ConfigurationBinder_Bind_instance { get; init; } + public required ImmutableEquatableArray TypesForGen_ConfigurationBinder_Bind_instance_BinderOptions { get; init; } + public required ImmutableEquatableArray TypesForGen_ConfigurationBinder_Bind_key_instance { get; init; } + + public required bool GraphContainsEnum { get; init; } + public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_BindCoreUntyped { get; init; } + public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_GetCore { get; init; } + public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_GetValueCore { get; init; } + public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_BindCore { get; init; } + public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_Initialize { get; init; } + public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_ParsePrimitive { get; init; } + + // TODO: add ImmutableEquatableDictionary to be supplied by the parser. + // https://github.com/dotnet/runtime/issues/89318 + public Dictionary GetTypeIndex() => TypeList.ToDictionary(t => t.TypeRef); } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs index f565d245cc550..353c7b7f1640d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.CodeAnalysis; +using DotnetRuntime.SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs index 3de6d7d465ad9..865fc51f47837 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs @@ -2,12 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.CodeAnalysis; +using DotnetRuntime.SourceGenerators; +using System; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { internal sealed record NullableSpec : TypeSpec { - private readonly TypeSpec _underlyingType; + private readonly TypeRef _underlyingTypeRef; public NullableSpec(ITypeSymbol type, TypeSpec underlyingType) : base(type) => _underlyingType = underlyingType; @@ -17,6 +19,8 @@ internal sealed record NullableSpec : TypeSpec public override TypeSpecKind SpecKind => TypeSpecKind.Nullable; - public override TypeSpec EffectiveType => _underlyingType; + public override TypeRef EffectiveTypeRef => _underlyingTypeRef; + + public override bool SkipBinding => false; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs index f6978fa9cf470..512acbd80228b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs @@ -18,9 +18,9 @@ public ObjectSpec(INamedTypeSymbol type) : base(type) { } public override bool CanInstantiate => InstantiationStrategy is not InstantiationStrategy.None && InitExceptionMessage is null; - public Dictionary Properties { get; } = new(StringComparer.OrdinalIgnoreCase); + public ImmutableEquatableArray Properties { get; set; } - public List ConstructorParameters { get; } = new(); + public ImmutableEquatableArray ConstructorParameters { get; set; } public string? InitExceptionMessage { get; set; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs index 2dfe08dc5f547..dd785c406720d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using Microsoft.CodeAnalysis; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -28,6 +27,8 @@ internal sealed record ParsableFromStringSpec : SimpleTypeSpec { public ParsableFromStringSpec(ITypeSymbol type) : base(type) { } + public override bool SkipBinding => false; + public override TypeSpecKind SpecKind => TypeSpecKind.ParsableFromString; public required StringParsableTypeKind StringParsableTypeKind { get; init; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs index 3c46f5f99818b..2840000bb0812 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs @@ -719,6 +719,7 @@ public class MyClass } [Fact] + [ActiveIssue("Work out why we aren't getting all the expected diagnostics.")] public async Task Collections() { string source = """ diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs index 846e64d904d53..d2ca1f8bb8f23 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs @@ -81,8 +81,8 @@ public record struct MyRecordStruct { } foreach (Diagnostic diagnostic in d) { - Assert.True(diagnostic.Id == Diagnostics.ValueTypesInvalidForBind.Id); - Assert.Contains(Diagnostics.ValueTypesInvalidForBind.Title, diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); + Assert.True(diagnostic.Id == Diagnostics.InvalidBindInput.Id); + Assert.Contains(Diagnostics.InvalidBindInput.Title, diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); Assert.NotNull(diagnostic.Location); } @@ -234,6 +234,7 @@ async Task Test(bool expectOutput) } [Fact] + [ActiveIssue("Work out why we aren't getting all the expected diagnostics.")] public async Task IssueDiagnosticsForAllOffendingCallsites() { string source = """ From 0859bec98f859e831e96cabe47abd1b85a52fbd1 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Mon, 11 Sep 2023 10:23:57 -0700 Subject: [PATCH 02/11] Iterate on implementation --- .../src/Roslyn/DiagnosticDescriptorHelper.cs | 8 +- .../src/SourceGenerators/DiagnosticInfo.cs | 18 - .../SourceGenerators/SourceWriterTests.cs | 2 +- .../ConfigurationBindingGenerator.Emitter.cs | 17 +- .../ConfigurationBindingGenerator.Parser.cs | 375 ++++++++---------- .../gen/ConfigurationBindingGenerator.cs | 35 +- .../gen/Emitter/ConfigurationBinder.cs | 64 +-- .../gen/Emitter/CoreBindingHelpers.cs | 143 ++++--- .../gen/Emitter/ExceptionMessages.cs | 2 +- .../gen/Emitter/Helpers.cs | 21 +- .../OptionsBuilderConfigurationExtensions.cs | 18 +- ...onfigurationServiceCollectionExtensions.cs | 20 +- ...nfiguration.Binder.SourceGeneration.csproj | 8 +- .../gen/Parser/BinderInvocation.cs | 33 +- .../gen/Parser/ConfigurationBinder.cs | 92 ++--- .../gen/Parser/DiagnosticDescriptors.cs | 2 +- .../gen/Parser/Extensions.cs | 20 +- .../gen/{Specs => Parser}/KnownTypeSymbols.cs | 9 +- .../OptionsBuilderConfigurationExtensions.cs | 27 +- ...onfigurationServiceCollectionExtensions.cs | 27 +- .../gen/Specs/BindingHelperInfo.cs | 146 +++++++ .../gen/Specs/InterceptorInfo.cs | 197 +++++++++ .../gen/Specs/InterceptorLocationInfo.cs | 89 ----- .../gen/Specs/Members/MemberSpec.cs | 4 +- .../gen/Specs/Members/ParameterSpec.cs | 2 +- .../gen/Specs/MethodsToGen.cs | 79 ++-- .../gen/Specs/SourceGenerationSpec.cs | 51 --- .../gen/Specs/Types/CollectionSpec.cs | 3 +- .../gen/Specs/Types/ComplexTypeSpec.cs | 2 +- .../gen/Specs/Types/NullableSpec.cs | 8 +- .../gen/Specs/Types/ObjectSpec.cs | 9 +- .../gen/Specs/Types/SimpleTypeSpec.cs | 3 +- .../gen/Specs/Types/TypeSpec.cs | 13 +- .../tests/Common/ConfigurationBinderTests.cs | 14 + .../ConfigurationBinder/Bind.generated.txt | 60 +-- .../Bind_Instance.generated.txt | 60 +-- .../Bind_Instance_BinderOptions.generated.txt | 60 +-- .../Bind_Key_Instance.generated.txt | 60 +-- .../ConfigurationBinder/Get.generated.txt | 68 ++-- .../GetValue.generated.txt | 30 +- .../ConfigurationBinder/Get_T.generated.txt | 68 ++-- .../Get_T_BinderOptions.generated.txt | 68 ++-- .../BindConfiguration.generated.txt | 26 +- .../OptionsBuilder/Bind_T.generated.txt | 26 +- .../Bind_T_BinderOptions.generated.txt | 26 +- .../Baselines/Primitives.generated.txt | 336 ++++++++-------- .../Configure_T.generated.txt | 76 ++-- .../Configure_T_BinderOptions.generated.txt | 76 ++-- .../Configure_T_name.generated.txt | 76 ++-- ...nfigure_T_name_BinderOptions.generated.txt | 76 ++-- .../GeneratorTests.Helpers.cs | 4 +- .../SourceGenerationTests/GeneratorTests.cs | 4 +- .../gen/Helpers/RoslynExtensions.cs | 6 - .../gen/JsonSourceGenerator.Parser.cs | 1 + 54 files changed, 1465 insertions(+), 1303 deletions(-) rename src/libraries/Microsoft.Extensions.Configuration.Binder/gen/{Specs => Parser}/KnownTypeSymbols.cs (96%) create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs delete mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorLocationInfo.cs delete mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs diff --git a/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs b/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs index 8c5783ff9bd0b..f684ae76038df 100644 --- a/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs +++ b/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.CodeAnalysis; - namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions { internal static partial class DiagnosticDescriptorHelper @@ -21,5 +19,11 @@ public static DiagnosticDescriptor Create( return new DiagnosticDescriptor(id, title, messageFormat, category, defaultSeverity, isEnabledByDefault, description, helpLink, customTags); } + + /// + /// Creates a copy of the Location instance that does not capture a reference to Compilation. + /// + public static Location GetTrimmedLocation(this Location location) + => Location.Create(location.SourceTree?.FilePath ?? "", location.SourceSpan, location.GetLineSpan().Span); } } diff --git a/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs b/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs index 92c6eb14a7bb7..de8e5e748d1b3 100644 --- a/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs +++ b/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Linq; using System.Numerics.Hashing; using Microsoft.CodeAnalysis; @@ -43,20 +42,3 @@ public override readonly int GetHashCode() return hashCode; } } - -internal static class DiagnosticInfoExtensions -{ - public static void Register(this List diagnostics, DiagnosticDescriptor descriptor, Location location, params object?[]? messageArgs) => - diagnostics.Add(new DiagnosticInfo - { - Descriptor = descriptor, - Location = location.GetTrimmedLocation(), - MessageArgs = messageArgs ?? Array.Empty(), - }); - - /// - /// Creates a copy of the Location instance that does not capture a reference to Compilation. - /// - private static Location GetTrimmedLocation(this Location location) - => Location.Create(location.SourceTree?.FilePath ?? "", location.SourceSpan, location.GetLineSpan().Span); -} diff --git a/src/libraries/Common/tests/Tests/SourceGenerators/SourceWriterTests.cs b/src/libraries/Common/tests/Tests/SourceGenerators/SourceWriterTests.cs index 6560d5fede9f0..ab0e53b07d526 100644 --- a/src/libraries/Common/tests/Tests/SourceGenerators/SourceWriterTests.cs +++ b/src/libraries/Common/tests/Tests/SourceGenerators/SourceWriterTests.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace DotnetRuntime.SourceGenerators +using SourceGenerators; using Xunit; namespace Common.Tests 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 e6df339f02667..f40542a6d4d4a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs @@ -1,7 +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.Collections.Immutable; +using System; +using System.Diagnostics; using Microsoft.CodeAnalysis; using SourceGenerators; @@ -11,15 +12,17 @@ public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerato { private sealed partial class Emitter { - private readonly SourceProductionContext _context; - private readonly SourceGenerationSpec _sourceGenSpec; + private readonly InterceptorInfo _interceptorInfo; + private readonly BindingHelperInfo _bindingHelperInfo; + private readonly SourceWriter _writer = new(); - public Emitter(SourceGenerationSpec sourceGenSpec) => _sourceGenSpec = sourceGenSpec; + public Emitter(SourceGenerationSpec sourceGenSpec) => + (_interceptorInfo, _bindingHelperInfo) = (sourceGenSpec.InterceptorInfo, sourceGenSpec.BindingHelperInfo); public void Emit(SourceProductionContext context) { - if (!ShouldEmitBindingExtensions()) + if (!ShouldEmitMethods(MethodsToGen.Any)) { return; } @@ -48,7 +51,7 @@ file static class {{Identifier.BindingExtensions}} EmitEndBlock(); // Binding namespace. - _context.AddSource($"{Identifier.BindingExtensions}.g.cs", _writer.ToSourceText()); + context.AddSource($"{Identifier.BindingExtensions}.g.cs", _writer.ToSourceText()); } private void EmitInterceptsLocationAttrDecl() @@ -75,7 +78,7 @@ public InterceptsLocationAttribute(string filePath, int line, int column) private void EmitUsingStatements() { - foreach (string @namespace in _sourceGenSpec.Namespaces.ToImmutableSortedSet()) + foreach (string @namespace in _bindingHelperInfo.Namespaces) { _writer.WriteLine($"using {@namespace};"); } 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 7397a9e824ba8..a1d45001baf05 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -8,44 +8,61 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerator { - private sealed partial class Parser + internal sealed partial class Parser { - private record struct InvocationDiagnosticInfo(DiagnosticDescriptor Descriptor, object[]? MessageArgs); + private readonly struct InvocationDiagnosticInfo + { + public InvocationDiagnosticInfo(DiagnosticDescriptor descriptor, object[]? messageArgs) => + (Descriptor, MessageArgs) = (descriptor, messageArgs); + + public DiagnosticDescriptor Descriptor { get; } + public object[]? MessageArgs { get; } + } - private readonly SourceProductionContext _context; - private readonly SourceGenerationSpec _sourceGenSpec = new(); private readonly KnownTypeSymbols _typeSymbols; - private readonly ImmutableArray _invocations; + private readonly bool _langVersionIsSupported; private readonly Dictionary _createdSpecs = new(SymbolEqualityComparer.Default); private readonly HashSet _unsupportedTypes = new(SymbolEqualityComparer.Default); - private readonly List _invocationTargetTypeDiags = new(); + // Data for incremental source generation spec. + private readonly BindingHelperInfo.Builder _helperInfoBuilder = new(); + private readonly InterceptorInfo.Builder _interceptorInfoBuilder = new(); + private readonly Dictionary> _typeDiagnostics = new(SymbolEqualityComparer.Default); + private readonly List _invocationTargetTypeDiags = new(); - public Parser(SourceProductionContext context, KnownTypeSymbols typeSymbols, ImmutableArray invocations) + public Parser(CompilationData compilationData) { - _context = context; - _typeSymbols = typeSymbols; - _invocations = invocations; + _typeSymbols = compilationData.TypeSymbols!; + _langVersionIsSupported = compilationData.LanguageVersionIsSupported; } - public SourceGenerationSpec? GetSourceGenerationSpec() + public List? Diagnostics { get; private set; } + + public SourceGenerationSpec? GetSourceGenerationSpec(ImmutableArray invocations) { + if (!_langVersionIsSupported) + { + ReportDiagnostic(DiagnosticDescriptors.LanguageVersionNotSupported, location: Location.None); + return null; + } + if (_typeSymbols is not { IConfiguration: { }, ConfigurationBinder: { } }) { return null; } - foreach (BinderInvocation invocation in _invocations) + foreach (BinderInvocation? invocation in invocations) { + Debug.Assert(invocation is not null); IMethodSymbol targetMethod = invocation.Operation.TargetMethod; INamedTypeSymbol? candidateBinderType = targetMethod.ContainingType; Debug.Assert(targetMethod.IsExtensionMethod); @@ -64,72 +81,14 @@ public Parser(SourceProductionContext context, KnownTypeSymbols typeSymbols, Imm } } - return CreateIncrementalGenerationSpec(); - } - - private SourceGenerationSpec CreateIncrementalGenerationSpec() - { - Debug.Assert(_typesForGen_ConfigurationBinder_BindMethods.Count <= 3); - return new SourceGenerationSpec { - // Type index. - TypeNamespaces = _typeNamespaces.ToImmutableEquatableArray(), - // TODO: add ImmutableEquatableDictionary to give the emitter (via the generation spec). - // https://github.com/dotnet/runtime/issues/89318 - TypeList = GetTypesForGen(_typeIndex.Values), - - // ConfigurationBinder invocation info. - MethodsToGen_ConfigurationBinder = _methodsToGen_ConfigurationBinder, - TypesForGen_ConfigurationBinder_Bind_instance = GetTypesForGen_ConfigurationBinder_Bind(MethodsToGen_ConfigurationBinder.Bind_instance), - TypesForGen_ConfigurationBinder_Bind_instance_BinderOptions = GetTypesForGen_ConfigurationBinder_Bind(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions), - TypesForGen_ConfigurationBinder_Bind_key_instance = GetTypesForGen_ConfigurationBinder_Bind(MethodsToGen_ConfigurationBinder.Bind_key_instance), - - // OptionsBuilderExt and ServiceCollection invocation info. - MethodsToGen_OptionsBuilderExt = _methodsToGen_OptionsBuilderExt, - MethodsToGen_ServiceCollectionExt = _methodsToGen_ServiceCollectionExt, - - // Core binding helper emit info. - GraphContainsEnum = _graphContainsEnum, - MethodsToGen_CoreBindingHelper = _methodsToGen_CoreBindingHelper, - TypesForGen_CoreBindingHelper_GetCore = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.GetCore), - TypesForGen_CoreBindingHelper_BindCoreUntyped = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.BindCoreUntyped), - TypesForGen_CoreBindingHelper_GetValueCore = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.GetValueCore), - TypesForGen_CoreBindingHelper_BindCore = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.BindCore), - TypesForGen_CoreBindingHelper_Initialize = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.Initialize), - TypesForGen_CoreBindingHelper_ParsePrimitive = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.ParsePrimitive), + InterceptorInfo = _interceptorInfoBuilder.ToIncrementalValue(), + BindingHelperInfo = _helperInfoBuilder.ToIncrementalValue() }; - - ImmutableEquatableArray GetTypesForGen_ConfigurationBinder_Bind(MethodsToGen_ConfigurationBinder overload) - { - Debug.Assert(overload is MethodsToGen_ConfigurationBinder.Bind_instance or MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions or MethodsToGen_ConfigurationBinder.Bind_key_instance); - _typesForGen_ConfigurationBinder_BindMethods.TryGetValue(overload, out HashSet? types); - return types is null ? ImmutableEquatableArray.Empty : GetTypesForGen(types); - } - - ImmutableEquatableArray GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper overload) - where TSpec : TypeSpec, IEquatable - { - _typesForGen_CoreBindingHelper.TryGetValue(overload, out HashSet? typesAsBase); - - if (typesAsBase is null) - { - return ImmutableEquatableArray.Empty; - } - - IEnumerable types = typeof(TSpec) == typeof(TypeSpec) - ? (HashSet)(object)typesAsBase - : typesAsBase.Select(t => (TSpec)t); - - return GetTypesForGen(types); - } - - static ImmutableEquatableArray GetTypesForGen(IEnumerable types) - where TSpec : TypeSpec, IEquatable => - types.OrderBy(t => t.TypeRef.FullyQualifiedDisplayString).ToImmutableEquatableArray(); } - private bool IsValidRootConfigType(ITypeSymbol? type) + private bool IsValidRootConfigType([NotNullWhen(true)] ITypeSymbol? type) { if (type is null || type.SpecialType is SpecialType.System_Object or SpecialType.System_Void || @@ -148,7 +107,7 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || { if (!IsValidRootConfigType(type)) { - _context.ReportDiagnostic(Diagnostic.Create(Diagnostics.CouldNotDetermineTypeInfo, invocationLocation)); + ReportDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocationLocation); return null; } @@ -161,7 +120,7 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || foreach (InvocationDiagnosticInfo diag in _invocationTargetTypeDiags) { - _context.ReportDiagnostic(Diagnostic.Create(diag.Descriptor, invocationLocation, diag.MessageArgs)); + ReportDiagnostic(diag.Descriptor, invocationLocation, diag.MessageArgs); } _invocationTargetTypeDiags.Clear(); @@ -182,24 +141,19 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || if (IsNullable(type, out ITypeSymbol? underlyingType)) { - spec = MemberTypeIsBindable(type, underlyingType, Diagnostics.NullableUnderlyingTypeNotSupported, out TypeSpec? underlyingTypeSpec) + spec = MemberTypeIsBindable(type, underlyingType, DiagnosticDescriptors.NullableUnderlyingTypeNotSupported, out TypeSpec? underlyingTypeSpec) ? new NullableSpec(type, underlyingTypeSpec) : null; } else if (IsParsableFromString(type, out StringParsableTypeKind specialTypeKind)) { ParsableFromStringSpec stringParsableSpec = new(type) { StringParsableTypeKind = specialTypeKind }; - - if (stringParsableSpec.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) - { - _sourceGenSpec.PrimitivesForHelperGen.Add(stringParsableSpec); - } - + _helperInfoBuilder.RegisterStringParsableType(stringParsableSpec); spec = stringParsableSpec; } else if (IsSupportedArrayType(type)) { - spec = CreateArraySpec((type as IArrayTypeSymbol)); + spec = CreateArraySpec((IArrayTypeSymbol)type); } else if (IsCollection(type)) { @@ -213,86 +167,28 @@ 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.Namespaces.Add("System.Collections.Generic"); + _helperInfoBuilder.Namespaces.Add("System.Collections.Generic"); spec = CreateObjectSpec(namedType); } else { - RegisterUnsupportedType(type, Diagnostics.TypeNotSupported); + RegisterUnsupportedType(type, DiagnosticDescriptors.TypeNotSupported); } foreach (InvocationDiagnosticInfo diag in _invocationTargetTypeDiags) { - RegisterTypeDiagnostic(type, diag); + RecordTypeDiagnostic(type, diag); } if (spec is { Namespace: string @namespace } && @namespace is not "") { - _sourceGenSpec.Namespaces.Add(@namespace); + _helperInfoBuilder.Namespaces.Add(@namespace); } return _createdSpecs[type] = spec; } - private bool TryRegisterTypeForBindCoreMainGen(ComplexTypeSpec type) - { - if (type.HasBindableMembers) - { - bool registeredForBindCoreGen = TryRegisterTypeForBindCoreGen(type); - Debug.Assert(registeredForBindCoreGen); - - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreMain, type); - Register_AsConfigWithChildren_HelperForGen_IfRequired(type); - return true; - } - - return false; - } - - private bool TryRegisterTypeForBindCoreGen(ComplexTypeSpec type) - { - if (type.HasBindableMembers) - { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, type); - return true; - } - - return false; - } - - private void RegisterTypeForGetCoreGen(TypeSpec typeSpec) - { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, typeSpec); - Register_AsConfigWithChildren_HelperForGen_IfRequired(typeSpec); - } - - 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; - } - - private void Register_AsConfigWithChildren_HelperForGen_IfRequired(TypeSpec possibleComplexType) - { - if (possibleComplexType is ComplexTypeSpec) - { - _sourceGenSpec.MethodsToGen_CoreBindingHelper |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren; - } - } - - /// - /// Registers interceptors for root binding methods, except for ConfigurationBinder.Bind, - /// which is handled by - /// - private void RegisterInterceptor(Enum method, IInvocationOperation operation) => - _interceptionInfo.RegisterCacheEntry(method, new InterceptorLocationInfo(operation)); - private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType) { if (type is INamedTypeSymbol { IsGenericType: true } genericType && @@ -413,16 +309,21 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t private EnumerableSpec? CreateArraySpec(IArrayTypeSymbol arrayTypeSymbol) { + if (_typeSymbols.List is not INamedTypeSymbol listTypeSymbol) + { + return null; + } + ITypeSymbol elementTypeSymbol = arrayTypeSymbol.ElementType; - if (!MemberTypeIsBindable(arrayTypeSymbol, elementTypeSymbol, Diagnostics.ElementTypeNotSupported, out TypeSpec elementTypeSpec)) + if (!MemberTypeIsBindable(arrayTypeSymbol, elementTypeSymbol, DiagnosticDescriptors.ElementTypeNotSupported, out TypeSpec? elementTypeSpec)) { return null; } // We want a BindCore method for List as a temp holder for the array values. // Since the element type is supported, we can certainly a list of elements. - EnumerableSpec listTypeSpec = (EnumerableSpec)GetOrCreateTypeSpec(_typeSymbols.List.Construct(elementTypeSymbol)); + EnumerableSpec listTypeSpec = (EnumerableSpec)GetOrCreateTypeSpec(listTypeSymbol.Construct(elementTypeSymbol))!; EnumerableSpec spec = new EnumerableSpec(arrayTypeSymbol) { @@ -433,15 +334,17 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t PopulationCastType = null, }; - bool registeredForBindCore = TryRegisterTypeForBindCoreGen(listTypeSpec) && TryRegisterTypeForBindCoreGen(spec); + bool registeredForBindCore = _helperInfoBuilder.TryRegisterTypeForBindCoreGen(listTypeSpec) && + _helperInfoBuilder.TryRegisterTypeForBindCoreGen(spec); Debug.Assert(registeredForBindCore); + return spec; } private CollectionSpec? CreateCollectionSpec(INamedTypeSymbol type) { CollectionSpec? spec; - if (IsCandidateDictionary(type, out ITypeSymbol keyType, out ITypeSymbol elementType)) + if (IsCandidateDictionary(type, out ITypeSymbol? keyType, out ITypeSymbol? elementType)) { spec = CreateDictionarySpec(type, keyType, elementType); Debug.Assert(spec is null or DictionarySpec { KeyType: null or ParsableFromStringSpec }); @@ -456,22 +359,22 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t return null; } - bool registerForBindCoreGen = TryRegisterTypeForBindCoreGen(spec); + bool registerForBindCoreGen = _helperInfoBuilder.TryRegisterTypeForBindCoreGen(spec); Debug.Assert(registerForBindCoreGen); return spec; } - private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol keyType, ITypeSymbol elementType) + private DictionarySpec? CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol keyType, ITypeSymbol elementType) { - if (!MemberTypeIsBindable(type, keyType, Diagnostics.DictionaryKeyNotSupported, out TypeSpec keySpec) || - !MemberTypeIsBindable(type, elementType, Diagnostics.ElementTypeNotSupported, out TypeSpec elementSpec)) + if (!MemberTypeIsBindable(type, keyType, DiagnosticDescriptors.DictionaryKeyNotSupported, out TypeSpec? keySpec) || + !MemberTypeIsBindable(type, elementType, DiagnosticDescriptors.ElementTypeNotSupported, out TypeSpec? elementSpec)) { return null; } if (keySpec.SpecKind is not TypeSpecKind.ParsableFromString) { - RegisterUnsupportedType(type, Diagnostics.DictionaryKeyNotSupported); + RegisterUnsupportedType(type, DiagnosticDescriptors.DictionaryKeyNotSupported); return null; } @@ -495,7 +398,7 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k } else { - RegisterUnsupportedType(type, Diagnostics.CollectionNotSupported); + RegisterUnsupportedType(type, DiagnosticDescriptors.CollectionNotSupported); return null; } } @@ -511,11 +414,11 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k populationCastType = _typeSymbols.GenericIDictionary; constructionStrategy = InstantiationStrategy.ToEnumerableMethod; populationStrategy = CollectionPopulationStrategy.Cast_Then_Add; - _sourceGenSpec.Namespaces.Add("System.Linq"); + _helperInfoBuilder.Namespaces.Add("System.Linq"); } else { - RegisterUnsupportedType(type, Diagnostics.CollectionNotSupported); + RegisterUnsupportedType(type, DiagnosticDescriptors.CollectionNotSupported); return null; } @@ -537,7 +440,7 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k private EnumerableSpec? CreateEnumerableSpec(INamedTypeSymbol type) { if (!TryGetElementType(type, out ITypeSymbol? elementType) || - !MemberTypeIsBindable(type, elementType, Diagnostics.ElementTypeNotSupported, out TypeSpec elementSpec)) + !MemberTypeIsBindable(type, elementType, DiagnosticDescriptors.ElementTypeNotSupported, out TypeSpec? elementSpec)) { return null; } @@ -562,7 +465,7 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k } else { - RegisterUnsupportedType(type, Diagnostics.CollectionNotSupported); + RegisterUnsupportedType(type, DiagnosticDescriptors.CollectionNotSupported); return null; } } @@ -602,7 +505,7 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k } else { - RegisterUnsupportedType(type, Diagnostics.CollectionNotSupported); + RegisterUnsupportedType(type, DiagnosticDescriptors.CollectionNotSupported); return null; } @@ -620,23 +523,25 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k return spec; } - private ObjectSpec? CreateObjectSpec(INamedTypeSymbol objectSymbol) + private ObjectSpec? CreateObjectSpec(INamedTypeSymbol typeSymbol) { // Add spec to cache before traversing properties to avoid stack overflow. - ObjectSpec objectSpec = new(objectSymbol); - _createdSpecs.Add(objectSymbol, objectSpec); + _createdSpecs.Add(typeSymbol, null); - string typeName = objectSpec.Name; - IMethodSymbol? ctor = null; + string typeName = typeSymbol.GetTypeName().Name; + InstantiationStrategy initStrategy = InstantiationStrategy.None; DiagnosticDescriptor? initDiagDescriptor = null; + string? initExceptionMessage = null; + + IMethodSymbol? ctor = null; - if (!(objectSymbol.IsAbstract || objectSymbol.TypeKind is TypeKind.Interface)) + if (!(typeSymbol.IsAbstract || typeSymbol.TypeKind is TypeKind.Interface)) { IMethodSymbol? parameterlessCtor = null; IMethodSymbol? parameterizedCtor = null; bool hasMultipleParameterizedCtors = false; - foreach (IMethodSymbol candidate in objectSymbol.InstanceConstructors) + foreach (IMethodSymbol candidate in typeSymbol.InstanceConstructors) { if (candidate.DeclaredAccessibility is not Accessibility.Public) { @@ -657,14 +562,14 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k } } - bool hasPublicParameterlessCtor = objectSymbol.IsValueType || parameterlessCtor is not null; + bool hasPublicParameterlessCtor = typeSymbol.IsValueType || parameterlessCtor is not null; if (!hasPublicParameterlessCtor && hasMultipleParameterizedCtors) { - initDiagDescriptor = Diagnostics.MultipleParameterizedConstructors; - objectSpec.InitExceptionMessage = string.Format(Emitter.ExceptionMessages.MultipleParameterizedConstructors, typeName); + initDiagDescriptor = DiagnosticDescriptors.MultipleParameterizedConstructors; + initExceptionMessage = string.Format(Emitter.ExceptionMessages.MultipleParameterizedConstructors, typeName); } - ctor = objectSymbol.IsValueType + ctor = typeSymbol.IsValueType // Roslyn ctor fetching APIs include paramerterless ctors for structs, unlike System.Reflection. ? parameterizedCtor ?? parameterlessCtor : parameterlessCtor ?? parameterizedCtor; @@ -672,21 +577,23 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k if (ctor is null) { - initDiagDescriptor = Diagnostics.MissingPublicInstanceConstructor; - objectSpec.InitExceptionMessage = string.Format(Emitter.ExceptionMessages.MissingPublicInstanceConstructor, typeName); + initDiagDescriptor = DiagnosticDescriptors.MissingPublicInstanceConstructor; + initExceptionMessage = string.Format(Emitter.ExceptionMessages.MissingPublicInstanceConstructor, typeName); } else { - objectSpec.InstantiationStrategy = ctor.Parameters.Length is 0 ? InstantiationStrategy.ParameterlessConstructor : InstantiationStrategy.ParameterizedConstructor; + initStrategy = ctor.Parameters.Length is 0 ? InstantiationStrategy.ParameterlessConstructor : InstantiationStrategy.ParameterizedConstructor; } if (initDiagDescriptor is not null) { - Debug.Assert(objectSpec.InitExceptionMessage is not null); - RegisterUnsupportedType(objectSymbol, initDiagDescriptor); + Debug.Assert(initExceptionMessage is not null); + RegisterUnsupportedType(typeSymbol, initDiagDescriptor); } - INamedTypeSymbol current = objectSymbol; + Dictionary? properties = null; + + INamedTypeSymbol? current = typeSymbol; while (current is not null) { ImmutableArray members = current.GetMembers(); @@ -695,12 +602,12 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k if (member is IPropertySymbol { IsIndexer: false, IsImplicitlyDeclared: false } property) { string propertyName = property.Name; - TypeSpec propertyTypeSpec = GetOrCreateTypeSpec(property.Type); + TypeSpec? propertyTypeSpec = GetOrCreateTypeSpec(property.Type); if (propertyTypeSpec?.CanBindTo is not true) { - InvocationDiagnosticInfo propertyDiagnostic = new InvocationDiagnosticInfo(Diagnostics.PropertyNotSupported, new string[] { propertyName, objectSymbol.ToDisplayString() }); - RegisterTypeDiagnostic(causingType: objectSymbol, propertyDiagnostic); + InvocationDiagnosticInfo propertyDiagnostic = new InvocationDiagnosticInfo(DiagnosticDescriptors.PropertyNotSupported, new string[] { propertyName, typeSymbol.ToDisplayString() }); + RecordTypeDiagnostic(causingType: typeSymbol, propertyDiagnostic); _invocationTargetTypeDiags.Add(propertyDiagnostic); } @@ -710,30 +617,33 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k string configKeyName = attributeData?.ConstructorArguments.FirstOrDefault().Value as string ?? propertyName; PropertySpec spec = new(property) { Type = propertyTypeSpec, ConfigurationKeyName = configKeyName }; - objectSpec.Properties[propertyName] = spec; - Register_AsConfigWithChildren_HelperForGen_IfRequired(propertyTypeSpec); + (properties ??= new(StringComparer.OrdinalIgnoreCase))[propertyName] = spec; + _helperInfoBuilder.Register_AsConfigWithChildren_HelperForGen_IfRequired(propertyTypeSpec); } } } current = current.BaseType; } - if (objectSpec.InstantiationStrategy is InstantiationStrategy.ParameterizedConstructor) + List? ctorParams = null; + + if (initStrategy is InstantiationStrategy.ParameterizedConstructor) { - List missingParameters = new(); - List invalidParameters = new(); + Debug.Assert(ctor is not null); + List? missingParameters = null; + List? invalidParameters = null; foreach (IParameterSymbol parameter in ctor.Parameters) { string parameterName = parameter.Name; - if (!objectSpec.Properties.TryGetValue(parameterName, out PropertySpec? propertySpec)) + if (properties?.TryGetValue(parameterName, out PropertySpec? propertySpec) is not true) { - missingParameters.Add(parameterName); + (missingParameters ??= new()).Add(parameterName); } else if (parameter.RefKind is not RefKind.None) { - invalidParameters.Add(parameterName); + (invalidParameters ??= new()).Add(parameterName); } else { @@ -744,43 +654,51 @@ private DictionarySpec CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol k }; propertySpec.MatchingCtorParam = paramSpec; - objectSpec.ConstructorParameters.Add(paramSpec); + (ctorParams ??= new()).Add(paramSpec); } } - if (invalidParameters.Count > 0) + if (invalidParameters?.Count > 0) { - objectSpec.InitExceptionMessage = string.Format(Emitter.ExceptionMessages.CannotBindToConstructorParameter, typeName, FormatParams(invalidParameters)); + initExceptionMessage = string.Format(Emitter.ExceptionMessages.CannotBindToConstructorParameter, typeName, FormatParams(invalidParameters)); } - else if (missingParameters.Count > 0) + else if (missingParameters?.Count > 0) { - if (objectSymbol.IsValueType) + if (typeSymbol.IsValueType) { - objectSpec.InstantiationStrategy = InstantiationStrategy.ParameterlessConstructor; + initStrategy = InstantiationStrategy.ParameterlessConstructor; } else { - objectSpec.InitExceptionMessage = string.Format(Emitter.ExceptionMessages.ConstructorParametersDoNotMatchProperties, typeName, FormatParams(missingParameters)); + initExceptionMessage = string.Format(Emitter.ExceptionMessages.ConstructorParametersDoNotMatchProperties, typeName, FormatParams(missingParameters)); } } - if (objectSpec.CanInstantiate) - { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.Initialize, objectSpec); - } - static string FormatParams(List names) => string.Join(",", names); } - Debug.Assert((objectSpec.CanInstantiate && objectSpec.InitExceptionMessage is null) || - (!objectSpec.CanInstantiate && objectSpec.InitExceptionMessage is not null) || - (!objectSpec.CanInstantiate && (objectSymbol.IsAbstract || objectSymbol.TypeKind is TypeKind.Interface))); + ObjectSpec typeSpec = new(typeSymbol) + { + InstantiationStrategy = initStrategy, + Properties = properties?.Values.OrderBy(p => p.Name).ToImmutableEquatableArray(), + ConstructorParameters = ctorParams?.ToImmutableEquatableArray(), + InitExceptionMessage = initExceptionMessage + }; + + if (typeSpec is { InstantiationStrategy: InstantiationStrategy.ParameterizedConstructor, CanInstantiate: true }) + { + _helperInfoBuilder.RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.Initialize, typeSpec); + } + + Debug.Assert((typeSpec.CanInstantiate && initExceptionMessage is null) || + (!typeSpec.CanInstantiate && initExceptionMessage is not null) || + (!typeSpec.CanInstantiate && (typeSymbol.IsAbstract || typeSymbol.TypeKind is TypeKind.Interface))); - TryRegisterTypeForBindCoreGen(objectSpec); - return objectSpec; + _helperInfoBuilder.TryRegisterTypeForBindCoreGen(typeSpec); + return typeSpec; } - private bool MemberTypeIsBindable(ITypeSymbol containingTypeSymbol, ITypeSymbol memberTypeSymbol, DiagnosticDescriptor containingTypeDiagDescriptor, out TypeSpec? memberTypeSpec) + private bool MemberTypeIsBindable(ITypeSymbol containingTypeSymbol, ITypeSymbol memberTypeSymbol, DiagnosticDescriptor containingTypeDiagDescriptor, [NotNullWhen(true)] out TypeSpec? memberTypeSpec) { if (GetOrCreateTypeSpec(memberTypeSymbol) is TypeSpec { CanBindTo: true } spec) { @@ -793,7 +711,7 @@ private bool MemberTypeIsBindable(ITypeSymbol containingTypeSymbol, ITypeSymbol return false; } - private bool TryGetElementType(INamedTypeSymbol type, out ITypeSymbol? elementType) + private bool TryGetElementType(INamedTypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? elementType) { INamedTypeSymbol? candidate = GetInterface(type, _typeSymbols.GenericIEnumerable_Unbound); @@ -807,7 +725,7 @@ private bool TryGetElementType(INamedTypeSymbol type, out ITypeSymbol? elementTy return false; } - private bool IsCandidateDictionary(INamedTypeSymbol type, out ITypeSymbol? keyType, out ITypeSymbol? elementType) + private bool IsCandidateDictionary(INamedTypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? keyType, [NotNullWhen(true)] out ITypeSymbol? elementType) { INamedTypeSymbol? candidate = GetInterface(type, _typeSymbols.GenericIDictionary_Unbound) ?? GetInterface(type, _typeSymbols.IReadOnlyDictionary_Unbound); @@ -842,15 +760,20 @@ private bool IsSupportedArrayType(ITypeSymbol type) if (arrayType.Rank > 1) { - RegisterUnsupportedType(arrayType, Diagnostics.MultiDimArraysNotSupported); + RegisterUnsupportedType(arrayType, DiagnosticDescriptors.MultiDimArraysNotSupported); return false; } return true; } - private static INamedTypeSymbol? GetInterface(INamedTypeSymbol type, INamedTypeSymbol @interface) + private static INamedTypeSymbol? GetInterface(INamedTypeSymbol type, INamedTypeSymbol? @interface) { + if (@interface is null) + { + return null; + } + if (IsInterfaceMatch(type, @interface)) { return type; @@ -867,8 +790,13 @@ private bool IsSupportedArrayType(ITypeSymbol type) return type.AllInterfaces.FirstOrDefault(candidate => SymbolEqualityComparer.Default.Equals(candidate, @interface)); } - private static bool IsInterfaceMatch(INamedTypeSymbol type, INamedTypeSymbol @interface) + private static bool IsInterfaceMatch(INamedTypeSymbol type, INamedTypeSymbol? @interface) { + if (@interface is null) + { + return false; + } + if (type.IsGenericType) { INamedTypeSymbol unbound = type.ConstructUnboundGenericType(); @@ -902,8 +830,8 @@ private static bool HasPublicParameterLessCtor(INamedTypeSymbol type) => private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol element) { - INamedTypeSymbol current = type; - while (current != null) + INamedTypeSymbol? current = type; + while (current is not null) { if (current.GetMembers("Add").Any(member => member is IMethodSymbol { Parameters.Length: 1 } method && @@ -918,8 +846,8 @@ private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol element) private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol key, ITypeSymbol element) { - INamedTypeSymbol current = type; - while (current != null) + INamedTypeSymbol? current = type; + while (current is not null) { if (current.GetMembers("Add").Any(member => member is IMethodSymbol { Parameters.Length: 2 } method && @@ -945,20 +873,20 @@ private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol key, ITypeSy return CreateCollectionSpec(constructedType); } - private void RegisterUnsupportedType(ITypeSymbol type, DiagnosticDescriptor descriptor = null) + private void RegisterUnsupportedType(ITypeSymbol type, DiagnosticDescriptor descriptor) { InvocationDiagnosticInfo diagInfo = new(descriptor, new string[] { type.ToDisplayString() }); if (!_unsupportedTypes.Contains(type)) { - RegisterTypeDiagnostic(type, diagInfo); + RecordTypeDiagnostic(type, diagInfo); _unsupportedTypes.Add(type); } _invocationTargetTypeDiags.Add(diagInfo); } - private void RegisterTypeDiagnostic(ITypeSymbol causingType, InvocationDiagnosticInfo info) + private void RecordTypeDiagnostic(ITypeSymbol causingType, InvocationDiagnosticInfo info) { bool typeHadDiags = _typeDiagnostics.TryGetValue(causingType, out HashSet? typeDiags); typeDiags ??= new HashSet(); @@ -969,6 +897,15 @@ private void RegisterTypeDiagnostic(ITypeSymbol causingType, InvocationDiagnosti _typeDiagnostics[causingType] = typeDiags; } } + + private void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location, params object?[]? messageArgs) => + (Diagnostics ??= new()).Add(new DiagnosticInfo + { + Descriptor = descriptor, + //Location = location?.GetTrimmedLocation(), + Location = location, + MessageArgs = messageArgs ?? Array.Empty(), + }); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs index f2300a2c3d2fc..d2a6a50f6a22a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs @@ -1,9 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Immutable; +//#define LAUNCH_DEBUGGER using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -13,7 +14,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration [Generator] public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerator { - private static readonly string ProjectName = Emitter.s_assemblyName.Name; + private static readonly string ProjectName = Emitter.s_assemblyName.Name!; public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -29,7 +30,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ? new CompilationData((CSharpCompilation)compilation) : null); - IncrementalValueProvider<(SourceGenerationSpec?, ImmutableEquatableArray)> genSpec = context.SyntaxProvider + IncrementalValueProvider<(SourceGenerationSpec?, ImmutableEquatableArray?)> genSpec = context.SyntaxProvider .CreateSyntaxProvider( (node, _) => BinderInvocation.IsCandidateSyntaxNode(node), BinderInvocation.Create) @@ -38,9 +39,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Combine(compilationData) .Select((tuple, cancellationToken) => { - Parser parser = new(tuple.Right); - SourceGenerationSpec spec = parser.ParseSourceGenerationSpec(tuple.Left, cancellationToken); - ImmutableEquatableArray diagnostics = parser.Diagnostics.ToImmutableEquatableArray(); + if (tuple.Right is not CompilationData compilationData) + { + return (null, null); + } + + Parser parser = new(compilationData); + SourceGenerationSpec? spec = parser.GetSourceGenerationSpec(tuple.Left); + ImmutableEquatableArray? diagnostics = parser.Diagnostics?.ToImmutableEquatableArray(); return (spec, diagnostics); }) .WithTrackingName(nameof(SourceGenerationSpec)); @@ -48,11 +54,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterSourceOutput(genSpec, ReportDiagnosticsAndEmitSource); } - private static void ReportDiagnosticsAndEmitSource(SourceProductionContext sourceProductionContext, (SourceGenerationSpec? SourceGenerationSpec, ImmutableEquatableArray Diagnostics) input) + private static void ReportDiagnosticsAndEmitSource(SourceProductionContext sourceProductionContext, (SourceGenerationSpec? SourceGenerationSpec, ImmutableEquatableArray? Diagnostics) input) { - foreach (DiagnosticInfo diagnostic in input.Diagnostics) + if (input.Diagnostics is ImmutableEquatableArray diagnostics) { - sourceProductionContext.ReportDiagnostic(diagnostic.CreateDiagnostic()); + foreach (DiagnosticInfo diagnostic in diagnostics) + { + sourceProductionContext.ReportDiagnostic(diagnostic.CreateDiagnostic()); + } } if (input.SourceGenerationSpec is SourceGenerationSpec spec) @@ -62,7 +71,7 @@ private static void ReportDiagnosticsAndEmitSource(SourceProductionContext sourc } } - private sealed record CompilationData + internal sealed class CompilationData { public bool LanguageVersionIsSupported { get; } public KnownTypeSymbols? TypeSymbols { get; } @@ -80,5 +89,11 @@ public CompilationData(CSharpCompilation compilation) } } } + + internal sealed record SourceGenerationSpec + { + public required InterceptorInfo InterceptorInfo { get; init; } + public required BindingHelperInfo BindingHelperInfo { get; init; } + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs index 7d548e384da18..c75fe9e333bb1 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs @@ -1,8 +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.Collections.Generic; using System.Diagnostics; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -10,11 +10,9 @@ public sealed partial class ConfigurationBindingGenerator { private sealed partial class Emitter { - private bool ShouldEmitMethods(MethodsToGen_ConfigurationBinder methods) => (_sourceGenSpec.MethodsToGen_ConfigurationBinder & methods) != 0; - private void EmitBindingExtensions_IConfiguration() { - if (!ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Any)) + if (!ShouldEmitMethods(MethodsToGen.ConfigBinder_Any)) { return; } @@ -31,30 +29,30 @@ private void EmitGetMethods() 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)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_Get_T)) { - StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_T, documentation); + EmitStartDefinition_Get_Or_GetValue_Overload(MethodsToGen.ConfigBinder_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)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_Get_T_BinderOptions)) { - StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_T_BinderOptions, documentation); + EmitStartDefinition_Get_Or_GetValue_Overload(MethodsToGen.ConfigBinder_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)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_Get_TypeOf)) { - StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_TypeOf, documentation); + EmitStartDefinition_Get_Or_GetValue_Overload(MethodsToGen.ConfigBinder_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)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_Get_TypeOf_BinderOptions)) { - StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_TypeOf_BinderOptions, documentation); + EmitStartDefinition_Get_Or_GetValue_Overload(MethodsToGen.ConfigBinder_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});"); } @@ -65,30 +63,30 @@ private void EmitGetValueMethods() 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)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_GetValue_T_key)) { - StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_T_key, documentation); + EmitStartDefinition_Get_Or_GetValue_Overload(MethodsToGen.ConfigBinder_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)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_GetValue_T_key_defaultValue)) { - StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_T_key_defaultValue, documentation); + EmitStartDefinition_Get_Or_GetValue_Overload(MethodsToGen.ConfigBinder_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)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_GetValue_TypeOf_key)) { - StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key, documentation); + EmitStartDefinition_Get_Or_GetValue_Overload(MethodsToGen.ConfigBinder_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)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_GetValue_TypeOf_key_defaultValue)) { - StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key_defaultValue, documentation); + EmitStartDefinition_Get_Or_GetValue_Overload(MethodsToGen.ConfigBinder_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};"); } @@ -96,47 +94,49 @@ private void EmitGetValueMethods() private void EmitBindMethods_ConfigurationBinder() { - if (!ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind)) + if (!ShouldEmitMethods(MethodsToGen.ConfigBinder_Bind)) { return; } string instanceParamExpr = $"object? {Identifier.instance}"; - if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_Bind_instance)) { EmitMethods( - MethodsToGen_ConfigurationBinder.Bind_instance, + _interceptorInfo.ConfigBinder_Bind_instance, additionalParams: instanceParamExpr, configExpression: Identifier.configuration, configureOptions: false); } - if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_Bind_instance_BinderOptions)) { EmitMethods( - MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions, + _interceptorInfo.ConfigBinder_Bind_instance_BinderOptions, additionalParams: $"{instanceParamExpr}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}", configExpression: Identifier.configuration, configureOptions: true); } - if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_key_instance)) + if (ShouldEmitMethods(MethodsToGen.ConfigBinder_Bind_key_instance)) { EmitMethods( - MethodsToGen_ConfigurationBinder.Bind_key_instance, + _interceptorInfo.ConfigBinder_Bind_key_instance, additionalParams: $"string {Identifier.key}, {instanceParamExpr}", configExpression: $"{Expression.configurationGetSection}({Identifier.key})", configureOptions: false); } - void EmitMethods(MethodsToGen_ConfigurationBinder method, string additionalParams, string configExpression, bool configureOptions) + void EmitMethods(ImmutableEquatableArray? interceptorInfo, string additionalParams, string configExpression, bool configureOptions) { - foreach ((ComplexTypeSpec type, List interceptorInfoList) in _interceptionInfo_ConfigBinder.GetOverloadInfo(method)) + Debug.Assert(interceptorInfo is not null); + + foreach ((ComplexTypeSpec type, ImmutableEquatableArray locations) in interceptorInfo) { EmitBlankLineIfRequired(); _writer.WriteLine($"/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively."); - EmitInterceptsLocationAnnotations(interceptorInfoList); + EmitInterceptsLocationAnnotations(locations); EmitStartBlock($"public static void {Identifier.Bind}_{type.IdentifierCompatibleSubstring}(this {Identifier.IConfiguration} {Identifier.configuration}, {additionalParams})"); if (type.HasBindableMembers) @@ -157,11 +157,11 @@ void EmitMethods(MethodsToGen_ConfigurationBinder method, string additionalParam } } - private void StartMethodDefinition(MethodsToGen_ConfigurationBinder method, string documentation) + private void EmitStartDefinition_Get_Or_GetValue_Overload(MethodsToGen overload, string documentation) { EmitBlankLineIfRequired(); _writer.WriteLine($"/// {documentation}"); - EmitInterceptsLocationAnnotations(method); + EmitInterceptsLocationAnnotations(overload); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs index e103abc2cb10e..6b4b3bc83e74d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; -using DotnetRuntime.SourceGenerators; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -19,7 +19,7 @@ private sealed partial class Emitter private bool _emitBlankLineBeforeNextStatement; private static readonly Regex s_arrayBracketsRegex = new(Regex.Escape("[]")); - private bool ShouldEmitMethods(MethodsToGen_CoreBindingHelper methods) => (_sourceGenSpec.MethodsToGen_CoreBindingHelper & methods) != 0; + private bool ShouldEmitMethods(MethodsToGen_CoreBindingHelper methods) => (_bindingHelperInfo.MethodsToGen & methods) != 0; private void EmitCoreBindingHelpers() { @@ -37,24 +37,45 @@ private void EmitCoreBindingHelpers() private void EmitConfigurationKeyCaches() { - if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.BindCore, out HashSet targetTypes)) + if (_bindingHelperInfo.TypesForGen_BindCore is not { Count: not 0 } types) { return; } EmitBlankLineIfRequired(); - foreach (TypeSpec type in targetTypes) + foreach (TypeSpec type in types) { if (type is not ObjectSpec objectType) { continue; } - HashSet keys = new(objectType.ConstructorParameters.Select(m => GetCacheElement(m))); - keys.UnionWith(objectType.Properties.Select(m => GetCacheElement(m))); + Debug.Assert(objectType.HasBindableMembers); + + HashSet? keys = null; static string GetCacheElement(MemberSpec member) => $@"""{member.ConfigurationKeyName}"""; + if (objectType.ConstructorParameters?.Select(m => GetCacheElement(m)) is IEnumerable paramNames) + { + keys = new(paramNames); + } + + if (objectType.Properties?.Select(m => GetCacheElement(m)) is IEnumerable propNames) + { + if (keys is null) + { + keys = new(propNames); + } + else + { + keys.UnionWith(propNames); + } + } + + // Type has bindable members. + Debug.Assert(keys is not null); + string configKeysSource = string.Join(", ", keys); string fieldName = GetConfigKeyCacheFieldName(objectType); _writer.WriteLine($@"private readonly static Lazy<{TypeDisplayString.HashSetOfString}> {fieldName} = new(() => new {TypeDisplayString.HashSetOfString}(StringComparer.OrdinalIgnoreCase) {{ {configKeysSource} }});"); @@ -63,7 +84,7 @@ private void EmitConfigurationKeyCaches() private void EmitGetCoreMethod() { - if (_sourceGenSpec.TypesForGen_CoreBindingHelper_GetCore.Count is 0) + if (_bindingHelperInfo.TypesForGen_GetCore is not { Count: not 0 } targetTypes) { return; } @@ -79,10 +100,9 @@ private void EmitGetCoreMethod() EmitIConfigurationHasValueOrChildrenCheck(voidReturn: false); bool isFirstType = true; - foreach (TypeSpec type in types) + foreach (TypeSpec type in targetTypes) { - TypeRef effectiveTypeRef = type.EffectiveTypeRef; - TypeSpec effectiveType = GetTypeSpec(effectiveTypeRef); + TypeSpec effectiveType = type.EffectiveType; TypeSpecKind kind = effectiveType.SpecKind; string conditionKindExpr = GetConditionKindExpr(ref isFirstType); @@ -143,7 +163,7 @@ void EmitCastToIConfigurationSection() => private void EmitGetValueCoreMethod() { - if (_sourceGenSpec.TypesForGen_CoreBindingHelper_GetValueCore.Count is 0) + if (_bindingHelperInfo.TypesForGen_GetValueCore is not { Count: not 0 } targetTypes) { return; } @@ -190,7 +210,7 @@ private void EmitGetValueCoreMethod() private void EmitBindCoreMainMethod() { - if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.BindCoreMain, out HashSet? targetTypes)) + if (_bindingHelperInfo.TypesForGen_BindCoreMain is not { Count: not 0 } targetTypes) { return; } @@ -223,12 +243,12 @@ private void EmitBindCoreMainMethod() private void EmitBindCoreMethods() { - foreach (TypeWithChildrenSpec type in _sourceGenSpec.TypesForGen_CoreBindingHelper_BindCore) + if (_bindingHelperInfo.TypesForGen_BindCore is not ImmutableEquatableArray types) { return; } - foreach (ComplexTypeSpec type in targetTypes) + foreach (ComplexTypeSpec type in types) { Debug.Assert(type.HasBindableMembers); EmitBlankLineIfRequired(); @@ -246,20 +266,21 @@ private void EmitBindCoreMethod(ComplexTypeSpec type) { if (effectiveType.InstantiationStrategy is InstantiationStrategy.Array) { - EmitPopulateImplForArray(enumerableType.ElementTypeRef); + Debug.Assert(type == effectiveType); + EmitPopulationImplForArray((EnumerableSpec)type); } else { - EmitPopulateImplForEnumerableWithAdd(enumerableType); + EmitPopulationImplForEnumerableWithAdd(enumerable); } } - else if (type is DictionarySpec dictionary) + else if (effectiveType is DictionarySpec dictionary) { EmitBindCoreImplForDictionary(dictionary); } else { - EmitBindCoreImplForObject((ObjectSpec)type); + EmitBindCoreImplForObject((ObjectSpec)effectiveType); } EmitEndBlock(); @@ -267,7 +288,12 @@ private void EmitBindCoreMethod(ComplexTypeSpec type) private void EmitInitializeMethods() { - foreach (ObjectSpec type in _sourceGenSpec.TypesForGen_CoreBindingHelper_Initialize) + if (_bindingHelperInfo.TypesForGen_Initialize is not ImmutableEquatableArray types) + { + return; + } + + foreach (ObjectSpec type in types) { EmitBlankLineIfRequired(); EmitInitializeMethod(type); @@ -276,9 +302,13 @@ private void EmitInitializeMethods() private void EmitInitializeMethod(ObjectSpec type) { + Debug.Assert(type.InstantiationStrategy is InstantiationStrategy.ParameterizedConstructor); Debug.Assert(type.CanInstantiate); - List ctorParams = type.ConstructorParameters; - IEnumerable initOnlyProps = type.Properties.Values.Where(prop => prop is { SetOnInit: true }); + Debug.Assert( + type is { Properties: not null, ConstructorParameters: not null }, + $"Expecting type for init method, {type.DisplayString}, to have both properties and ctor params."); + + IEnumerable initOnlyProps = type.Properties.Where(prop => prop is { SetOnInit: true }); List ctorArgList = new(); string displayString = type.DisplayString; @@ -423,22 +453,33 @@ private void EmitHelperMethods() } if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreMain | MethodsToGen_CoreBindingHelper.GetCore) || - ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions)) + ShouldEmitMethods(MethodsToGen.ConfigBinder_Bind_instance_BinderOptions)) { EmitBlankLineIfRequired(); EmitGetBinderOptionsHelper(); } - if (_sourceGenSpec.GraphContainsEnum) + if (_bindingHelperInfo.TypesForGen_ParsePrimitive is { Count: not 0 } stringParsableTypes) { - EmitBlankLineIfRequired(); - EmitEnumParseMethod(); - } + bool enumTypeExists = false; - foreach (ParsableFromStringSpec type in _sourceGenSpec.TypesForGen_CoreBindingHelper_ParsePrimitive) - { - EmitBlankLineIfRequired(); - EmitPrimitiveParseMethod(type); + foreach (ParsableFromStringSpec type in stringParsableTypes) + { + EmitBlankLineIfRequired(); + + if (type.StringParsableTypeKind == StringParsableTypeKind.Enum) + { + if (!enumTypeExists) + { + EmitEnumParseMethod(); + enumTypeExists = true; + } + } + else + { + EmitPrimitiveParseMethod(type); + } + } } } @@ -603,7 +644,7 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.getPath}()}}", $"{{typeof({typeDisplayString})}}"); - EmitStartBlock($"public static {typeDisplayString} {type.ToParseMethodName()}(string {Identifier.value}, Func {Identifier.getPath})"); + EmitStartBlock($"public static {typeDisplayString} {type.ParseMethodName}(string {Identifier.value}, Func {Identifier.getPath})"); EmitEndBlock($$""" try { @@ -616,8 +657,9 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) """); } - private void EmitPopulateImplForArray(TypeRef elementTypeRef) + private void EmitPopulationImplForArray(EnumerableSpec type) { + Debug.Assert(type.TypeToInstantiate is { CanBindTo: true }); EnumerableSpec typeToInstantiate = (EnumerableSpec)type.TypeToInstantiate; // Create list and bind elements. @@ -625,7 +667,6 @@ private void EmitPopulateImplForArray(TypeRef elementTypeRef) EmitBindingLogic(typeToInstantiate, tempIdentifier, Identifier.configuration, InitializationKind.Declaration, ValueDefaulting.None); // Resize array and add binded elements. - _writer.WriteLine(); _writer.WriteLine($$""" {{Identifier.Int32}} {{Identifier.originalCount}} = {{Identifier.instance}}.{{Identifier.Length}}; {{Identifier.Array}}.{{Identifier.Resize}}(ref {{Identifier.instance}}, {{Identifier.originalCount}} + {{tempIdentifier}}.{{Identifier.Count}}); @@ -633,12 +674,10 @@ private void EmitPopulateImplForArray(TypeRef elementTypeRef) """); } - private void EmitPopulateImplForEnumerableWithAdd(EnumerableSpec type) + private void EmitPopulationImplForEnumerableWithAdd(EnumerableSpec type) { EmitCollectionCastIfRequired(type, out string instanceIdentifier); - private void EmitPopulateImplForEnumerableWithAddCore(string bindTargetIdentifier, TypeRef elementTypeRef) - { Emit_Foreach_Section_In_ConfigChildren_StartBlock(); string addExpr = $"{instanceIdentifier}.{Identifier.Add}"; @@ -679,8 +718,8 @@ private void EmitBindCoreImplForDictionary(DictionarySpec type) Emit_Foreach_Section_In_ConfigChildren_StartBlock(); - ParsableFromStringSpec effectiveKeyType = (ParsableFromStringSpec)GetEffectiveTypeSpec(type.KeyTypeRef); - TypeSpec effectiveElementType = GetEffectiveTypeSpec(type.ElementTypeRef); + ParsableFromStringSpec keyType = type.KeyType; + TypeSpec elementType = type.ElementType; // Parse key EmitBindingLogic( @@ -753,9 +792,8 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) private void EmitBindCoreImplForObject(ObjectSpec type) { - Debug.Assert(type.HasBindableMembers); + Debug.Assert(type is { HasBindableMembers: true, Properties: not null }); - string displayString = type.TypeRef.MinimalDisplayString; string keyCacheFieldName = GetConfigKeyCacheFieldName(type); string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.DisplayString}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});"; _writer.WriteLine(validateMethodCallExpr); @@ -776,10 +814,6 @@ private void EmitBindCoreImplForObject(ObjectSpec type) } } - /// - /// Emits binding logic for an object property of ctor parameter. - /// - /// True if no exception was thrown, otherwise, false. private bool EmitBindImplForMember( MemberSpec member, string memberAccessExpr, @@ -857,7 +891,7 @@ private void EmitBindingLogicForComplexMember( InitializationKind initKind; string targetObjAccessExpr; - if (effectiveMemberType.TypeRef.IsValueType) + if (effectiveMemberType.IsValueType) { if (!canSet) { @@ -884,21 +918,26 @@ private void EmitBindingLogicForComplexMember( targetObjAccessExpr = tempIdentifier; } + else if (member.CanGet) + { + targetObjAccessExpr = memberAccessExpr; + initKind = InitializationKind.AssignmentWithNullCheck; + } else { targetObjAccessExpr = memberAccessExpr; - initKind = canGet ? InitializationKind.AssignmentWithNullCheck : InitializationKind.SimpleAssignment; + initKind = InitializationKind.SimpleAssignment; } Action? writeOnSuccess = !canSet ? null : bindedValueIdentifier => + { + if (memberAccessExpr != bindedValueIdentifier) { - if (memberAccessExpr != bindedValueIdentifier) - { - _writer.WriteLine($"{memberAccessExpr} = {bindedValueIdentifier};"); - } - }; + _writer.WriteLine($"{memberAccessExpr} = {bindedValueIdentifier};"); + } + }; EmitBindingLogic( effectiveMemberType, @@ -1067,7 +1106,7 @@ private bool EmitObjectInit(ComplexTypeSpec type, string memberAccessExpr, Initi { case InitializationKind.Declaration: { - Debug.Assert(!memberAccessExpr.Contains(".")); + Debug.Assert(!memberAccessExpr.Contains('.')); _writer.WriteLine($"var {memberAccessExpr} = {initExpr};"); } break; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs index 788a15d94e5ac..696af27d69737 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ExceptionMessages.cs @@ -18,7 +18,7 @@ internal static class ExceptionMessages public const string MissingPublicInstanceConstructor = "Cannot create instance of type '{0}' because it is missing a public instance constructor."; public const string MultipleParameterizedConstructors = "Cannot create instance of type '{0}' because it has multiple public parameterized constructors."; public const string ParameterHasNoMatchingConfig = "Cannot create instance of type '{0}' because parameter '{1}' has no matching config. Each parameter in the constructor that does not have a default value must have a corresponding config entry."; - public const string TypeNotSupportedAsInput = "Unable to bind to type '{0}': generator did not detect the type as input, or the type is not supported for binding."; + public const string TypeNotDetectedAsInput = "Unable to bind to type '{0}': generator did not detect the type as input."; } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs index 1879e06276149..5a372889b2f46 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs @@ -136,30 +136,29 @@ private static class Identifier public const string Value = nameof(Value); } - private bool ShouldEmitBindingExtensions() => - ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Any) || - ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Any) || - ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Any); + private bool ShouldEmitMethods(MethodsToGen methods) => (_interceptorInfo.MethodsToGen & methods) != 0; - private void EmitInterceptsLocationAnnotations(Enum generatedBindingOverload) + private void EmitInterceptsLocationAnnotations(MethodsToGen overload) { + IEnumerable? infoList = _interceptorInfo.GetInfo(overload); + bool interceptsCalls = infoList is not null; + // 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 = _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); + overload is MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions || + overload is MethodsToGen.OptionsBuilderExt_Bind_T_BinderOptions); if (interceptsCalls) { - EmitInterceptsLocationAnnotations(infoList); + EmitInterceptsLocationAnnotations(infoList!); } } - private void EmitInterceptsLocationAnnotations(List infoList) + private void EmitInterceptsLocationAnnotations(IEnumerable infoList) { - foreach (InterceptorLocationInfo info in infoList) + foreach (InvocationLocationInfo info in infoList) { _writer.WriteLine($@"[{Identifier.InterceptsLocation}(@""{info.FilePath}"", {info.LineNumber}, {info.CharacterNumber})]"); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsBuilderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsBuilderConfigurationExtensions.cs index 7fd5d695eaf45..fdc4286e34c55 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsBuilderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsBuilderConfigurationExtensions.cs @@ -7,11 +7,9 @@ public sealed partial class ConfigurationBindingGenerator { private sealed partial class Emitter { - private bool ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder methods) => (_sourceGenSpec.MethodsToGen_OptionsBuilderExt & methods) != 0; - private void EmitBindingExtensions_OptionsBuilder() { - if (!ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Any)) + if (!ShouldEmitMethods(MethodsToGen.OptionsBuilderExt_Any)) { return; } @@ -24,7 +22,7 @@ private void EmitBindingExtensions_OptionsBuilder() private void EmitBindMethods_Extensions_OptionsBuilder() { - if (!ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Bind)) + if (!ShouldEmitMethods(MethodsToGen.OptionsBuilderExt_Bind)) { return; } @@ -32,15 +30,15 @@ private void EmitBindMethods_Extensions_OptionsBuilder() const string documentation = @"/// Registers a configuration instance which will bind against."; const string paramList = $"{Identifier.IConfiguration} {Identifier.config}"; - if (ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.Bind_T)) + if (ShouldEmitMethods(MethodsToGen.OptionsBuilderExt_Bind_T)) { - EmitMethodStartBlock(MethodsToGen_Extensions_OptionsBuilder.Bind_T, "Bind", paramList, documentation); + EmitMethodStartBlock(MethodsToGen.OptionsBuilderExt_Bind_T, "Bind", paramList, documentation); _writer.WriteLine($"return Bind({Identifier.optionsBuilder}, {Identifier.config}, {Identifier.configureBinder}: null);"); EmitEndBlock(); } EmitMethodStartBlock( - MethodsToGen_Extensions_OptionsBuilder.Bind_T_BinderOptions, + MethodsToGen.OptionsBuilderExt_Bind_T_BinderOptions, "Bind", paramList + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureBinder}", documentation); @@ -57,7 +55,7 @@ private void EmitBindMethods_Extensions_OptionsBuilder() private void EmitBindConfigurationMethod() { - if (!ShouldEmitMethods(MethodsToGen_Extensions_OptionsBuilder.BindConfiguration_T_path_BinderOptions)) + if (!ShouldEmitMethods(MethodsToGen.OptionsBuilderExt_BindConfiguration_T_path_BinderOptions)) { return; } @@ -65,7 +63,7 @@ private void EmitBindConfigurationMethod() 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.configureBinder} = null"; - EmitMethodStartBlock(MethodsToGen_Extensions_OptionsBuilder.BindConfiguration, "BindConfiguration", paramList, documentation); + EmitMethodStartBlock(MethodsToGen.OptionsBuilderExt_BindConfiguration, "BindConfiguration", paramList, documentation); EmitCheckForNullArgument_WithBlankLine(Identifier.optionsBuilder); EmitCheckForNullArgument_WithBlankLine(Identifier.configSectionPath); @@ -89,7 +87,7 @@ private void EmitBindConfigurationMethod() EmitEndBlock(); } - private void EmitMethodStartBlock(MethodsToGen_Extensions_OptionsBuilder method, string methodName, string paramList, string documentation) + private void EmitMethodStartBlock(MethodsToGen method, string methodName, string paramList, string documentation) { paramList = $"this {TypeDisplayString.OptionsBuilderOfTOptions} {Identifier.optionsBuilder}, {paramList}"; EmitBlankLineIfRequired(); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsConfigurationServiceCollectionExtensions.cs index 7577e0c49de4d..daa3b79db8abc 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/OptionsConfigurationServiceCollectionExtensions.cs @@ -7,11 +7,9 @@ public sealed partial class ConfigurationBindingGenerator { private sealed partial class Emitter { - private bool ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection methods) => (_sourceGenSpec.MethodsToGen_ServiceCollectionExt & methods) != 0; - private void EmitBindingExtensions_IServiceCollection() { - if (!ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Any)) + if (!ShouldEmitMethods(MethodsToGen.ServiceCollectionExt_Any)) { return; } @@ -26,26 +24,26 @@ private void EmitConfigureMethods() const string defaultNameExpr = "string.Empty"; string configParam = $"{Identifier.IConfiguration} {Identifier.config}"; - if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T)) + if (ShouldEmitMethods(MethodsToGen.ServiceCollectionExt_Configure_T)) { - EmitStartMethod(MethodsToGen_Extensions_ServiceCollection.Configure_T, configParam); + EmitStartMethod(MethodsToGen.ServiceCollectionExt_Configure_T, configParam); _writer.WriteLine($"return {Identifier.Configure}<{Identifier.TOptions}>({Identifier.services}, {defaultNameExpr}, {Identifier.config}, {Identifier.configureOptions}: null);"); EmitEndBlock(); } - if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T_name)) + if (ShouldEmitMethods(MethodsToGen.ServiceCollectionExt_Configure_T_name)) { EmitStartMethod( - MethodsToGen_Extensions_ServiceCollection.Configure_T_name, + MethodsToGen.ServiceCollectionExt_Configure_T_name, paramList: $"string? {Identifier.name}, " + configParam); _writer.WriteLine($"return {Identifier.Configure}<{Identifier.TOptions}>({Identifier.services}, {Identifier.name}, {Identifier.config}, {Identifier.configureOptions}: null);"); EmitEndBlock(); } - if (ShouldEmitMethods(MethodsToGen_Extensions_ServiceCollection.Configure_T_BinderOptions)) + if (ShouldEmitMethods(MethodsToGen.ServiceCollectionExt_Configure_T_BinderOptions)) { EmitStartMethod( - MethodsToGen_Extensions_ServiceCollection.Configure_T_BinderOptions, + MethodsToGen.ServiceCollectionExt_Configure_T_BinderOptions, paramList: configParam + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}"); _writer.WriteLine($"return {Identifier.Configure}<{Identifier.TOptions}>({Identifier.services}, {defaultNameExpr}, {Identifier.config}, {Identifier.configureOptions});"); EmitEndBlock(); @@ -54,7 +52,7 @@ private void EmitConfigureMethods() // 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. - EmitStartMethod(MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions, paramList: $"string? {Identifier.name}, " + configParam + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}"); + EmitStartMethod(MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions, paramList: $"string? {Identifier.name}, " + configParam + $", {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}"); EmitCheckForNullArgument_WithBlankLine(Identifier.services); EmitCheckForNullArgument_WithBlankLine(Identifier.config); _writer.WriteLine($$""" @@ -65,7 +63,7 @@ private void EmitConfigureMethods() EmitEndBlock(); } - private void EmitStartMethod(MethodsToGen_Extensions_ServiceCollection overload, string paramList) + private void EmitStartMethod(MethodsToGen overload, string paramList) { paramList = $"this {Identifier.IServiceCollection} {Identifier.services}, {paramList}"; 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 ec9cd28569443..f72a9f4f9a3e8 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 @@ -1,6 +1,7 @@ netstandard2.0 + $(NetCoreAppToolCurrent) false false true @@ -14,6 +15,7 @@ + @@ -40,15 +42,15 @@ + - - + + - diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/BinderInvocation.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/BinderInvocation.cs index cb978b56091b7..b1cf51acb3b4a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/BinderInvocation.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/BinderInvocation.cs @@ -9,8 +9,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal sealed record BinderInvocation(IInvocationOperation Operation, Location Location) + internal sealed class BinderInvocation { + private BinderInvocation(IInvocationOperation operation, Location location) + { + Operation = operation; + Location = location; + } + + public IInvocationOperation Operation { get; } + public Location Location { get; } + public static BinderInvocation? Create(GeneratorSyntaxContext context, CancellationToken cancellationToken) { Debug.Assert(IsCandidateSyntaxNode(context.Node)); @@ -35,8 +44,8 @@ public static bool IsCandidateSyntaxNode(SyntaxNode node) } && IsCandidateBindingMethodName(memberName); static bool IsCandidateBindingMethodName(string name) => - IsCandidateMethodName_ConfigurationBinder(name) || - IsCandidateMethodName_OptionsBuilderConfigurationExtensions(name) || + IsValidMethodName_ConfigurationBinder(name) || + IsValidMethodName_OptionsBuilderConfigurationExtensions(name) || IsValidMethodName_OptionsConfigurationServiceCollectionExtensions(name); } @@ -62,10 +71,10 @@ public static bool IsBindingOperation(IInvocationOperation operation) { "ConfigurationBinder" => containingNamespaceName is "Microsoft.Extensions.Configuration" && - IsCandidateMethodName_ConfigurationBinder(methodName), + IsValidMethodName_ConfigurationBinder(methodName), "OptionsBuilderConfigurationExtensions" => containingNamespaceName is "Microsoft.Extensions.DependencyInjection" && - IsCandidateMethodName_OptionsBuilderConfigurationExtensions(methodName), + IsValidMethodName_OptionsBuilderConfigurationExtensions(methodName), "OptionsConfigurationServiceCollectionExtensions" => containingNamespaceName is "Microsoft.Extensions.DependencyInjection" && IsValidMethodName_OptionsConfigurationServiceCollectionExtensions(methodName), @@ -73,16 +82,10 @@ containingNamespaceName is "Microsoft.Extensions.DependencyInjection" && }; } - private static bool IsCandidateMethodName_ConfigurationBinder(string name) => name is - nameof(MethodsToGen_ConfigurationBinder.Bind) or - nameof(MethodsToGen_ConfigurationBinder.Get) or - nameof(MethodsToGen_ConfigurationBinder.GetValue); + private static bool IsValidMethodName_ConfigurationBinder(string name) => name is "Bind" or "Get" or "GetValue"; - private static bool IsCandidateMethodName_OptionsBuilderConfigurationExtensions(string name) => name is - nameof(MethodsToGen_Extensions_OptionsBuilder.Bind) or - nameof(MethodsToGen_Extensions_OptionsBuilder.BindConfiguration); + private static bool IsValidMethodName_OptionsBuilderConfigurationExtensions(string name) => name is "Bind" or "BindConfiguration"; - private static bool IsValidMethodName_OptionsConfigurationServiceCollectionExtensions(string name) => name is - nameof(MethodsToGen_Extensions_ServiceCollection.Configure); + private static bool IsValidMethodName_OptionsConfigurationServiceCollectionExtensions(string name) => name is "Configure"; } -} \ No newline at end of file +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs index 895f79b170225..de8e32054e1c7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs @@ -3,33 +3,31 @@ using System; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis; -using DotnetRuntime.SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed partial class ConfigurationBindingGenerator { - private sealed partial class Parser + internal sealed partial class Parser { private void ParseInvocation_ConfigurationBinder(BinderInvocation invocation) { switch (invocation.Operation.TargetMethod.Name) { - case nameof(MethodsToGen_ConfigurationBinder.Bind): + case "Bind": { ParseBindInvocation_ConfigurationBinder(invocation); } break; - case nameof(MethodsToGen_ConfigurationBinder.Get): + case "Get": { ParseGetInvocation(invocation); } break; - case nameof(MethodsToGen_ConfigurationBinder.GetValue): + case "GetValue": { ParseGetValueInvocation(invocation); } @@ -48,39 +46,39 @@ private void ParseBindInvocation_ConfigurationBinder(BinderInvocation invocation return; } - MethodsToGen_ConfigurationBinder overload = MethodsToGen_ConfigurationBinder.None; + MethodsToGen overload = MethodsToGen.None; if (paramCount is 2) { - overload = MethodsToGen_ConfigurationBinder.Bind_instance; + overload = MethodsToGen.ConfigBinder_Bind_instance; } else if (paramCount is 3) { if (@params[1].Type.SpecialType is SpecialType.System_String) { - overload = MethodsToGen_ConfigurationBinder.Bind_key_instance; + overload = MethodsToGen.ConfigBinder_Bind_key_instance; } else if (SymbolEqualityComparer.Default.Equals(@params[2].Type, _typeSymbols.ActionOfBinderOptions)) { - overload = MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions; + overload = MethodsToGen.ConfigBinder_Bind_instance_BinderOptions; } } - if (overload is MethodsToGen_ConfigurationBinder.None) + if (overload is MethodsToGen.None) { return; } int instanceIndex = overload switch { - MethodsToGen_ConfigurationBinder.Bind_instance => 1, - MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions => 1, - MethodsToGen_ConfigurationBinder.Bind_key_instance => 2, + MethodsToGen.ConfigBinder_Bind_instance => 1, + MethodsToGen.ConfigBinder_Bind_instance_BinderOptions => 1, + MethodsToGen.ConfigBinder_Bind_key_instance => 2, _ => throw new InvalidOperationException() }; IArgumentOperation instanceArg = GetArgumentForParameterAtIndex(operation.Arguments, instanceIndex); - if (instanceArg.Parameter.Type.SpecialType != SpecialType.System_Object) + if (instanceArg.Parameter?.Type.SpecialType is not SpecialType.System_Object) { return; } @@ -89,19 +87,19 @@ private void ParseBindInvocation_ConfigurationBinder(BinderInvocation invocation if (!IsValidRootConfigType(type)) { - Diagnostics.Register(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); + ReportDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); return; } - if (type!.IsValueType) + if (type.IsValueType) { - Diagnostics.Register(DiagnosticDescriptors.InvalidBindInput, invocation.Location, type); + ReportDiagnostic(DiagnosticDescriptors.ValueTypesInvalidForBind, invocation.Location, type); return; } - if (GetTargetTypeForRootInvocationCore(type, invocation.Location) is TypeSpec typeSpec) + if (GetTargetTypeForRootInvocationCore(type, invocation.Location) is ComplexTypeSpec typeSpec) { - RegisterInterceptor(overload, typeSpec, invocation.Operation); + _interceptorInfoBuilder.RegisterInterceptor_ConfigBinder_Bind(overload, typeSpec, invocation.Operation); } static ITypeSymbol? ResolveType(IOperation conversionOperation) => @@ -146,7 +144,7 @@ private void ParseGetInvocation(BinderInvocation invocation) return; } - MethodsToGen_ConfigurationBinder overload = MethodsToGen_ConfigurationBinder.None; + MethodsToGen overload = MethodsToGen.None; ITypeSymbol? type; if (targetMethod.IsGenericMethod) @@ -160,11 +158,11 @@ private void ParseGetInvocation(BinderInvocation invocation) if (paramCount is 1) { - overload = MethodsToGen_ConfigurationBinder.Get_T; + overload = MethodsToGen.ConfigBinder_Get_T; } else if (paramCount is 2 && SymbolEqualityComparer.Default.Equals(@params[1].Type, _typeSymbols.ActionOfBinderOptions)) { - overload = MethodsToGen_ConfigurationBinder.Get_T_BinderOptions; + overload = MethodsToGen.ConfigBinder_Get_T_BinderOptions; } } else if (paramCount > 3) @@ -178,18 +176,18 @@ private void ParseGetInvocation(BinderInvocation invocation) if (paramCount is 2) { - overload = MethodsToGen_ConfigurationBinder.Get_TypeOf; + overload = MethodsToGen.ConfigBinder_Get_TypeOf; } else if (paramCount is 3 && SymbolEqualityComparer.Default.Equals(@params[2].Type, _typeSymbols.ActionOfBinderOptions)) { - overload = MethodsToGen_ConfigurationBinder.Get_TypeOf_BinderOptions; + overload = MethodsToGen.ConfigBinder_Get_TypeOf_BinderOptions; } } if (GetTargetTypeForRootInvocation(type, invocation.Location) is TypeSpec typeSpec) { - RegisterInvocation(overload, invocation.Operation); - RegisterTypeForGetCoreGen(typeSpec); + _interceptorInfoBuilder.RegisterInterceptor(overload, operation); + _helperInfoBuilder.RegisterTypeForGetCoreGen(typeSpec); } } @@ -201,7 +199,7 @@ private void ParseGetValueInvocation(BinderInvocation invocation) ImmutableArray @params = targetMethod.Parameters; int paramCount = @params.Length; - MethodsToGen_ConfigurationBinder overload = MethodsToGen_ConfigurationBinder.None; + MethodsToGen overload = MethodsToGen.None; ITypeSymbol? type; if (targetMethod.IsGenericMethod) @@ -215,11 +213,11 @@ private void ParseGetValueInvocation(BinderInvocation invocation) if (paramCount is 2) { - overload = MethodsToGen_ConfigurationBinder.GetValue_T_key; + overload = MethodsToGen.ConfigBinder_GetValue_T_key; } else if (paramCount is 3 && SymbolEqualityComparer.Default.Equals(@params[2].Type, type)) { - overload = MethodsToGen_ConfigurationBinder.GetValue_T_key_defaultValue; + overload = MethodsToGen.ConfigBinder_GetValue_T_key_defaultValue; } } else if (paramCount > 4) @@ -238,45 +236,29 @@ private void ParseGetValueInvocation(BinderInvocation invocation) if (paramCount is 3) { - overload = MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key; + overload = MethodsToGen.ConfigBinder_GetValue_TypeOf_key; } else if (paramCount is 4 && @params[3].Type.SpecialType is SpecialType.System_Object) { - overload = MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key_defaultValue; + overload = MethodsToGen.ConfigBinder_GetValue_TypeOf_key_defaultValue; } } - ITypeSymbol effectiveType = (IsNullable(type, out ITypeSymbol? underlyingType) ? underlyingType : type)!; - if (!IsValidRootConfigType(type)) { - Diagnostics.Register(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); + ReportDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); return; } - if (IsParsableFromString(effectiveType, out _)) + ITypeSymbol effectiveType = IsNullable(type, out ITypeSymbol? underlyingType) ? underlyingType : type; + + if (IsParsableFromString(effectiveType, out _) && + GetTargetTypeForRootInvocationCore(type, invocation.Location) is TypeSpec typeSpec) { - RegisterInvocation(overload, invocation.Operation); - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, typeSpec); + _interceptorInfoBuilder.RegisterInterceptor(overload, operation); + _helperInfoBuilder.RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, typeSpec); } } - - private void RegisterInvocation(MethodsToGen_ConfigurationBinder overload, IInvocationOperation operation) - { - _sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; - RegisterInterceptor(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 RegisterInterceptor(MethodsToGen_ConfigurationBinder overload, TypeSpec typeSpec, IInvocationOperation operation) - { - _sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload; - _interceptionInfo_ConfigBinder.RegisterOverloadInfo(overload, typeSpec, operation); - } } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs index f2e61930efa5f..59d3d3afa28d0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed partial class ConfigurationBindingGenerator { - private sealed partial class Parser + internal sealed partial class Parser { private static class DiagnosticDescriptors { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs index fa0b3691ec404..3f6b2aec1b8d4 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs @@ -16,6 +16,12 @@ internal static class ParserExtensions genericsOptions: SymbolDisplayGenericsOptions.None, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + private static readonly SymbolDisplayFormat s_minimalDisplayFormat = new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + public static void RegisterCacheEntry(this Dictionary cache, TKey key, TEntry entry) where TKey : notnull where TValue : ICollection, new() @@ -28,12 +34,6 @@ public static void RegisterCacheEntry(this Dictionary> source, out ComplexTypeSpec Key, out List Value) - { - Key = (ComplexTypeSpec)source.Key; - Value = source.Value; - } - public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type) { if (type is IArrayTypeSymbol arrayType) @@ -64,5 +64,13 @@ public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type) return sb.ToString(); } + + public static (string? Namespace, string DisplayString, string Name) GetTypeName(this ITypeSymbol type) + { + string? @namespace = type.ContainingNamespace?.ToDisplayString(); + string displayString = type.ToDisplayString(s_minimalDisplayFormat); + string name = (@namespace is null ? string.Empty : @namespace + ".") + displayString.Replace(".", "+"); + return (@namespace, displayString, name); + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/KnownTypeSymbols.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs similarity index 96% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/KnownTypeSymbols.cs rename to src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs index e381dc9c7c43e..07dae8689782e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/KnownTypeSymbols.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal sealed record KnownTypeSymbols + internal sealed class KnownTypeSymbols { public CSharpCompilation Compilation { get; } @@ -37,7 +37,7 @@ internal sealed record KnownTypeSymbols public INamedTypeSymbol? OptionsConfigurationServiceCollectionExtensions { get; } public INamedTypeSymbol GenericIList_Unbound { get; } - public INamedTypeSymbol GenericICollection_Unbound { get; } + public INamedTypeSymbol? GenericICollection_Unbound { get; } public INamedTypeSymbol GenericICollection { get; } public INamedTypeSymbol GenericIEnumerable_Unbound { get; } public INamedTypeSymbol IEnumerable { get; } @@ -61,7 +61,8 @@ public KnownTypeSymbols(CSharpCompilation compilation) { Compilation = compilation; - // Primitives (needed because they are Microsoft.CodeAnalysis.SpecialType.None) + // Primitives + String = compilation.GetSpecialType(SpecialType.System_String); CultureInfo = compilation.GetBestTypeByMetadataName(typeof(CultureInfo)); DateOnly = compilation.GetBestTypeByMetadataName("System.DateOnly"); DateTimeOffset = compilation.GetBestTypeByMetadataName(typeof(DateTimeOffset)); @@ -103,7 +104,7 @@ public KnownTypeSymbols(CSharpCompilation compilation) // Used for type equivalency checks for unbound generics. The parameters of the types // retured by the Roslyn Get*Type* APIs are not unbound, so we construct unbound // generics to equal those corresponding to generic types in the input type graphs. - GenericICollection_Unbound = GenericICollection?.ConstructUnboundGenericType(); + GenericICollection_Unbound = GenericICollection.ConstructUnboundGenericType(); GenericIDictionary_Unbound = GenericIDictionary?.ConstructUnboundGenericType(); GenericIEnumerable_Unbound = compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T).ConstructUnboundGenericType(); GenericIList_Unbound = compilation.GetSpecialType(SpecialType.System_Collections_Generic_IList_T).ConstructUnboundGenericType(); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs index 9cf59a120e1fd..23aed6a7ae364 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs @@ -10,7 +10,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed partial class ConfigurationBindingGenerator { - private sealed partial class Parser + internal sealed partial class Parser { private void ParseInvocation_OptionsBuilderExt(BinderInvocation invocation) { @@ -58,16 +58,16 @@ private void ParseBindInvocation_OptionsBuilderExt(BinderInvocation invocation, return; } - MethodsToGen_Extensions_OptionsBuilder overload = paramCount switch + MethodsToGen overload = paramCount switch { - 2 => MethodsToGen_Extensions_OptionsBuilder.Bind_T, + 2 => MethodsToGen.OptionsBuilderExt_Bind_T, 3 when SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type) => - MethodsToGen_Extensions_OptionsBuilder.Bind_T_BinderOptions, - _ => MethodsToGen_Extensions_OptionsBuilder.None + MethodsToGen.OptionsBuilderExt_Bind_T_BinderOptions, + _ => MethodsToGen.None }; - if (overload is not MethodsToGen_Extensions_OptionsBuilder.None && - TryRegisterTypeForMethodGen(MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions, typeSpec)) + if (overload is not MethodsToGen.None && + TryRegisterTypeForMethodGen(MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions, typeSpec)) { RegisterInvocation(overload, operation); } @@ -84,22 +84,21 @@ private void ParseBindConfigurationInvocation(BinderInvocation invocation, Compl if (paramCount is 3 && @params[1].Type.SpecialType is SpecialType.System_String && SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type) && - TryRegisterTypeForBindCoreMainGen(typeSpec)) + _helperInfoBuilder.TryRegisterTypeForBindCoreMainGen(typeSpec)) { - RegisterInvocation(MethodsToGen_Extensions_OptionsBuilder.BindConfiguration_T_path_BinderOptions, invocation.Operation); + RegisterInvocation(MethodsToGen.OptionsBuilderExt_BindConfiguration_T_path_BinderOptions, invocation.Operation); } } - private void RegisterInvocation(MethodsToGen_Extensions_OptionsBuilder overload, IInvocationOperation operation) + private void RegisterInvocation(MethodsToGen overload, IInvocationOperation operation) { - _sourceGenSpec.MethodsToGen_OptionsBuilderExt |= overload; - RegisterInterceptor(overload, operation); + _interceptorInfoBuilder.RegisterInterceptor(overload, operation); // Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource. - _sourceGenSpec.Namespaces.Add("Microsoft.Extensions.Options"); + _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.Options"); // Emitting refs to OptionsBuilder. - _sourceGenSpec.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); + _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs index 11b81c6b6c801..3e7a5cd086403 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs @@ -1,18 +1,16 @@ // 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.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; -using DotnetRuntime.SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed partial class ConfigurationBindingGenerator { - private sealed partial class Parser + internal sealed partial class Parser { private void ParseInvocation_ServiceCollectionExt(BinderInvocation invocation) { @@ -32,11 +30,11 @@ private void ParseInvocation_ServiceCollectionExt(BinderInvocation invocation) return; } - MethodsToGen_Extensions_ServiceCollection overload; + MethodsToGen overload; if (paramCount is 2 && SymbolEqualityComparer.Default.Equals(_typeSymbols.IConfiguration, @params[1].Type)) { - overload = MethodsToGen_Extensions_ServiceCollection.Configure_T; + overload = MethodsToGen.ServiceCollectionExt_Configure_T; } else if (paramCount is 3) { @@ -46,12 +44,12 @@ private void ParseInvocation_ServiceCollectionExt(BinderInvocation invocation) if (secondParamType.SpecialType is SpecialType.System_String && SymbolEqualityComparer.Default.Equals(_typeSymbols.IConfiguration, thirdParamType)) { - overload = MethodsToGen_Extensions_ServiceCollection.Configure_T_name; + overload = MethodsToGen.ServiceCollectionExt_Configure_T_name; } else if (SymbolEqualityComparer.Default.Equals(_typeSymbols.IConfiguration, secondParamType) && SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, thirdParamType)) { - overload = MethodsToGen_Extensions_ServiceCollection.Configure_T_BinderOptions; + overload = MethodsToGen.ServiceCollectionExt_Configure_T_BinderOptions; } else { @@ -63,7 +61,7 @@ @params[1].Type.SpecialType is SpecialType.System_String && SymbolEqualityComparer.Default.Equals(_typeSymbols.IConfiguration, @params[2].Type) && SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[3].Type)) { - overload = MethodsToGen_Extensions_ServiceCollection.Configure_T_name_BinderOptions; + overload = MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions; } else { @@ -78,18 +76,19 @@ @params[1].Type.SpecialType is SpecialType.System_String && if (GetTargetTypeForRootInvocation(typeSymbol, invocation.Location) is ComplexTypeSpec typeSpec && TryRegisterTypeForMethodGen(overload, typeSpec)) { - RegisterInterceptor(overload, operation); + _interceptorInfoBuilder.RegisterInterceptor(overload, operation); } } - private bool TryRegisterTypeForMethodGen(MethodsToGen_Extensions_ServiceCollection overload, ComplexTypeSpec typeSpec) + private bool TryRegisterTypeForMethodGen(MethodsToGen overload, ComplexTypeSpec typeSpec) { - if (TryRegisterTypeForBindCoreMainGen(typeSpec)) + Debug.Assert((MethodsToGen.ServiceCollectionExt_Any & overload) is not 0); + if (_helperInfoBuilder.TryRegisterTypeForBindCoreMainGen(typeSpec)) { - _sourceGenSpec.MethodsToGen_ServiceCollectionExt |= overload; - _sourceGenSpec.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); + _interceptorInfoBuilder.MethodsToGen |= overload; + _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); // Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource, IConfigureOptions<>, ConfigureNamedOptions<>. - _sourceGenSpec.Namespaces.Add("Microsoft.Extensions.Options"); + _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.Options"); return true; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs new file mode 100644 index 0000000000000..f1eaea3ab588e --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs @@ -0,0 +1,146 @@ +// 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.Linq; +using SourceGenerators; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + internal sealed record BindingHelperInfo + { + public required ImmutableEquatableArray Namespaces { get; init; } + public required bool EmitConfigurationKeyCaches { get; init; } + + public required MethodsToGen_CoreBindingHelper MethodsToGen { get; init; } + public required ImmutableEquatableArray? TypesForGen_BindCoreMain { get; init; } + public required ImmutableEquatableArray? TypesForGen_GetCore { get; init; } + public required ImmutableEquatableArray? TypesForGen_GetValueCore { get; init; } + public required ImmutableEquatableArray? TypesForGen_BindCore { get; init; } + public required ImmutableEquatableArray? TypesForGen_Initialize { get; init; } + public required ImmutableEquatableArray? TypesForGen_ParsePrimitive { get; init; } + + public sealed class Builder + { + private MethodsToGen_CoreBindingHelper _methodsToGen; + private bool _emitConfigurationKeyCaches; + + private readonly Dictionary> _typesForGen = new(); + + public SortedSet Namespaces { get; } = new() + { + "System", + "System.CodeDom.Compiler", + "System.Globalization", + "System.Runtime.CompilerServices", + "Microsoft.Extensions.Configuration", + }; + + public bool TryRegisterTypeForBindCoreMainGen(ComplexTypeSpec type) + { + if (type.HasBindableMembers) + { + bool registeredForBindCoreGen = TryRegisterTypeForBindCoreGen(type); + Debug.Assert(registeredForBindCoreGen); + + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreMain, type); + Register_AsConfigWithChildren_HelperForGen_IfRequired(type); + return true; + } + + return false; + } + + public bool TryRegisterTypeForBindCoreGen(ComplexTypeSpec type) + { + if (type.HasBindableMembers) + { + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, type); + + if (type is ObjectSpec) + { + _emitConfigurationKeyCaches = true; + } + + return true; + } + + return false; + } + + public void RegisterTypeForGetCoreGen(TypeSpec type) + { + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, type); + Register_AsConfigWithChildren_HelperForGen_IfRequired(type); + } + + public void RegisterStringParsableType(ParsableFromStringSpec type) + { + if (type.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) + { + _methodsToGen |= MethodsToGen_CoreBindingHelper.ParsePrimitive; + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.ParsePrimitive, type); + } + } + + public void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type) + { + if (!_typesForGen.TryGetValue(method, out HashSet? types)) + { + _typesForGen[method] = types = new HashSet(); + } + + types.Add(type); + _methodsToGen |= method; + } + + public void Register_AsConfigWithChildren_HelperForGen_IfRequired(TypeSpec type) + { + if (type is ComplexTypeSpec) + { + _methodsToGen |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren; + } + } + + public BindingHelperInfo ToIncrementalValue() + { + return new BindingHelperInfo + { + Namespaces = Namespaces.ToImmutableEquatableArray(), + EmitConfigurationKeyCaches = _emitConfigurationKeyCaches, + + MethodsToGen = _methodsToGen, + TypesForGen_GetCore = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.GetCore), + TypesForGen_BindCoreMain = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.BindCoreMain), + TypesForGen_GetValueCore = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.GetValueCore), + TypesForGen_BindCore = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.BindCore), + TypesForGen_Initialize = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.Initialize), + TypesForGen_ParsePrimitive = GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper.ParsePrimitive) + }; + + ImmutableEquatableArray? GetTypesForGen_CoreBindingHelper(MethodsToGen_CoreBindingHelper overload) + where TSpec : TypeSpec, IEquatable + { + _typesForGen.TryGetValue(overload, out HashSet? typesAsBase); + + if (typesAsBase is null) + { + return null; + } + + IEnumerable types = typeof(TSpec) == typeof(TypeSpec) + ? (HashSet)(object)typesAsBase + : typesAsBase.Select(t => (TSpec)t); + + return GetTypesForGen(types); + } + + static ImmutableEquatableArray GetTypesForGen(IEnumerable types) + where TSpec : TypeSpec, IEquatable => + types.OrderBy(t => t.AssemblyQualifiedName).ToImmutableEquatableArray(); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs new file mode 100644 index 0000000000000..7e143d86b17e7 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs @@ -0,0 +1,197 @@ +// 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.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Text; +using SourceGenerators; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + internal sealed record InterceptorInfo + { + public required MethodsToGen MethodsToGen { get; init; } + + public required ImmutableEquatableArray? ConfigBinder_Bind_instance { get; init; } + public required ImmutableEquatableArray? ConfigBinder_Bind_instance_BinderOptions { get; init; } + public required ImmutableEquatableArray? ConfigBinder_Bind_key_instance { get; init; } + + + public required ImmutableEquatableArray? ConfigBinder { get; init; } + public required ImmutableEquatableArray? OptionsBuilderExt { get; init; } + public required ImmutableEquatableArray? ServiceCollectionExt { get; init; } + + public IEnumerable? GetInfo(MethodsToGen interceptor) + { + Debug.Assert((MethodsToGen.ConfigBinder_Bind & interceptor) is 0); + + ImmutableEquatableArray? infoList; + if ((MethodsToGen.ConfigBinder_Any ^ MethodsToGen.ConfigBinder_Bind & interceptor) is not 0) + { + infoList = ConfigBinder; + } + else if ((MethodsToGen.OptionsBuilderExt_Any & interceptor) is not 0) + { + infoList = OptionsBuilderExt; + } + else + { + Debug.Assert((MethodsToGen.ServiceCollectionExt_Any & interceptor) is not 0); + infoList = ServiceCollectionExt; + } + + return infoList?.Where(i => i.Interceptor == interceptor); + } + + internal sealed class Builder + { + private TypeInterceptorInfoBuildler? _configBinder_InfoBuilder_Bind_instance; + private TypeInterceptorInfoBuildler? _configBinder_InfoBuilder_Bind_instance_BinderOptions; + private TypeInterceptorInfoBuildler? _configBinder_InfoBuilder_Bind_key_instance; + + private List? _interceptors_configBinder; + private List? _interceptors_OptionsBuilderExt; + private List? _interceptors_serviceCollectionExt; + + public MethodsToGen MethodsToGen { get; set; } + + public void RegisterInterceptor_ConfigBinder_Bind(MethodsToGen overload, ComplexTypeSpec type, IInvocationOperation invocation) + { + Debug.Assert((MethodsToGen.ConfigBinder_Bind & overload) is not 0); + + switch (overload) + { + case MethodsToGen.ConfigBinder_Bind_instance: + RegisterInterceptor(ref _configBinder_InfoBuilder_Bind_instance); + break; + case MethodsToGen.ConfigBinder_Bind_instance_BinderOptions: + RegisterInterceptor(ref _configBinder_InfoBuilder_Bind_instance_BinderOptions); + break; + case MethodsToGen.ConfigBinder_Bind_key_instance: + RegisterInterceptor(ref _configBinder_InfoBuilder_Bind_key_instance); + break; + } + + MethodsToGen |= overload; + + void RegisterInterceptor(ref TypeInterceptorInfoBuildler? infoBuilder) => + (infoBuilder ??= new()).RegisterInterceptor(overload, type, invocation); + } + + public void RegisterInterceptor(MethodsToGen overload, IInvocationOperation operation) + { + Debug.Assert((MethodsToGen.ConfigBinder_Bind & overload) is 0); + + if ((MethodsToGen.ConfigBinder_Any ^ MethodsToGen.ConfigBinder_Bind & overload) is not 0) + { + RegisterInterceptor(ref _interceptors_configBinder); + } + else if ((MethodsToGen.OptionsBuilderExt_Any & overload) is not 0) + { + RegisterInterceptor(ref _interceptors_OptionsBuilderExt); + } + else + { + Debug.Assert((MethodsToGen.ServiceCollectionExt_Any & overload) is not 0); + RegisterInterceptor(ref _interceptors_serviceCollectionExt); + } + + MethodsToGen |= overload; + + void RegisterInterceptor(ref List? infoList) => + (infoList ??= new()).Add(new InvocationLocationInfo(overload, operation)); + } + + public InterceptorInfo ToIncrementalValue() => + new InterceptorInfo + { + MethodsToGen = MethodsToGen, + + ConfigBinder = _interceptors_configBinder?.OrderBy(i => i.FilePath).ToImmutableEquatableArray(), + OptionsBuilderExt = _interceptors_OptionsBuilderExt?.OrderBy(i => i.FilePath).ToImmutableEquatableArray(), + ServiceCollectionExt = _interceptors_serviceCollectionExt?.OrderBy(i => i.FilePath).ToImmutableEquatableArray(), + + ConfigBinder_Bind_instance = _configBinder_InfoBuilder_Bind_instance?.ToIncrementalValue(), + ConfigBinder_Bind_instance_BinderOptions = _configBinder_InfoBuilder_Bind_instance_BinderOptions?.ToIncrementalValue(), + ConfigBinder_Bind_key_instance = _configBinder_InfoBuilder_Bind_key_instance?.ToIncrementalValue(), + }; + } + } + + internal sealed class TypeInterceptorInfoBuildler + { + private readonly Dictionary _invocationInfoBuilderCache = new(); + + public void RegisterInterceptor(MethodsToGen overload, ComplexTypeSpec type, IInvocationOperation invocation) + { + if (!_invocationInfoBuilderCache.TryGetValue(type, out TypedInterceptorInvocationInfo.Builder? invocationInfoBuilder)) + { + _invocationInfoBuilderCache[type] = invocationInfoBuilder = new TypedInterceptorInvocationInfo.Builder(overload, type); + } + + invocationInfoBuilder.RegisterInvocation(invocation); + } + + public ImmutableEquatableArray? ToIncrementalValue() => + _invocationInfoBuilderCache.Values + .Select(b => b.ToIncrementalValue()) + .OrderBy(i => i.TargetType.AssemblyQualifiedName) + .ToImmutableEquatableArray(); + } + + internal sealed record TypedInterceptorInvocationInfo + { + public required ComplexTypeSpec TargetType { get; init; } + public required ImmutableEquatableArray Locations { get; init; } + + public void Deconstruct(out ComplexTypeSpec targetType, out ImmutableEquatableArray locations) => + (targetType, locations) = (TargetType, Locations); + + public sealed class Builder(MethodsToGen Overload, ComplexTypeSpec TargetType) + { + private readonly List _infoList = new(); + + public void RegisterInvocation(IInvocationOperation invocation) => + _infoList.Add(new InvocationLocationInfo(Overload, invocation)); + + public TypedInterceptorInvocationInfo ToIncrementalValue() => new() + { + TargetType = TargetType, + Locations = _infoList.OrderBy(i => i.FilePath).ToImmutableEquatableArray() + }; + } + } + + internal sealed record InvocationLocationInfo + { + public InvocationLocationInfo(MethodsToGen interceptor, IInvocationOperation invocation) + { + MemberAccessExpressionSyntax memberAccessExprSyntax = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)invocation.Syntax).Expression); + SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; + TextSpan memberNameSpan = memberAccessExprSyntax.Name.Span; + FileLinePositionSpan linePosSpan = operationSyntaxTree.GetLineSpan(memberNameSpan); + + Interceptor = interceptor; + LineNumber = linePosSpan.StartLinePosition.Line + 1; + CharacterNumber = linePosSpan.StartLinePosition.Character + 1; + FilePath = GetInterceptorFilePath(); + + // Use 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 + string GetInterceptorFilePath() + { + SourceReferenceResolver? sourceReferenceResolver = invocation.SemanticModel?.Compilation.Options.SourceReferenceResolver; + return sourceReferenceResolver?.NormalizePath(operationSyntaxTree.FilePath, baseFilePath: null) ?? operationSyntaxTree.FilePath; + } + } + + public MethodsToGen Interceptor { get; } + public string FilePath { get; } + public int LineNumber { get; } + public int CharacterNumber { get; } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorLocationInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorLocationInfo.cs deleted file mode 100644 index 441acbe6a7444..0000000000000 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorLocationInfo.cs +++ /dev/null @@ -1,89 +0,0 @@ -// 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) - { - MemberAccessExpressionSyntax memberAccessExprSyntax = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)operation.Syntax).Expression); - SyntaxTree operationSyntaxTree = operation.Syntax.SyntaxTree; - TextSpan memberNameSpan = memberAccessExprSyntax.Name.Span; - FileLinePositionSpan linePosSpan = operationSyntaxTree.GetLineSpan(memberNameSpan); - - LineNumber = linePosSpan.StartLinePosition.Line + 1; - CharacterNumber = linePosSpan.StartLinePosition.Character + 1; - FilePath = GetInterceptorFilePath(); - - // Use 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 - string GetInterceptorFilePath() - { - SourceReferenceResolver? sourceReferenceResolver = operation.SemanticModel?.Compilation.Options.SourceReferenceResolver; - return sourceReferenceResolver?.NormalizePath(operationSyntaxTree.FilePath, baseFilePath: null) ?? operationSyntaxTree.FilePath; - } - } - - public string FilePath { get; } - public int LineNumber { get; } - public int CharacterNumber { get; } - } - - 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 ArgumentOutOfRangeException(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/Specs/Members/MemberSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs index 0e85b0b8bd138..effd550482595 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs @@ -3,11 +3,9 @@ using System.Diagnostics; using Microsoft.CodeAnalysis; -using DotnetRuntime.SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - [DebuggerDisplay("Name={Name}, Type={TypeRef.Name}")] internal abstract record MemberSpec { public MemberSpec(ISymbol member) @@ -20,7 +18,7 @@ public MemberSpec(ISymbol member) public string Name { get; } public string DefaultValueExpr { get; protected set; } - public required TypeRef TypeRef { get; init; } + public required TypeSpec Type { get; init; } public required string ConfigurationKeyName { get; init; } public abstract bool CanGet { get; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/ParameterSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/ParameterSpec.cs index 0f17a6247f74d..d7e560ad3542f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/ParameterSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/ParameterSpec.cs @@ -14,7 +14,7 @@ public ParameterSpec(IParameterSymbol parameter) : base(parameter) if (parameter.HasExplicitDefaultValue) { - string formatted = SymbolDisplay.FormatPrimitive(parameter.ExplicitDefaultValue, quoteStrings: true, useHexadecimalNumbers: false); + string formatted = SymbolDisplay.FormatPrimitive(parameter.ExplicitDefaultValue!, quoteStrings: true, useHexadecimalNumbers: false); if (formatted is not "null") { DefaultValueExpr = formatted; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs index 197264f13d2d8..ff77a099c91c5 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs @@ -16,137 +16,130 @@ internal enum MethodsToGen_CoreBindingHelper Initialize = 0x10, HasValueOrChildren = 0x20, AsConfigWithChildren = 0x40, + ParsePrimitive = 0x80, } /// /// Methods on Microsoft.Extensions.Configuration.ConfigurationBinder /// [Flags] - internal enum MethodsToGen_ConfigurationBinder + internal enum MethodsToGen { None = 0x0, + Any = ConfigBinder_Any | OptionsBuilderExt_Any | ServiceCollectionExt_Any, + #region IConfiguration ext. method overloads: 0x1 - 0x400 /// /// Bind(IConfiguration, object?). /// - Bind_instance = 0x1, + ConfigBinder_Bind_instance = 0x1, /// /// Bind(IConfiguration, object?, Action?). /// - Bind_instance_BinderOptions = 0x2, + ConfigBinder_Bind_instance_BinderOptions = 0x2, /// /// Bind(IConfiguration, string, object?). /// - Bind_key_instance = 0x4, + ConfigBinder_Bind_key_instance = 0x4, /// /// Get(IConfiguration). /// - Get_T = 0x8, + ConfigBinder_Get_T = 0x8, /// /// Get(IConfiguration, Action?). /// - Get_T_BinderOptions = 0x10, + ConfigBinder_Get_T_BinderOptions = 0x10, /// /// Get(IConfiguration, Type). /// - Get_TypeOf = 0x20, + ConfigBinder_Get_TypeOf = 0x20, /// /// Get(IConfiguration, Type, Action?). /// - Get_TypeOf_BinderOptions = 0x40, + ConfigBinder_Get_TypeOf_BinderOptions = 0x40, /// /// GetValue(IConfiguration, Type, string). /// - GetValue_TypeOf_key = 0x80, + ConfigBinder_GetValue_TypeOf_key = 0x80, /// /// GetValue(IConfiguration, Type, object?). /// - GetValue_TypeOf_key_defaultValue = 0x100, + ConfigBinder_GetValue_TypeOf_key_defaultValue = 0x100, /// /// GetValue(IConfiguration, string). /// - GetValue_T_key = 0x200, + ConfigBinder_GetValue_T_key = 0x200, /// /// GetValue(IConfiguration, string, T). /// - GetValue_T_key_defaultValue = 0x400, + ConfigBinder_GetValue_T_key_defaultValue = 0x400, // Method groups - Bind = Bind_instance | Bind_instance_BinderOptions | Bind_key_instance, - Get = Get_T | Get_T_BinderOptions | Get_TypeOf | Get_TypeOf_BinderOptions, - GetValue = GetValue_T_key | GetValue_T_key_defaultValue | GetValue_TypeOf_key | GetValue_TypeOf_key_defaultValue, + ConfigBinder_Bind = ConfigBinder_Bind_instance | ConfigBinder_Bind_instance_BinderOptions | ConfigBinder_Bind_key_instance, + ConfigBinder_Get = ConfigBinder_Get_T | ConfigBinder_Get_T_BinderOptions | ConfigBinder_Get_TypeOf | ConfigBinder_Get_TypeOf_BinderOptions, + ConfigBinder_GetValue = ConfigBinder_GetValue_T_key | ConfigBinder_GetValue_T_key_defaultValue | ConfigBinder_GetValue_TypeOf_key | ConfigBinder_GetValue_TypeOf_key_defaultValue, - Any = Bind | Get | GetValue, - } - - [Flags] - internal enum MethodsToGen_Extensions_OptionsBuilder - { - None = 0x0, + ConfigBinder_Any = ConfigBinder_Bind | ConfigBinder_Get | ConfigBinder_GetValue, + #endregion ConfigurationBinder ext. method overloads. + #region OptionsBuilder ext. method overloads: 0x800 - 0x2000 /// /// Bind(OptionsBuilder, IConfiguration). /// - Bind_T = 0x1, + OptionsBuilderExt_Bind_T = 0x800, /// /// Bind(OptionsBuilder, IConfiguration, Action?). /// - Bind_T_BinderOptions = 0x2, + OptionsBuilderExt_Bind_T_BinderOptions = 0x1000, /// /// BindConfiguration(OptionsBuilder, string, Action?). /// - BindConfiguration_T_path_BinderOptions = 0x4, + OptionsBuilderExt_BindConfiguration_T_path_BinderOptions = 0x2000, // Method group. BindConfiguration_T is its own method group. - Bind = Bind_T | Bind_T_BinderOptions, - - BindConfiguration = BindConfiguration_T_path_BinderOptions, + OptionsBuilderExt_Bind = OptionsBuilderExt_Bind_T | OptionsBuilderExt_Bind_T_BinderOptions, - Any = Bind | BindConfiguration, - } + OptionsBuilderExt_BindConfiguration = OptionsBuilderExt_BindConfiguration_T_path_BinderOptions, - /// - /// Methods on Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions - /// - [Flags] - internal enum MethodsToGen_Extensions_ServiceCollection - { - None = 0x0, + OptionsBuilderExt_Any = OptionsBuilderExt_Bind | OptionsBuilderExt_BindConfiguration, + #endregion OptionsBuilder ext. method overloads. + #region IServiceCollection ext. method overloads: 0x4000 - 0x20000 /// /// Configure(IServiceCollection, IConfiguration). /// - Configure_T = 0x1, + ServiceCollectionExt_Configure_T = 0x4000, /// /// Configure(IServiceCollection, string, IConfiguration). /// - Configure_T_name = 0x2, + ServiceCollectionExt_Configure_T_name = 0x8000, /// /// Configure(IServiceCollection, IConfiguration, Action?). /// - Configure_T_BinderOptions = 0x4, + ServiceCollectionExt_Configure_T_BinderOptions = 0x10000, /// /// Configure(IServiceCollection, string, IConfiguration, Action?). /// - Configure_T_name_BinderOptions = 0x8, + ServiceCollectionExt_Configure_T_name_BinderOptions = 0x20000, - Configure = Configure_T | Configure_T_name | Configure_T_BinderOptions | Configure_T_name_BinderOptions, + ServiceCollectionExt_Configure = ServiceCollectionExt_Configure_T | ServiceCollectionExt_Configure_T_name | ServiceCollectionExt_Configure_T_BinderOptions | ServiceCollectionExt_Configure_T_name_BinderOptions, - Any = Configure, + ServiceCollectionExt_Any = ServiceCollectionExt_Configure, + #endregion IServiceCollection ext. method overloads: 0x4000 - 0x20000 } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs deleted file mode 100644 index 6f594a55f623d..0000000000000 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 SourceGenerators; - -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 HashSet PrimitivesForHelperGen { get; } = new(); - public HashSet Namespaces { get; } = new() - { - "System", - "System.CodeDom.Compiler", - "System.Globalization", - "System.Runtime.CompilerServices", - "Microsoft.Extensions.Configuration", - }; - - public MethodsToGen_CoreBindingHelper MethodsToGen_CoreBindingHelper { get; set; } - public MethodsToGen_ConfigurationBinder MethodsToGen_ConfigurationBinder { get; set; } - public MethodsToGen_Extensions_OptionsBuilder MethodsToGen_OptionsBuilderExt { get; set; } - public MethodsToGen_Extensions_ServiceCollection MethodsToGen_ServiceCollectionExt { get; set; } - - public required ImmutableEquatableArray TypeNamespaces { get; init; } - public required ImmutableEquatableArray TypeList { get; init; } - - public required ImmutableEquatableArray TypesForGen_ConfigurationBinder_Bind_instance { get; init; } - public required ImmutableEquatableArray TypesForGen_ConfigurationBinder_Bind_instance_BinderOptions { get; init; } - public required ImmutableEquatableArray TypesForGen_ConfigurationBinder_Bind_key_instance { get; init; } - - public required bool GraphContainsEnum { get; init; } - public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_BindCoreUntyped { get; init; } - public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_GetCore { get; init; } - public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_GetValueCore { get; init; } - public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_BindCore { get; init; } - public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_Initialize { get; init; } - public required ImmutableEquatableArray TypesForGen_CoreBindingHelper_ParsePrimitive { get; init; } - - // TODO: add ImmutableEquatableDictionary to be supplied by the parser. - // https://github.com/dotnet/runtime/issues/89318 - public Dictionary GetTypeIndex() => TypeList.ToDictionary(t => t.TypeRef); - } -} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs index 353c7b7f1640d..93745a1219511 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.CodeAnalysis; -using DotnetRuntime.SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -12,6 +11,8 @@ public CollectionSpec(ITypeSymbol type) : base(type) { } public sealed override bool CanInstantiate => TypeToInstantiate?.CanInstantiate ?? InstantiationStrategy is not InstantiationStrategy.None; + public sealed override required InstantiationStrategy InstantiationStrategy { get; set; } + public required TypeSpec ElementType { get; init; } public required CollectionPopulationStrategy PopulationStrategy { get; init; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ComplexTypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ComplexTypeSpec.cs index da5a5130141a5..7bcd0c65072a3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ComplexTypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ComplexTypeSpec.cs @@ -9,7 +9,7 @@ internal abstract record ComplexTypeSpec : TypeSpec { public ComplexTypeSpec(ITypeSymbol type) : base(type) { } - public InstantiationStrategy InstantiationStrategy { get; set; } + public virtual InstantiationStrategy InstantiationStrategy { get; set; } public sealed override bool CanBindTo => CanInstantiate || HasBindableMembers; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs index 865fc51f47837..3de6d7d465ad9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs @@ -2,14 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.CodeAnalysis; -using DotnetRuntime.SourceGenerators; -using System; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { internal sealed record NullableSpec : TypeSpec { - private readonly TypeRef _underlyingTypeRef; + private readonly TypeSpec _underlyingType; public NullableSpec(ITypeSymbol type, TypeSpec underlyingType) : base(type) => _underlyingType = underlyingType; @@ -19,8 +17,6 @@ internal sealed record NullableSpec : TypeSpec public override TypeSpecKind SpecKind => TypeSpecKind.Nullable; - public override TypeRef EffectiveTypeRef => _underlyingTypeRef; - - public override bool SkipBinding => false; + public override TypeSpec EffectiveType => _underlyingType; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs index 512acbd80228b..402453aa86527 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs @@ -1,10 +1,9 @@ // 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.Linq; using Microsoft.CodeAnalysis; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -14,13 +13,13 @@ public ObjectSpec(INamedTypeSymbol type) : base(type) { } public override TypeSpecKind SpecKind => TypeSpecKind.Object; - public override bool HasBindableMembers => Properties.Values.Any(p => p.ShouldBindTo); + public override bool HasBindableMembers => Properties?.Any(p => p.ShouldBindTo) is true; public override bool CanInstantiate => InstantiationStrategy is not InstantiationStrategy.None && InitExceptionMessage is null; - public ImmutableEquatableArray Properties { get; set; } + public ImmutableEquatableArray? Properties { get; set; } - public ImmutableEquatableArray ConstructorParameters { get; set; } + public ImmutableEquatableArray? ConstructorParameters { get; set; } public string? InitExceptionMessage { get; set; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs index dd785c406720d..2dfe08dc5f547 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using Microsoft.CodeAnalysis; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -27,8 +28,6 @@ internal sealed record ParsableFromStringSpec : SimpleTypeSpec { public ParsableFromStringSpec(ITypeSymbol type) : base(type) { } - public override bool SkipBinding => false; - public override TypeSpecKind SpecKind => TypeSpecKind.ParsableFromString; public required StringParsableTypeKind StringParsableTypeKind { get; init; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs index 651a40639f0ce..575afc75be766 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs @@ -9,23 +9,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration [DebuggerDisplay("Name={DisplayString}, Kind={SpecKind}")] internal abstract record TypeSpec { - private static readonly SymbolDisplayFormat s_minimalDisplayFormat = new SymbolDisplayFormat( - globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, - typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, - genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); - public TypeSpec(ITypeSymbol type) { - Namespace = type.ContainingNamespace?.ToDisplayString(); - DisplayString = type.ToDisplayString(s_minimalDisplayFormat); - Name = (Namespace is null ? string.Empty : Namespace + ".") + DisplayString.Replace(".", "+"); + (Namespace, DisplayString, Name) = type.GetTypeName(); + AssemblyQualifiedName = type.ContainingAssembly + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); IdentifierCompatibleSubstring = type.ToIdentifierCompatibleSubstring(); IsValueType = type.IsValueType; } public string Name { get; } + public string AssemblyQualifiedName { get; } + public string DisplayString { get; } public string IdentifierCompatibleSubstring { get; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs index 7e635c1f0bdaa..82edf0c86de34 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -1851,12 +1851,19 @@ public void RecursiveTypeGraphs_DirectRef() var obj = configuration.Get(); Assert.Equal("Hello", obj.MyString); +#if BUILDING_SOURCE_GENERATOR_TESTS + // Record type specs don't work well with recursive type graphs. This includes indirect + // transitive props. Current behavior is to drop all such members from binding. Make TODO to doc + // this as unsupported feature for v1, or to fix it in a follow-up to initial incremental PR. + Assert.Null(obj.MyClass); +#else var nested = obj.MyClass; Assert.Equal("World", nested.MyString); var deeplyNested = nested.MyClass; Assert.Equal("World", deeplyNested.MyString); Assert.Null(deeplyNested.MyClass); +#endif } [Fact] @@ -1880,12 +1887,19 @@ public void RecursiveTypeGraphs_IndirectRef() var obj = configuration.Get(); Assert.Equal("Hello", obj.MyString); +#if BUILDING_SOURCE_GENERATOR_TESTS + // Record type specs don't work well with recursive type graphs. This includes indirect + // transitive props. Current behavior is to drop all such members from binding. Make TODO to doc + // this as unsupported feature for v1, or to fix it in a follow-up to initial incremental PR. + Assert.Null(obj.MyList); +#else var nested = obj.MyList[0]; Assert.Equal("World", nested.MyString); var deeplyNested = nested.MyList[0]; Assert.Equal("World", deeplyNested.MyString); Assert.Null(deeplyNested.MyList); +#endif } [Fact] 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 fc0dda4b5b3ae..568eb56f9ed8b 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 @@ -86,16 +86,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { - instance.Add(ParseInt(value, () => section.Path)); + element = new Program.MyClass2(); } + instance[section.Key] = element; } } @@ -110,15 +111,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + if (section.Value is string value) { - element = new Program.MyClass2(); + instance.Add(ParseInt(value, () => section.Path)); } - instance[section.Key] = element; } } @@ -126,42 +126,42 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section0) { - instance.MyString = value0; + Dictionary? temp2 = instance.MyComplexDictionary; + temp2 ??= new Dictionary(); + BindCore(section0, ref temp2, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp2; } - if (configuration["MyInt"] is string value1) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section3) { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + Dictionary? temp5 = instance.MyDictionary; + temp5 ??= new Dictionary(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp5; } - else if (defaultValueIfNotFound) + + if (configuration["MyInt"] is string value6) { - instance.MyInt = default; + instance.MyInt = ParseInt(value6, () => configuration.GetSection("MyInt").Path); } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + else if (defaultValueIfNotFound) { - List? temp4 = instance.MyList; - temp4 ??= new List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section7) { - Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + List? temp9 = instance.MyList; + temp9 ??= new List(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp9; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + if (configuration["MyString"] is string value10) { - Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + instance.MyString = value10; } } 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 9cbc0a22c5c84..ba7b563ba83b2 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 @@ -50,16 +50,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { - instance.Add(ParseInt(value, () => section.Path)); + element = new Program.MyClass2(); } + instance[section.Key] = element; } } @@ -74,15 +75,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + if (section.Value is string value) { - element = new Program.MyClass2(); + instance.Add(ParseInt(value, () => section.Path)); } - instance[section.Key] = element; } } @@ -90,42 +90,42 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section0) { - instance.MyString = value0; + Dictionary? temp2 = instance.MyComplexDictionary; + temp2 ??= new Dictionary(); + BindCore(section0, ref temp2, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp2; } - if (configuration["MyInt"] is string value1) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section3) { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + Dictionary? temp5 = instance.MyDictionary; + temp5 ??= new Dictionary(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp5; } - else if (defaultValueIfNotFound) + + if (configuration["MyInt"] is string value6) { - instance.MyInt = default; + instance.MyInt = ParseInt(value6, () => configuration.GetSection("MyInt").Path); } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + else if (defaultValueIfNotFound) { - List? temp4 = instance.MyList; - temp4 ??= new List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section7) { - Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + List? temp9 = instance.MyList; + temp9 ??= new List(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp9; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + if (configuration["MyString"] is string value10) { - Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + instance.MyString = value10; } } 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 f3efa07fc0c5c..62564974fe31d 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 @@ -50,16 +50,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { - instance.Add(ParseInt(value, () => section.Path)); + element = new Program.MyClass2(); } + instance[section.Key] = element; } } @@ -74,15 +75,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + if (section.Value is string value) { - element = new Program.MyClass2(); + instance.Add(ParseInt(value, () => section.Path)); } - instance[section.Key] = element; } } @@ -90,42 +90,42 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section0) { - instance.MyString = value0; + Dictionary? temp2 = instance.MyComplexDictionary; + temp2 ??= new Dictionary(); + BindCore(section0, ref temp2, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp2; } - if (configuration["MyInt"] is string value1) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section3) { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + Dictionary? temp5 = instance.MyDictionary; + temp5 ??= new Dictionary(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp5; } - else if (defaultValueIfNotFound) + + if (configuration["MyInt"] is string value6) { - instance.MyInt = default; + instance.MyInt = ParseInt(value6, () => configuration.GetSection("MyInt").Path); } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + else if (defaultValueIfNotFound) { - List? temp4 = instance.MyList; - temp4 ??= new List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section7) { - Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + List? temp9 = instance.MyList; + temp9 ??= new List(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp9; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + if (configuration["MyString"] is string value10) { - Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + instance.MyString = value10; } } 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 89b82d31bc19a..84e94ea6470ac 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 @@ -50,16 +50,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { - instance.Add(ParseInt(value, () => section.Path)); + element = new Program.MyClass2(); } + instance[section.Key] = element; } } @@ -74,15 +75,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + if (section.Value is string value) { - element = new Program.MyClass2(); + instance.Add(ParseInt(value, () => section.Path)); } - instance[section.Key] = element; } } @@ -90,42 +90,42 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value0) + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section0) { - instance.MyString = value0; + Dictionary? temp2 = instance.MyComplexDictionary; + temp2 ??= new Dictionary(); + BindCore(section0, ref temp2, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp2; } - if (configuration["MyInt"] is string value1) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section3) { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + Dictionary? temp5 = instance.MyDictionary; + temp5 ??= new Dictionary(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp5; } - else if (defaultValueIfNotFound) + + if (configuration["MyInt"] is string value6) { - instance.MyInt = default; + instance.MyInt = ParseInt(value6, () => configuration.GetSection("MyInt").Path); } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + else if (defaultValueIfNotFound) { - List? temp4 = instance.MyList; - temp4 ??= new List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section7) { - Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; + List? temp9 = instance.MyList; + temp9 ??= new List(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp9; } - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + if (configuration["MyString"] is string value10) { - Dictionary? temp10 = instance.MyComplexDictionary; - temp10 ??= new Dictionary(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp10; + instance.MyString = value10; } } 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 5e7eeae29254a..fa23f19a0b882 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 @@ -48,7 +48,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #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_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyArray", "MyDictionary", "MyInt", "MyList", "MyString" }); private readonly static Lazy> s_configKeys_ProgramMyClass2 = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) @@ -81,17 +81,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { var temp2 = new List(); @@ -112,46 +101,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value4) + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section4) { - instance.MyString = value4; + int[]? temp6 = instance.MyArray; + temp6 ??= new int[0]; + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp6; } - if (configuration["MyInt"] is string value5) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section7) { - instance.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path); + Dictionary? temp9 = instance.MyDictionary; + temp9 ??= new Dictionary(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp9; } - else if (defaultValueIfNotFound) + + if (configuration["MyInt"] is string value10) { - instance.MyInt = default; + instance.MyInt = ParseInt(value10, () => configuration.GetSection("MyInt").Path); } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6) + else if (defaultValueIfNotFound) { - List? temp8 = instance.MyList; - temp8 ??= new List(); - BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp8; + instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section9) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section11) { - int[]? temp11 = instance.MyArray; - temp11 ??= new int[0]; - BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp11; + List? temp13 = instance.MyList; + temp13 ??= new List(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp13; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section12) + if (configuration["MyString"] is string value14) { - Dictionary? temp14 = instance.MyDictionary; - temp14 ??= new Dictionary(); - BindCore(section12, ref temp14, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp14; + instance.MyString = value14; } } 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 bf7e64bd31f90..c2037aecf05f0 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 @@ -61,35 +61,35 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - if (type == typeof(int)) + if (type == typeof(byte[])) { - return ParseInt(value, () => section.Path); + return ParseByteArray(value, () => section.Path); } else if (type == typeof(bool?)) { return ParseBool(value, () => section.Path); } - else if (type == typeof(byte[])) - { - return ParseByteArray(value, () => section.Path); - } else if (type == typeof(CultureInfo)) { return ParseCultureInfo(value, () => section.Path); } + else if (type == typeof(int)) + { + return ParseInt(value, () => section.Path); + } return null; } - public static int ParseInt(string value, Func getPath) + public static byte[] ParseByteArray(string value, Func getPath) { try { - return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return Convert.FromBase64String(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); } } @@ -105,27 +105,27 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static byte[] ParseByteArray(string value, Func getPath) + public static CultureInfo ParseCultureInfo(string value, Func getPath) { try { - return Convert.FromBase64String(value); + return CultureInfo.GetCultureInfo(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); } } - public static CultureInfo ParseCultureInfo(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return CultureInfo.GetCultureInfo(value); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); + 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.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt index 3fc5176bf50f0..d7fe71cdfff9c 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 @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #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_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyArray", "MyDictionary", "MyInt", "MyList", "MyString" }); public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) { @@ -62,17 +62,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { var temp1 = new List(); @@ -93,46 +82,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section3) { - instance.MyString = value3; + int[]? temp5 = instance.MyArray; + temp5 ??= new int[0]; + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp5; } - if (configuration["MyInt"] is string value4) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section6) { - instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + Dictionary? temp8 = instance.MyDictionary; + temp8 ??= new Dictionary(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp8; } - else if (defaultValueIfNotFound) + + if (configuration["MyInt"] is string value9) { - instance.MyInt = default; + instance.MyInt = ParseInt(value9, () => configuration.GetSection("MyInt").Path); } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + else if (defaultValueIfNotFound) { - List? temp7 = instance.MyList; - temp7 ??= new List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section8) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section10) { - int[]? temp10 = instance.MyArray; - temp10 ??= new int[0]; - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp10; + List? temp12 = instance.MyList; + temp12 ??= new List(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp12; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + if (configuration["MyString"] is string value13) { - Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + instance.MyString = value13; } } 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 81c23d7ceea65..0cfc4f33e8acc 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 @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #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_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyArray", "MyDictionary", "MyInt", "MyList", "MyString" }); public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) { @@ -62,17 +62,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { var temp1 = new List(); @@ -93,46 +82,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section3) { - instance.MyString = value3; + int[]? temp5 = instance.MyArray; + temp5 ??= new int[0]; + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp5; } - if (configuration["MyInt"] is string value4) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section6) { - instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + Dictionary? temp8 = instance.MyDictionary; + temp8 ??= new Dictionary(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp8; } - else if (defaultValueIfNotFound) + + if (configuration["MyInt"] is string value9) { - instance.MyInt = default; + instance.MyInt = ParseInt(value9, () => configuration.GetSection("MyInt").Path); } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + else if (defaultValueIfNotFound) { - List? temp7 = instance.MyList; - temp7 ??= new List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section8) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section10) { - int[]? temp10 = instance.MyArray; - temp10 ??= new int[0]; - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp10; + List? temp12 = instance.MyList; + temp12 ??= new List(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp12; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + if (configuration["MyString"] is string value13) { - Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + instance.MyString = value13; } } 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 44f1df2e78232..3ef9949f0e453 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 @@ -63,7 +63,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion OptionsBuilder extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt", "MyList", "MyString" }); public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -104,26 +104,26 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (configuration["MyInt"] is string value1) { - instance.MyString = value1; - } - - if (configuration["MyInt"] is string value2) - { - instance.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (configuration["MyString"] is string value5) { - List? temp5 = instance.MyList; - temp5 ??= new List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + instance.MyString = value5; } } 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 8d7c70a27b3f2..0fd5b77d800e6 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 @@ -73,7 +73,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt", "MyList", "MyString" }); public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -114,26 +114,26 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (configuration["MyInt"] is string value1) { - instance.MyString = value1; - } - - if (configuration["MyInt"] is string value2) - { - instance.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (configuration["MyString"] is string value5) { - List? temp5 = instance.MyList; - temp5 ??= new List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + instance.MyString = value5; } } 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 385079af1709a..45e006258c04f 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 @@ -67,7 +67,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt", "MyList", "MyString" }); public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -108,26 +108,26 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value1) + if (configuration["MyInt"] is string value1) { - instance.MyString = value1; - } - - if (configuration["MyInt"] is string value2) - { - instance.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (configuration["MyString"] is string value5) { - List? temp5 = instance.MyList; - temp5 ??= new List(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp5; + instance.MyString = value5; } } 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 a8373ca527095..f6e1f3eee898f 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 @@ -50,7 +50,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #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", "Prop28", "Prop29", "Prop30" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Prop0", "Prop1", "Prop10", "Prop11", "Prop12", "Prop13", "Prop14", "Prop15", "Prop16", "Prop17", "Prop18", "Prop19", "Prop2", "Prop20", "Prop21", "Prop22", "Prop23", "Prop24", "Prop25", "Prop26", "Prop27", "Prop28", "Prop29", "Prop3", "Prop30", "Prop4", "Prop5", "Prop6", "Prop7", "Prop8", "Prop9" }); public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { @@ -74,241 +74,241 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration instance.Prop1 = default; } - if (configuration["Prop2"] is string value2) + if (configuration["Prop10"] is string value2) { - instance.Prop2 = ParseSbyte(value2, () => configuration.GetSection("Prop2").Path); + instance.Prop10 = ParseFloat(value2, () => configuration.GetSection("Prop10").Path); } else if (defaultValueIfNotFound) { - instance.Prop2 = default; + instance.Prop10 = default; } - if (configuration["Prop3"] is string value3) + if (configuration["Prop11"] is string value3) { - instance.Prop3 = ParseChar(value3, () => configuration.GetSection("Prop3").Path); + instance.Prop11 = ParseHalf(value3, () => configuration.GetSection("Prop11").Path); } else if (defaultValueIfNotFound) { - instance.Prop3 = default; + instance.Prop11 = default; } - if (configuration["Prop4"] is string value4) + if (configuration["Prop12"] is string value4) { - instance.Prop4 = ParseDouble(value4, () => configuration.GetSection("Prop4").Path); + instance.Prop12 = ParseUInt128(value4, () => configuration.GetSection("Prop12").Path); } else if (defaultValueIfNotFound) { - instance.Prop4 = default; - } - - if (configuration["Prop5"] is string value5) - { - instance.Prop5 = value5; + instance.Prop12 = default; } - if (configuration["Prop6"] is string value6) + if (configuration["Prop13"] is string value5) { - instance.Prop6 = ParseInt(value6, () => configuration.GetSection("Prop6").Path); + instance.Prop13 = ParseUshort(value5, () => configuration.GetSection("Prop13").Path); } else if (defaultValueIfNotFound) { - instance.Prop6 = default; + instance.Prop13 = default; } - if (configuration["Prop8"] is string value7) + if (configuration["Prop14"] is string value6) { - instance.Prop8 = ParseShort(value7, () => configuration.GetSection("Prop8").Path); + instance.Prop14 = ParseUint(value6, () => configuration.GetSection("Prop14").Path); } else if (defaultValueIfNotFound) { - instance.Prop8 = default; + instance.Prop14 = default; } - if (configuration["Prop9"] is string value8) + if (configuration["Prop15"] is string value7) { - instance.Prop9 = ParseLong(value8, () => configuration.GetSection("Prop9").Path); + instance.Prop15 = ParseUlong(value7, () => configuration.GetSection("Prop15").Path); } else if (defaultValueIfNotFound) { - instance.Prop9 = default; + instance.Prop15 = default; } - if (configuration["Prop10"] is string value9) + if (configuration["Prop16"] is string value8) { - instance.Prop10 = ParseFloat(value9, () => configuration.GetSection("Prop10").Path); - } - else if (defaultValueIfNotFound) - { - instance.Prop10 = default; + instance.Prop16 = value8; } - if (configuration["Prop13"] is string value10) - { - instance.Prop13 = ParseUshort(value10, () => configuration.GetSection("Prop13").Path); - } - else if (defaultValueIfNotFound) + if (configuration["Prop17"] is string value9) { - instance.Prop13 = default; + instance.Prop17 = ParseCultureInfo(value9, () => configuration.GetSection("Prop17").Path); } - if (configuration["Prop14"] is string value11) + if (configuration["Prop18"] is string value10) { - instance.Prop14 = ParseUint(value11, () => configuration.GetSection("Prop14").Path); + instance.Prop18 = ParseDateOnly(value10, () => configuration.GetSection("Prop18").Path); } else if (defaultValueIfNotFound) { - instance.Prop14 = default; + instance.Prop18 = default; } - if (configuration["Prop15"] is string value12) + if (configuration["Prop19"] is string value11) { - instance.Prop15 = ParseUlong(value12, () => configuration.GetSection("Prop15").Path); + instance.Prop19 = ParseDateTime(value11, () => configuration.GetSection("Prop19").Path); } else if (defaultValueIfNotFound) { - instance.Prop15 = default; + instance.Prop19 = default; } - if (configuration["Prop16"] is string value13) + if (configuration["Prop2"] is string value12) { - instance.Prop16 = value13; + instance.Prop2 = ParseSbyte(value12, () => configuration.GetSection("Prop2").Path); } - - if (configuration["Prop17"] is string value14) + else if (defaultValueIfNotFound) { - instance.Prop17 = ParseCultureInfo(value14, () => configuration.GetSection("Prop17").Path); + instance.Prop2 = default; } - if (configuration["Prop19"] is string value15) + if (configuration["Prop20"] is string value13) { - instance.Prop19 = ParseDateTime(value15, () => configuration.GetSection("Prop19").Path); + instance.Prop20 = ParseDateTimeOffset(value13, () => configuration.GetSection("Prop20").Path); } else if (defaultValueIfNotFound) { - instance.Prop19 = default; + instance.Prop20 = default; } - if (configuration["Prop20"] is string value16) + if (configuration["Prop21"] is string value14) { - instance.Prop20 = ParseDateTimeOffset(value16, () => configuration.GetSection("Prop20").Path); + instance.Prop21 = ParseDecimal(value14, () => configuration.GetSection("Prop21").Path); } else if (defaultValueIfNotFound) { - instance.Prop20 = default; + instance.Prop21 = default; } - if (configuration["Prop21"] is string value17) + if (configuration["Prop22"] is string value15) { - instance.Prop21 = ParseDecimal(value17, () => configuration.GetSection("Prop21").Path); + instance.Prop22 = ParseTimeOnly(value15, () => configuration.GetSection("Prop22").Path); } else if (defaultValueIfNotFound) { - instance.Prop21 = default; + instance.Prop22 = default; } - if (configuration["Prop23"] is string value18) + if (configuration["Prop23"] is string value16) { - instance.Prop23 = ParseTimeSpan(value18, () => configuration.GetSection("Prop23").Path); + instance.Prop23 = ParseTimeSpan(value16, () => configuration.GetSection("Prop23").Path); } else if (defaultValueIfNotFound) { instance.Prop23 = default; } - if (configuration["Prop24"] is string value19) + if (configuration["Prop24"] is string value17) { - instance.Prop24 = ParseGuid(value19, () => configuration.GetSection("Prop24").Path); + instance.Prop24 = ParseGuid(value17, () => configuration.GetSection("Prop24").Path); } else if (defaultValueIfNotFound) { instance.Prop24 = default; } - if (configuration["Prop25"] is string value20) + if (configuration["Prop25"] is string value18) { - instance.Prop25 = ParseUri(value20, () => configuration.GetSection("Prop25").Path); + instance.Prop25 = ParseUri(value18, () => configuration.GetSection("Prop25").Path); } - if (configuration["Prop26"] is string value21) + if (configuration["Prop26"] is string value19) { - instance.Prop26 = ParseVersion(value21, () => configuration.GetSection("Prop26").Path); + instance.Prop26 = ParseVersion(value19, () => configuration.GetSection("Prop26").Path); } - if (configuration["Prop27"] is string value22) + if (configuration["Prop27"] is string value20) { - instance.Prop27 = ParseEnum(value22, () => configuration.GetSection("Prop27").Path); + instance.Prop27 = ParseEnum(value20, () => configuration.GetSection("Prop27").Path); } else if (defaultValueIfNotFound) { instance.Prop27 = default; } - if (configuration["Prop7"] is string value23) + if (configuration["Prop28"] is string value21) + { + instance.Prop28 = ParseByteArray(value21, () => configuration.GetSection("Prop28").Path); + } + + if (configuration["Prop29"] is string value22) { - instance.Prop7 = ParseInt128(value23, () => configuration.GetSection("Prop7").Path); + instance.Prop29 = ParseInt(value22, () => configuration.GetSection("Prop29").Path); } else if (defaultValueIfNotFound) { - instance.Prop7 = default; + instance.Prop29 = default; } - if (configuration["Prop11"] is string value24) + if (configuration["Prop3"] is string value23) { - instance.Prop11 = ParseHalf(value24, () => configuration.GetSection("Prop11").Path); + instance.Prop3 = ParseChar(value23, () => configuration.GetSection("Prop3").Path); } else if (defaultValueIfNotFound) { - instance.Prop11 = default; + instance.Prop3 = default; } - if (configuration["Prop12"] is string value25) + if (configuration["Prop30"] is string value24) { - instance.Prop12 = ParseUInt128(value25, () => configuration.GetSection("Prop12").Path); + instance.Prop30 = ParseDateTime(value24, () => configuration.GetSection("Prop30").Path); } else if (defaultValueIfNotFound) { - instance.Prop12 = default; + instance.Prop30 = default; } - if (configuration["Prop18"] is string value26) + if (configuration["Prop4"] is string value25) { - instance.Prop18 = ParseDateOnly(value26, () => configuration.GetSection("Prop18").Path); + instance.Prop4 = ParseDouble(value25, () => configuration.GetSection("Prop4").Path); } else if (defaultValueIfNotFound) { - instance.Prop18 = default; + instance.Prop4 = default; } - if (configuration["Prop22"] is string value27) + if (configuration["Prop5"] is string value26) { - instance.Prop22 = ParseTimeOnly(value27, () => configuration.GetSection("Prop22").Path); + instance.Prop5 = value26; + } + + if (configuration["Prop6"] is string value27) + { + instance.Prop6 = ParseInt(value27, () => configuration.GetSection("Prop6").Path); } else if (defaultValueIfNotFound) { - instance.Prop22 = default; + instance.Prop6 = default; } - if (configuration["Prop28"] is string value28) + if (configuration["Prop7"] is string value28) { - instance.Prop28 = ParseByteArray(value28, () => configuration.GetSection("Prop28").Path); + instance.Prop7 = ParseInt128(value28, () => configuration.GetSection("Prop7").Path); + } + else if (defaultValueIfNotFound) + { + instance.Prop7 = default; } - if (configuration["Prop29"] is string value29) + if (configuration["Prop8"] is string value29) { - instance.Prop29 = ParseInt(value29, () => configuration.GetSection("Prop29").Path); + instance.Prop8 = ParseShort(value29, () => configuration.GetSection("Prop8").Path); } else if (defaultValueIfNotFound) { - instance.Prop29 = default; + instance.Prop8 = default; } - if (configuration["Prop30"] is string value30) + if (configuration["Prop9"] is string value30) { - instance.Prop30 = ParseDateTime(value30, () => configuration.GetSection("Prop30").Path); + instance.Prop9 = ParseLong(value30, () => configuration.GetSection("Prop9").Path); } else if (defaultValueIfNotFound) { - instance.Prop30 = default; + instance.Prop9 = default; } } @@ -335,39 +335,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static bool ParseBool(string value, Func getPath) + public static byte[] ParseByteArray(string value, Func getPath) { try { - return bool.Parse(value); + return Convert.FromBase64String(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); } } - public static byte ParseByte(string value, Func getPath) + public static bool ParseBool(string value, Func getPath) { try { - return byte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return bool.Parse(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); } } - public static sbyte ParseSbyte(string value, Func getPath) + public static byte ParseByte(string value, Func getPath) { try { - return sbyte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return byte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(sbyte)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte)}'.", exception); } } @@ -383,147 +383,151 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static double ParseDouble(string value, Func getPath) + public static decimal ParseDecimal(string value, Func getPath) { try { - return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(double)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(decimal)}'.", exception); } } - public static int ParseInt(string value, Func getPath) + public static double ParseDouble(string value, Func getPath) { try { - return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(double)}'.", exception); } } - public static short ParseShort(string value, Func getPath) + public static float ParseFloat(string value, Func getPath) { try { - return short.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(short)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(float)}'.", exception); } } - public static long ParseLong(string value, Func getPath) + public static DateOnly ParseDateOnly(string value, Func getPath) { try { - return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return DateOnly.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(long)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateOnly)}'.", exception); } } - public static float ParseFloat(string value, Func getPath) + public static DateTime ParseDateTime(string value, Func getPath) { try { - return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + return DateTime.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(float)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateTime)}'.", exception); } } - public static ushort ParseUshort(string value, Func getPath) + public static DateTimeOffset ParseDateTimeOffset(string value, Func getPath) { try { - return ushort.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return DateTimeOffset.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ushort)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateTimeOffset)}'.", exception); } } - public static uint ParseUint(string value, Func getPath) + public static T ParseEnum(string value, Func getPath) where T : struct { try { - return uint.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + #if NETFRAMEWORK || NETSTANDARD2_0 + return (T)Enum.Parse(typeof(T), value, ignoreCase: true); + #else + return Enum.Parse(value, ignoreCase: true); + #endif } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(uint)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(T)}'.", exception); } } - public static ulong ParseUlong(string value, Func getPath) + public static CultureInfo ParseCultureInfo(string value, Func getPath) { try { - return ulong.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return CultureInfo.GetCultureInfo(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ulong)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); } } - public static CultureInfo ParseCultureInfo(string value, Func getPath) + public static Guid ParseGuid(string value, Func getPath) { try { - return CultureInfo.GetCultureInfo(value); + return Guid.Parse(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Guid)}'.", exception); } } - public static DateTime ParseDateTime(string value, Func getPath) + public static Half ParseHalf(string value, Func getPath) { try { - return DateTime.Parse(value, CultureInfo.InvariantCulture); + return Half.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateTime)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Half)}'.", exception); } } - public static DateTimeOffset ParseDateTimeOffset(string value, Func getPath) + public static Int128 ParseInt128(string value, Func getPath) { try { - return DateTimeOffset.Parse(value, CultureInfo.InvariantCulture); + return Int128.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateTimeOffset)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Int128)}'.", exception); } } - public static decimal ParseDecimal(string value, Func getPath) + public static TimeOnly ParseTimeOnly(string value, Func getPath) { try { - return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + return TimeOnly.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(decimal)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(TimeOnly)}'.", exception); } } @@ -539,127 +543,123 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static Guid ParseGuid(string value, Func getPath) + public static UInt128 ParseUInt128(string value, Func getPath) { try { - return Guid.Parse(value); + return UInt128.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Guid)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(UInt128)}'.", exception); } } - public static Uri ParseUri(string value, Func getPath) + public static Version ParseVersion(string value, Func getPath) { try { - return new Uri(value, UriKind.RelativeOrAbsolute); + return Version.Parse(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Uri)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Version)}'.", exception); } } - public static Version ParseVersion(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return Version.Parse(value); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Version)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } - public static T ParseEnum(string value, Func getPath) where T : struct + public static long ParseLong(string value, Func getPath) { try { - #if NETFRAMEWORK || NETSTANDARD2_0 - return (T)Enum.Parse(typeof(T), value, ignoreCase: true); - #else - return Enum.Parse(value, ignoreCase: true); - #endif + return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(T)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(long)}'.", exception); } } - public static Int128 ParseInt128(string value, Func getPath) + public static sbyte ParseSbyte(string value, Func getPath) { try { - return Int128.Parse(value, CultureInfo.InvariantCulture); + return sbyte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Int128)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(sbyte)}'.", exception); } } - public static Half ParseHalf(string value, Func getPath) + public static short ParseShort(string value, Func getPath) { try { - return Half.Parse(value, CultureInfo.InvariantCulture); + return short.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Half)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(short)}'.", exception); } } - public static UInt128 ParseUInt128(string value, Func getPath) + public static uint ParseUint(string value, Func getPath) { try { - return UInt128.Parse(value, CultureInfo.InvariantCulture); + return uint.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(UInt128)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(uint)}'.", exception); } } - public static DateOnly ParseDateOnly(string value, Func getPath) + public static ulong ParseUlong(string value, Func getPath) { try { - return DateOnly.Parse(value, CultureInfo.InvariantCulture); + return ulong.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateOnly)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ulong)}'.", exception); } } - public static TimeOnly ParseTimeOnly(string value, Func getPath) + public static ushort ParseUshort(string value, Func getPath) { try { - return TimeOnly.Parse(value, CultureInfo.InvariantCulture); + return ushort.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(TimeOnly)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ushort)}'.", exception); } } - public static byte[] ParseByteArray(string value, Func getPath) + public static Uri ParseUri(string value, Func getPath) { try { - return Convert.FromBase64String(value); + return new Uri(value, UriKind.RelativeOrAbsolute); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Uri)}'.", 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 3e38a484c8255..5c8e2bffda59c 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 @@ -59,8 +59,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyDictionary", "MyInt", "MyList", "MyList2", "MyString" }); 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -86,31 +86,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) { - instance.Add(ParseInt(value, () => section.Path)); + instance[section.Key] = value; } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - - if (configuration["MyInt"] is string value1) - { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) @@ -121,13 +107,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) { - instance[section.Key] = value; + instance.Add(ParseInt(value, () => section.Path)); } } } @@ -136,42 +122,56 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section2) { - instance.MyString = value3; + Dictionary? temp4 = instance.MyDictionary; + temp4 ??= new Dictionary(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp4; } - if (configuration["MyInt"] is string value4) + if (configuration["MyInt"] is string value5) { - instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6) { - List? temp7 = instance.MyList; - temp7 ??= new List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + List? temp8 = instance.MyList; + temp8 ??= new List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section9) { - List? temp10 = instance.MyList2; - temp10 ??= new List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + List? temp11 = instance.MyList2; + temp11 ??= new List(); + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp11; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + if (configuration["MyString"] is string value12) + { + instance.MyString = value12; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value13) { - Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + instance.MyInt = ParseInt(value13, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; } } 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 186e93a49207b..c49be31cfdc6f 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 @@ -59,8 +59,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyDictionary", "MyInt", "MyList", "MyList2", "MyString" }); 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -86,31 +86,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) { - instance.Add(ParseInt(value, () => section.Path)); + instance[section.Key] = value; } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - - if (configuration["MyInt"] is string value1) - { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) @@ -121,13 +107,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) { - instance[section.Key] = value; + instance.Add(ParseInt(value, () => section.Path)); } } } @@ -136,42 +122,56 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section2) { - instance.MyString = value3; + Dictionary? temp4 = instance.MyDictionary; + temp4 ??= new Dictionary(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp4; } - if (configuration["MyInt"] is string value4) + if (configuration["MyInt"] is string value5) { - instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6) { - List? temp7 = instance.MyList; - temp7 ??= new List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + List? temp8 = instance.MyList; + temp8 ??= new List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section9) { - List? temp10 = instance.MyList2; - temp10 ??= new List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + List? temp11 = instance.MyList2; + temp11 ??= new List(); + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp11; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + if (configuration["MyString"] is string value12) + { + instance.MyString = value12; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value13) { - Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + instance.MyInt = ParseInt(value13, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; } } 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 7958adb112533..de3bbce3bec8f 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 @@ -59,8 +59,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyDictionary", "MyInt", "MyList", "MyList2", "MyString" }); 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -86,31 +86,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) { - instance.Add(ParseInt(value, () => section.Path)); + instance[section.Key] = value; } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - - if (configuration["MyInt"] is string value1) - { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) @@ -121,13 +107,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) { - instance[section.Key] = value; + instance.Add(ParseInt(value, () => section.Path)); } } } @@ -136,42 +122,56 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section2) { - instance.MyString = value3; + Dictionary? temp4 = instance.MyDictionary; + temp4 ??= new Dictionary(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp4; } - if (configuration["MyInt"] is string value4) + if (configuration["MyInt"] is string value5) { - instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6) { - List? temp7 = instance.MyList; - temp7 ??= new List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + List? temp8 = instance.MyList; + temp8 ??= new List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section9) { - List? temp10 = instance.MyList2; - temp10 ??= new List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + List? temp11 = instance.MyList2; + temp11 ??= new List(); + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp11; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + if (configuration["MyString"] is string value12) + { + instance.MyString = value12; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value13) { - Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + instance.MyInt = ParseInt(value13, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; } } 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 b87d0c0a259cc..30c6882448122 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 @@ -53,8 +53,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyDictionary", "MyInt", "MyList", "MyList2", "MyString" }); 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -80,31 +80,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) { - instance.Add(ParseInt(value, () => section.Path)); + instance[section.Key] = value; } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - - if (configuration["MyInt"] is string value1) - { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) @@ -115,13 +101,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { if (section.Value is string value) { - instance[section.Key] = value; + instance.Add(ParseInt(value, () => section.Path)); } } } @@ -130,42 +116,56 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyString"] is string value3) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section2) { - instance.MyString = value3; + Dictionary? temp4 = instance.MyDictionary; + temp4 ??= new Dictionary(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp4; } - if (configuration["MyInt"] is string value4) + if (configuration["MyInt"] is string value5) { - instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6) { - List? temp7 = instance.MyList; - temp7 ??= new List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; + List? temp8 = instance.MyList; + temp8 ??= new List(); + BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp8; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section9) { - List? temp10 = instance.MyList2; - temp10 ??= new List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + List? temp11 = instance.MyList2; + temp11 ??= new List(); + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp11; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + if (configuration["MyString"] is string value12) + { + instance.MyString = value12; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value13) { - Dictionary? temp13 = instance.MyDictionary; - temp13 ??= new Dictionary(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp13; + instance.MyInt = ParseInt(value13, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs index 9d7c1b7d9408f..578b9be9fe809 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs @@ -113,8 +113,8 @@ private static async Task VerifyAgainstBaselineUsingFile( #if UPDATE_BASELINES if (!success) { - const string envVarName = "RepoRootDir" - string errMessage = $"To update baselines, specify a '{envVarName}' environment variable. See this assembly's README.md doc for more details." + const string envVarName = "RepoRootDir"; + string errMessage = $"To update baselines, specify a '{envVarName}' environment variable. See this assembly's README.md doc for more details."; string? repoRootDir = Environment.GetEnvironmentVariable(envVarName); Assert.True(repoRootDir is not null, errMessage); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs index d2ca1f8bb8f23..3e236c3f6db52 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs @@ -81,8 +81,8 @@ public record struct MyRecordStruct { } foreach (Diagnostic diagnostic in d) { - Assert.True(diagnostic.Id == Diagnostics.InvalidBindInput.Id); - Assert.Contains(Diagnostics.InvalidBindInput.Title, diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); + Assert.True(diagnostic.Id == Diagnostics.ValueTypesInvalidForBind.Id); + Assert.Contains(Diagnostics.ValueTypesInvalidForBind.Title, diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); Assert.NotNull(diagnostic.Location); } diff --git a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs index 7d280ce7603c2..a143883cdd2de 100644 --- a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs @@ -36,12 +36,6 @@ internal static class RoslynExtensions return reference?.SyntaxTree.GetLocation(reference.Span); } - /// - /// Creates a copy of the Location instance that does not capture a reference to Compilation. - /// - public static Location GetTrimmedLocation(this Location location) - => Location.Create(location.SourceTree?.FilePath ?? "", location.SourceSpan, location.GetLineSpan().Span); - /// /// Returns true if the specified location is contained in one of the syntax trees in the compilation. /// diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 0f3b11b038bc9..49ee900b6b712 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; using SourceGenerators; namespace System.Text.Json.SourceGeneration From 82318e746d4e643c46676cf13e19b0b86a040f87 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Sun, 17 Sep 2023 17:04:44 -0700 Subject: [PATCH 03/11] Add incremental tests & driver --- .../tests/SourceGenerators/RoslynTestUtils.cs | 5 + .../ConfigBindingGenDriver.cs | 108 ++++++ .../GeneratorTests.Baselines.cs | 10 +- .../GeneratorTests.Helpers.cs | 68 ++-- .../GeneratorTests.Incremental.cs | 345 ++++++++++++++++++ .../SourceGenerationTests/GeneratorTests.cs | 53 ++- ...ation.Binder.SourceGeneration.Tests.csproj | 4 +- 7 files changed, 518 insertions(+), 75 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenDriver.cs create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs diff --git a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs index e67289721d8af..5c101ca2c4753 100644 --- a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs +++ b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs @@ -110,6 +110,11 @@ public static Project WithDocuments(this Project project, IEnumerable so return result; } + public static Project WithDocument(this Project proj, string text) + { + return proj.AddDocument("src-0.cs", text).Project; + } + public static Project WithDocument(this Project proj, string name, string text) { return proj.AddDocument(name, text).Project; diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenDriver.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenDriver.cs new file mode 100644 index 0000000000000..404b38e89b07a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenDriver.cs @@ -0,0 +1,108 @@ +// 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.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Extensions.Configuration.Binder.SourceGeneration; +using SourceGenerators.Tests; +using Xunit; + +namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)] + public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase + { + internal sealed class ConfigBindingGenDriver : IDisposable + { + private readonly CSharpParseOptions _parseOptions; + private readonly CSharpGeneratorDriver _csharpGenerationDriver; + + private readonly AdhocWorkspace _workspace; + private Project _project; + + public ConfigBindingGenDriver( + LanguageVersion langVersion = LanguageVersion.LatestMajor, + IEnumerable? assemblyReferences = null) + { + assemblyReferences ??= s_compilationAssemblyRefs; + + _parseOptions = new CSharpParseOptions(langVersion).WithFeatures(new[] { + new KeyValuePair("InterceptorsPreview", "") , + new KeyValuePair("InterceptorsPreviewNamespaces", "Microsoft.Extensions.Configuration.Binder.SourceGeneration") + }); + + _workspace = RoslynTestUtils.CreateTestWorkspace(); + + _project = RoslynTestUtils.CreateTestProject(_workspace, assemblyReferences, langVersion: langVersion) + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithNullableContextOptions(NullableContextOptions.Annotations)) + .WithParseOptions(_parseOptions); + + + _csharpGenerationDriver = CSharpGeneratorDriver.Create(new[] { new ConfigurationBindingGenerator().AsSourceGenerator() }, parseOptions: _parseOptions); + } + + public async Task RunGeneratorAndUpdateCompilation(params string[]? sources) + { + if (sources is not null) + { + _project = _project.WithDocuments(sources); + Assert.True(_project.Solution.Workspace.TryApplyChanges(_project.Solution)); + } + + Compilation compilation = (await _project.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false))!; + GeneratorDriver generatorDriver = _csharpGenerationDriver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out _, CancellationToken.None); + GeneratorDriverRunResult runDriverRunResult = generatorDriver.GetRunResult(); + + return new ConfigBindingGenResult + { + OutputCompilation = outputCompilation, + Diagnostics = runDriverRunResult.Diagnostics, + GeneratedSources = runDriverRunResult.Results[0].GeneratedSources, + }; + } + + public void Dispose() => _workspace.Dispose(); + } + + internal struct ConfigBindingGenResult + { + public required Compilation OutputCompilation { get; init; } + + public required ImmutableArray GeneratedSources { get; init; } + + /// + /// Diagnostics produced by the generator alone. Doesn't include any from other build participants. + /// + public required ImmutableArray Diagnostics { get; init; } + } + } + + internal static class ConfigBindinGenDriverExtensions + { + public static void AssertHasSourceAndNoDiagnostics(this ConfigurationBindingGeneratorTests.ConfigBindingGenResult result) + { + Assert.Single(result.GeneratedSources); + Assert.NotEmpty(result.Diagnostics); + } + + public static void AssertHasSourceAndDiagnostics(this ConfigurationBindingGeneratorTests.ConfigBindingGenResult result) + { + Assert.Single(result.GeneratedSources); + Assert.NotEmpty(result.Diagnostics); + } + + public static void AssertEmpty(this ConfigurationBindingGeneratorTests.ConfigBindingGenResult result) + { + Assert.Empty(result.GeneratedSources); + Assert.Empty(result.Diagnostics); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs index 2840000bb0812..35395d29d8f57 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs @@ -654,9 +654,9 @@ public class MyClass2 }" ; - var (d, r) = await RunGenerator(source); - Assert.Empty(r); - Assert.Empty(d); + ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source); + Assert.Empty(result.GeneratedSources); + Assert.Empty(result.Diagnostics); } [Fact] @@ -766,7 +766,7 @@ public interface ICustomSet : ISet } """; - await VerifyAgainstBaselineUsingFile("Collections.generated.txt", source, validateOutputCompDiags: false, assessDiagnostics: (d) => + await VerifyAgainstBaselineUsingFile("Collections.generated.txt", source, validateOutputDiags: false, assessDiagnostics: (d) => { Assert.Equal(3, d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); Assert.Equal(6, d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count()); @@ -819,7 +819,7 @@ await VerifyAgainstBaselineUsingFile( { Assert.Equal(2, d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); }, - validateOutputCompDiags: false); + validateOutputDiags: false); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs index 578b9be9fe809..2dd980e3ee08e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs @@ -9,10 +9,10 @@ using System.IO; using System.Linq; using System.Reflection; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Binder.SourceGeneration; using Microsoft.Extensions.DependencyInjection; @@ -24,6 +24,9 @@ namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests { public partial class ConfigurationBindingGeneratorTests { + /// + /// Keep in sync with variants, e.g. . + /// private const string BindCallSampleCode = """ using System.Collections.Generic; using Microsoft.Extensions.Configuration; @@ -98,7 +101,7 @@ private static async Task VerifyAgainstBaselineUsingFile( string testSourceCode, Action>? assessDiagnostics = null, ExtensionClassType extType = ExtensionClassType.None, - bool validateOutputCompDiags = true) + bool validateOutputDiags = true) { string path = extType is ExtensionClassType.None ? Path.Combine("Baselines", filename) @@ -107,11 +110,15 @@ private static async Task VerifyAgainstBaselineUsingFile( string[] expectedLines = baseline.Replace("%VERSION%", typeof(ConfigurationBindingGenerator).Assembly.GetName().Version?.ToString()) .Split(Environment.NewLine); - var (d, r) = await RunGenerator(testSourceCode, validateOutputCompDiags); - bool success = RoslynTestUtils.CompareLines(expectedLines, r[0].SourceText, out string errorMessage); + ConfigBindingGenDriver genDriver = new(); + ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(testSourceCode); + GeneratedSourceResult generatedSource = Assert.Single(result.GeneratedSources); -#if UPDATE_BASELINES - if (!success) + SourceText resultSourceText = generatedSource.SourceText; + bool resultEqualsBaseline = RoslynTestUtils.CompareLines(expectedLines, resultSourceText, out string errorMessage); + +#if !UPDATE_BASELINES + if (!resultEqualsBaseline) { const string envVarName = "RepoRootDir"; string errMessage = $"To update baselines, specify a '{envVarName}' environment variable. See this assembly's README.md doc for more details."; @@ -119,61 +126,46 @@ private static async Task VerifyAgainstBaselineUsingFile( string? repoRootDir = Environment.GetEnvironmentVariable(envVarName); Assert.True(repoRootDir is not null, errMessage); - IEnumerable lines = r[0].SourceText.Lines.Select(l => l.ToString()); + IEnumerable lines = resultSourceText.Lines.Select(l => l.ToString()); string source = string.Join(Environment.NewLine, lines).TrimEnd(Environment.NewLine.ToCharArray()) + Environment.NewLine; path = Path.Combine($"{repoRootDir}\\src\\libraries\\Microsoft.Extensions.Configuration.Binder\\tests\\SourceGenerationTests\\", path); await File.WriteAllTextAsync(path, source).ConfigureAwait(false); - success = true; + resultEqualsBaseline = true; } #endif - Assert.Single(r); - (assessDiagnostics ?? ((d) => Assert.Empty(d))).Invoke(d); - Assert.True(success, errorMessage); + assessDiagnostics ??= static (diagnostics) => Assert.Empty(diagnostics); + assessDiagnostics(result.Diagnostics); + Assert.True(resultEqualsBaseline, errorMessage); } - private static async Task<(ImmutableArray, ImmutableArray)> RunGenerator( - string testSourceCode, - bool validateOutputCompDiags = false, + private static async Task RunGeneratorAndUpdateCompilation( + string source, LanguageVersion langVersion = LanguageVersion.CSharp12, - IEnumerable? references = null) + IEnumerable? assemblyReferences = null, + bool validateCompilationDiagnostics = false) { - using var workspace = RoslynTestUtils.CreateTestWorkspace(); - CSharpParseOptions parseOptions = new CSharpParseOptions(langVersion).WithFeatures(new[] { - new KeyValuePair("InterceptorsPreview", ""), - new KeyValuePair("InterceptorsPreviewNamespaces", "Microsoft.Extensions.Configuration.Binder.SourceGeneration") - }); - - Project proj = RoslynTestUtils.CreateTestProject(workspace, references ?? s_compilationAssemblyRefs, langVersion: langVersion) - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithNullableContextOptions(NullableContextOptions.Annotations)) - .WithDocuments(new string[] { testSourceCode }) - .WithParseOptions(parseOptions); - - Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution)); - - Compilation comp = await proj.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); - CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { new ConfigurationBindingGenerator().AsSourceGenerator() }, parseOptions: parseOptions); - GeneratorDriver gd = cgd.RunGeneratorsAndUpdateCompilation(comp, out Compilation outputCompilation, out _, CancellationToken.None); - GeneratorDriverRunResult runResult = gd.GetRunResult(); + ConfigBindingGenDriver driver = new ConfigBindingGenDriver(langVersion, assemblyReferences); + ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(source); - if (validateOutputCompDiags) + if (validateCompilationDiagnostics) { - ImmutableArray diagnostics = outputCompilation.GetDiagnostics(); - Assert.False(diagnostics.Any(d => d.Severity > DiagnosticSeverity.Info)); + ImmutableArray compilationDiags = result.OutputCompilation.GetDiagnostics(); + Assert.False(compilationDiags.Any(d => d.Severity > DiagnosticSeverity.Info)); } - return (runResult.Results[0].Diagnostics, runResult.Results[0].GeneratedSources); + return result; } - public static List GetAssemblyRefsWithAdditional(params Type[] additional) + private static List GetAssemblyRefsWithAdditional(params Type[] additional) { List assemblies = new(s_compilationAssemblyRefs); assemblies.AddRange(additional.Select(t => t.Assembly)); return assemblies; } - public static HashSet GetFilteredAssemblyRefs(IEnumerable exclusions) + private static HashSet GetFilteredAssemblyRefs(IEnumerable exclusions) { HashSet assemblies = new(s_compilationAssemblyRefs); foreach (Type exclusion in exclusions) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs new file mode 100644 index 0000000000000..5a7e75149716c --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs @@ -0,0 +1,345 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)] + public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase + { + [ActiveIssue("")] + [Fact] + public async Task RunWithNoDiags_Then_NoEdit() + { + ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + + ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); + result.AssertHasSourceAndNoDiagnostics(); + + result = await driver.RunGeneratorAndUpdateCompilation(); + result.AssertEmpty(); + } + + [ActiveIssue("")] + [Fact] + public async Task RunWithNoDiags_Then_NoOpEdit() + { + ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + + ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); + result.AssertHasSourceAndNoDiagnostics(); + + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_ReorderedInvocations); + result.AssertEmpty(); + + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_ReorderedConfigTypeMembers); + result.AssertEmpty(); + } + + [ActiveIssue("")] + [Fact] + public async Task RunWithNoDiags_Then_EditWithNoDiags() + { + ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + + ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); + result.AssertHasSourceAndNoDiagnostics(); + + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithDifferentConfigTypeName); + result.AssertEmpty(); + } + + [ActiveIssue("")] + [Fact] + public async Task RunWithNoDiags_Then_EditWithDiags() + { + ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + + ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); + result.AssertHasSourceAndNoDiagnostics(); + + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); + result.AssertEmpty(); + } + + [ActiveIssue("")] + [Fact] + public async Task RunWithDiags_Then_NoEdit() + { + ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + + ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); + result.AssertHasSourceAndDiagnostics(); + + result = await driver.RunGeneratorAndUpdateCompilation(); + result.AssertEmpty(); + } + + [ActiveIssue("")] + [Fact] + public async Task RunWithDiags_Then_NoOpEdit() + { + ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + + ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); + result.AssertHasSourceAndDiagnostics(); + + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_ReorderedInvocations); + result.AssertEmpty(); + + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_ReorderedConfigTypeMembers); + result.AssertEmpty(); + } + + [ActiveIssue("")] + [Fact] + public async Task RunWithDiags_Then_EditWithNoDiags() + { + ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + + ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); + result.AssertHasSourceAndDiagnostics(); + + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); + result.AssertEmpty(); + } + + [ActiveIssue("")] + [Fact] + public async Task RunWithDiags_Then_EditWithDiags() + { + ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + + ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); + result.AssertHasSourceAndDiagnostics(); + + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_WithDiffMemberName); + result.AssertHasSourceAndDiagnostics(); + } + + /// + /// Keep in sync with . + /// + private const string BindCallSampleCodeVariant_ReorderedInvocations = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = new(); + config.Bind(configObj, options => { }); + config.Bind("key", configObj); + config.Bind(configObj); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public Dictionary MyDictionary { get; set; } + public Dictionary MyComplexDictionary { get; set; } + } + + public class MyClass2 { } + } + """; + + /// + /// Keep in sync with . + /// + private const string BindCallSampleCodeVariant_ReorderedConfigTypeMembers = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = new(); + config.Bind(configObj, options => { }); + config.Bind("key", configObj); + config.Bind(configObj); + } + + public class MyClass + { + public List MyList { get; set; } + public Dictionary MyDictionary { get; set; } + public string MyString { get; set; } + public int MyInt { get; set; } + public Dictionary MyComplexDictionary { get; set; } + } + + public class MyClass2 { } + } + """; + + /// + /// Keep in sync with . + /// + private const string BindCallSampleCodeVariant_WithDifferentConfigTypeName = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass0 configObj = new(); + config.Bind(configObj, options => { }); + config.Bind("key", configObj); + config.Bind(configObj); + } + + public class MyClass0 + { + public List MyList { get; set; } + public Dictionary MyDictionary { get; set; } + public string MyString { get; set; } + public int MyInt { get; set; } + public Dictionary MyComplexDictionary { get; set; } + } + + public class MyClass2 { } + } + """; + + private const string BindCallSampleCodeVariant_WithUnsupportedMember = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = new(); + config.Bind(configObj); + config.Bind(configObj, options => { }); + config.Bind("key", configObj); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public Dictionary MyDictionary { get; set; } + public Dictionary MyComplexDictionary { get; set; } + public int[,] UnsupportedMember { get; set; } + } + + public class MyClass2 { } + } + """; + + private const string BindCallSampleCodeVariant_WithUnsupportedMember_ReorderedInvocations = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = new(); + config.Bind("key", configObj); + config.Bind(configObj); + config.Bind(configObj, options => { }); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public Dictionary MyDictionary { get; set; } + public Dictionary MyComplexDictionary { get; set; } + public int[,] UnsupportedMember { get; set; } + } + + public class MyClass2 { } + } + """; + + private const string BindCallSampleCodeVariant_WithUnsupportedMember_ReorderedConfigTypeMembers = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = new(); + config.Bind(configObj); + config.Bind(configObj, options => { }); + config.Bind("key", configObj); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public int[,] UnsupportedMember { get; set; } + public Dictionary MyDictionary { get; set; } + public Dictionary MyComplexDictionary { get; set; } + public List MyList { get; set; } + } + + public class MyClass2 { } + } + """; + + private const string BindCallSampleCodeVariant_WithUnsupportedMember_WithDiffMemberName = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = new(); + config.Bind(configObj); + config.Bind(configObj, options => { }); + config.Bind("key", configObj); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public Dictionary MyDictionary { get; set; } + public Dictionary MyComplexDictionary { get; set; } + public int[,] UnsupportedMember_DiffMemberName { get; set; } + } + + public class MyClass2 { } + } + """; + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs index 3e236c3f6db52..8d5bd39161a9a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs @@ -27,10 +27,10 @@ public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTes [InlineData(LanguageVersion.CSharp10)] public async Task LangVersionMustBeCharp12OrHigher(LanguageVersion langVersion) { - var (d, r) = await RunGenerator(BindCallSampleCode, langVersion: langVersion); - Assert.Empty(r); + ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(BindCallSampleCode, langVersion: langVersion); + Assert.Empty(result.GeneratedSources); - Diagnostic diagnostic = Assert.Single(d); + Diagnostic diagnostic = Assert.Single(result.Diagnostics); Assert.True(diagnostic.Id == "SYSLIB1102"); Assert.Contains("C# 12", diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); @@ -75,11 +75,11 @@ public record struct MyRecordStruct { } } """; - var (d, r) = await RunGenerator(source); - Assert.Empty(r); - Assert.Equal(7, d.Count()); + ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source); + Assert.Empty(result.GeneratedSources); + Assert.Equal(7, result.Diagnostics.Count()); - foreach (Diagnostic diagnostic in d) + foreach (Diagnostic diagnostic in result.Diagnostics) { Assert.True(diagnostic.Id == Diagnostics.ValueTypesInvalidForBind.Id); Assert.Contains(Diagnostics.ValueTypesInvalidForBind.Title, diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); @@ -111,11 +111,11 @@ public record struct MyRecordStruct { } } """; - var (d, r) = await RunGenerator(source); - Assert.Empty(r); - Assert.Equal(2, d.Count()); + ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source); + Assert.Empty(result.GeneratedSources); + Assert.Equal(2, result.Diagnostics.Count()); - foreach (Diagnostic diagnostic in d) + foreach (Diagnostic diagnostic in result.Diagnostics) { Assert.True(diagnostic.Id == Diagnostics.CouldNotDetermineTypeInfo.Id); Assert.Contains(Diagnostics.CouldNotDetermineTypeInfo.Title, diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); @@ -163,11 +163,11 @@ public class MyClass { } } """; - var (d, r) = await RunGenerator(source); - Assert.Empty(r); - Assert.Equal(6, d.Count()); + ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source); + Assert.Empty(result.GeneratedSources); + Assert.Equal(6, result.Diagnostics.Count()); - foreach (Diagnostic diagnostic in d) + foreach (Diagnostic diagnostic in result.Diagnostics) { Assert.True(diagnostic.Id == Diagnostics.CouldNotDetermineTypeInfo.Id); Assert.Contains(Diagnostics.CouldNotDetermineTypeInfo.Title, diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); @@ -218,18 +218,9 @@ public class MyClass0 { } async Task Test(bool expectOutput) { - var (d, r) = await RunGenerator(source, references: GetFilteredAssemblyRefs(exclusions)); - - Assert.Empty(d); - - if (expectOutput) - { - Assert.Single(r); - } - else - { - Assert.Empty(r); - } + ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetFilteredAssemblyRefs(exclusions)); + Assert.Empty(result.Diagnostics); + Assert.Equal(expectOutput ? 1 : 0, result.GeneratedSources.Length); } } @@ -283,10 +274,10 @@ public class AnotherGraphWithUnsupportedMembers } """; - var (d, r) = await RunGenerator(source, references: GetAssemblyRefsWithAdditional(typeof(ImmutableArray<>), typeof(Encoding), typeof(JsonSerializer))); - Assert.Single(r); - Assert.Equal(47, d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); - Assert.Equal(44, d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count()); + ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ImmutableArray<>), typeof(Encoding), typeof(JsonSerializer))); + Assert.Single(result.GeneratedSources); + Assert.True(result.Diagnostics.Any(diag => diag.Id == Diagnostics.TypeNotSupported.Id)); + Assert.True(result.Diagnostics.Any(diag => diag.Id == Diagnostics.PropertyNotSupported.Id)); } } } 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 fc8db157eddee..adb25342fbbfe 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 @@ -30,7 +30,9 @@ + + @@ -64,4 +66,4 @@ - + \ No newline at end of file From ff62dd48dd7dcd1ae01d221c7b44a591e349436b Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 22 Sep 2023 10:03:42 -0700 Subject: [PATCH 04/11] Make incremental tests pass and revert functional regression --- .../src/SourceGenerators/TypeModelHelper.cs | 59 ++ .../src/SourceGenerators}/TypeRef.cs | 3 +- .../SourceGenerators/GeneratorTestHelpers.cs | 84 +++ .../tests/SourceGenerators/RoslynTestUtils.cs | 5 - .../ConfigurationBindingGenerator.Emitter.cs | 11 +- .../ConfigurationBindingGenerator.Parser.cs | 543 ++++++++---------- .../gen/ConfigurationBindingGenerator.cs | 34 +- .../gen/Emitter/ConfigurationBinder.cs | 4 +- .../gen/Emitter/CoreBindingHelpers.cs | 246 ++++---- .../gen/Emitter/Helpers.cs | 2 - ...nfiguration.Binder.SourceGeneration.csproj | 7 +- .../gen/Parser/ConfigurationBinder.cs | 57 +- .../gen/Parser/DiagnosticDescriptors.cs | 14 + .../gen/Parser/Extensions.cs | 3 + .../gen/Parser/Helpers.cs | 81 +++ .../OptionsBuilderConfigurationExtensions.cs | 44 +- ...onfigurationServiceCollectionExtensions.cs | 29 +- .../gen/Specs/BindingHelperInfo.cs | 148 +++-- .../gen/Specs/InterceptorInfo.cs | 59 +- .../gen/Specs/Members/MemberSpec.cs | 5 +- .../gen/Specs/Members/ParameterSpec.cs | 2 +- .../gen/Specs/Members/PropertySpec.cs | 2 +- .../gen/Specs/MethodsToGen.cs | 4 +- .../gen/Specs/SourceGenerationSpec.cs | 14 + .../gen/Specs/TypeIndex.cs | 98 ++++ .../gen/Specs/Types/CollectionSpec.cs | 59 +- .../gen/Specs/Types/ComplexTypeSpec.cs | 29 - .../gen/Specs/Types/NullableSpec.cs | 22 - .../gen/Specs/Types/ObjectSpec.cs | 41 +- .../gen/Specs/Types/SimpleTypeSpec.cs | 33 +- .../gen/Specs/Types/TypeSpec.cs | 48 +- .../tests/Common/ConfigurationBinderTests.cs | 14 - .../ConfigBindingGenTestDriver.cs | 126 ++++ .../GeneratorTests.Baselines.cs | 2 +- .../GeneratorTests.Helpers.cs | 10 +- .../GeneratorTests.Incremental.cs | 185 +++--- .../SourceGenerationTests/GeneratorTests.cs | 12 +- ...ation.Binder.SourceGeneration.Tests.csproj | 14 +- .../gen/Helpers/RoslynExtensions.cs | 57 -- .../gen/Model/ContextGenerationSpec.cs | 2 +- .../gen/Model/ParameterGenerationSpec.cs | 2 + .../gen/Model/PropertyGenerationSpec.cs | 1 + .../PropertyInitializerGenerationSpec.cs | 2 + .../gen/Model/SourceGenerationOptionsSpec.cs | 1 + .../gen/Model/TypeGenerationSpec.cs | 1 + .../System.Text.Json.SourceGeneration.targets | 2 +- .../JsonSourceGeneratorIncrementalTests.cs | 74 +-- ...t.Json.SourceGeneration.Unit.Tests.targets | 1 + 48 files changed, 1376 insertions(+), 920 deletions(-) rename src/libraries/{System.Text.Json/gen/Model => Common/src/SourceGenerators}/TypeRef.cs (96%) create mode 100644 src/libraries/Common/tests/SourceGenerators/GeneratorTestHelpers.cs create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Helpers.cs create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs delete mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ComplexTypeSpec.cs delete mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs diff --git a/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs b/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs index 73c19d61ca122..1c00634fc48d3 100644 --- a/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs +++ b/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs @@ -3,6 +3,8 @@ using Microsoft.CodeAnalysis; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; namespace SourceGenerators { @@ -32,5 +34,62 @@ void TraverseContainingTypes(INamedTypeSymbol current) } } } + + public static string GetFullyQualifiedName(this ITypeSymbol type) => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + /// + /// Removes any type metadata that is erased at compile time, such as NRT annotations and tuple labels. + /// + public static ITypeSymbol EraseCompileTimeMetadata(this Compilation compilation, ITypeSymbol type) + { + if (type.NullableAnnotation is NullableAnnotation.Annotated) + { + type = type.WithNullableAnnotation(NullableAnnotation.None); + } + + if (type is INamedTypeSymbol namedType) + { + if (namedType.IsTupleType) + { + if (namedType.TupleElements.Length < 2) + { + return type; + } + + ImmutableArray erasedElements = namedType.TupleElements + .Select(e => compilation.EraseCompileTimeMetadata(e.Type)) + .ToImmutableArray(); + + type = compilation.CreateTupleTypeSymbol(erasedElements); + } + else if (namedType.IsGenericType) + { + if (namedType.IsUnboundGenericType) + { + return namedType; + } + + ImmutableArray typeArguments = namedType.TypeArguments; + INamedTypeSymbol? containingType = namedType.ContainingType; + + if (containingType?.IsGenericType == true) + { + containingType = (INamedTypeSymbol)compilation.EraseCompileTimeMetadata(containingType); + type = namedType = containingType.GetTypeMembers().First(t => t.Name == namedType.Name && t.Arity == namedType.Arity); + } + + if (typeArguments.Length > 0) + { + ITypeSymbol[] erasedTypeArgs = typeArguments + .Select(compilation.EraseCompileTimeMetadata) + .ToArray(); + + type = namedType.ConstructedFrom.Construct(erasedTypeArgs); + } + } + } + + return type; + } } } diff --git a/src/libraries/System.Text.Json/gen/Model/TypeRef.cs b/src/libraries/Common/src/SourceGenerators/TypeRef.cs similarity index 96% rename from src/libraries/System.Text.Json/gen/Model/TypeRef.cs rename to src/libraries/Common/src/SourceGenerators/TypeRef.cs index 050aba0cda658..cfbf33ed74136 100644 --- a/src/libraries/System.Text.Json/gen/Model/TypeRef.cs +++ b/src/libraries/Common/src/SourceGenerators/TypeRef.cs @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Diagnostics; using Microsoft.CodeAnalysis; -namespace System.Text.Json.SourceGeneration +namespace SourceGenerators { /// /// An equatable value representing type identity. diff --git a/src/libraries/Common/tests/SourceGenerators/GeneratorTestHelpers.cs b/src/libraries/Common/tests/SourceGenerators/GeneratorTestHelpers.cs new file mode 100644 index 0000000000000..d62a3c788e73d --- /dev/null +++ b/src/libraries/Common/tests/SourceGenerators/GeneratorTestHelpers.cs @@ -0,0 +1,84 @@ +// 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 System.Linq; +using System.Reflection; +using Xunit; + +namespace SourceGenerators.Tests +{ + internal static class GeneratorTestHelpers + { + /// + /// Asserts for structural equality, returning a path to the mismatching data when not equal. + /// + public static void AssertStructurallyEqual(T expected, T actual) + { + CheckAreEqualCore(expected, actual, new()); + static void CheckAreEqualCore(object expected, object actual, Stack path) + { + if (expected is null || actual is null) + { + if (expected is not null || actual is not null) + { + FailNotEqual(); + } + + return; + } + + Type type = expected.GetType(); + if (type != actual.GetType()) + { + FailNotEqual(); + return; + } + + if (expected is IEnumerable leftCollection) + { + if (actual is not IEnumerable rightCollection) + { + FailNotEqual(); + return; + } + + object?[] expectedValues = leftCollection.Cast().ToArray(); + object?[] actualValues = rightCollection.Cast().ToArray(); + + for (int i = 0; i < Math.Max(expectedValues.Length, actualValues.Length); i++) + { + object? expectedElement = i < expectedValues.Length ? expectedValues[i] : ""; + object? actualElement = i < actualValues.Length ? actualValues[i] : ""; + + path.Push($"[{i}]"); + CheckAreEqualCore(expectedElement, actualElement, path); + path.Pop(); + } + } + + if (type.GetProperty("EqualityContract", BindingFlags.Instance | BindingFlags.NonPublic, null, returnType: typeof(Type), types: Array.Empty(), null) != null) + { + // Type is a C# record, run pointwise equality comparison. + foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + path.Push("." + property.Name); + CheckAreEqualCore(property.GetValue(expected), property.GetValue(actual), path); + path.Pop(); + } + + return; + } + + if (!expected.Equals(actual)) + { + FailNotEqual(); + } + + void FailNotEqual() => Assert.Fail($"Value not equal in ${string.Join("", path.Reverse())}: expected {expected}, but was {actual}."); + } + } + } +} diff --git a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs index 5c101ca2c4753..e67289721d8af 100644 --- a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs +++ b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs @@ -110,11 +110,6 @@ public static Project WithDocuments(this Project project, IEnumerable so return result; } - public static Project WithDocument(this Project proj, string text) - { - return proj.AddDocument("src-0.cs", text).Project; - } - public static Project WithDocument(this Project proj, string name, string text) { return proj.AddDocument(name, text).Project; 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 f40542a6d4d4a..1721a124dead9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Diagnostics; using Microsoft.CodeAnalysis; using SourceGenerators; @@ -14,11 +12,16 @@ private sealed partial class Emitter { private readonly InterceptorInfo _interceptorInfo; private readonly BindingHelperInfo _bindingHelperInfo; + private readonly TypeIndex _typeIndex; private readonly SourceWriter _writer = new(); - public Emitter(SourceGenerationSpec sourceGenSpec) => - (_interceptorInfo, _bindingHelperInfo) = (sourceGenSpec.InterceptorInfo, sourceGenSpec.BindingHelperInfo); + public Emitter(SourceGenerationSpec sourceGenSpec) + { + _interceptorInfo = sourceGenSpec.InterceptorInfo; + _bindingHelperInfo = sourceGenSpec.BindingHelperInfo; + _typeIndex = new TypeIndex(sourceGenSpec.ConfigTypes); + } public void Emit(SourceProductionContext context) { 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 a1d45001baf05..fe0dbdc7989af 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; using SourceGenerators; @@ -15,43 +16,25 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerator { - internal sealed partial class Parser + internal sealed partial class Parser(CompilationData compilationData) { - private readonly struct InvocationDiagnosticInfo - { - public InvocationDiagnosticInfo(DiagnosticDescriptor descriptor, object[]? messageArgs) => - (Descriptor, MessageArgs) = (descriptor, messageArgs); - - public DiagnosticDescriptor Descriptor { get; } - public object[]? MessageArgs { get; } - } - - private readonly KnownTypeSymbols _typeSymbols; - private readonly bool _langVersionIsSupported; + private readonly KnownTypeSymbols _typeSymbols = compilationData.TypeSymbols!; + private readonly bool _langVersionIsSupported = compilationData.LanguageVersionIsSupported; - private readonly Dictionary _createdSpecs = new(SymbolEqualityComparer.Default); - private readonly HashSet _unsupportedTypes = new(SymbolEqualityComparer.Default); + private readonly List _invocationTypeParseInfo = new(); + private readonly Queue _typesToParse = new(); + private readonly Dictionary _createdTypeSpecs = new(SymbolEqualityComparer.Default); - // Data for incremental source generation spec. - private readonly BindingHelperInfo.Builder _helperInfoBuilder = new(); private readonly InterceptorInfo.Builder _interceptorInfoBuilder = new(); - - private readonly Dictionary> _typeDiagnostics = new(SymbolEqualityComparer.Default); - private readonly List _invocationTargetTypeDiags = new(); - - public Parser(CompilationData compilationData) - { - _typeSymbols = compilationData.TypeSymbols!; - _langVersionIsSupported = compilationData.LanguageVersionIsSupported; - } + private BindingHelperInfo.Builder? _helperInfoBuilder; // Init'ed with type index when registering interceptors, after creating type specs. public List? Diagnostics { get; private set; } - public SourceGenerationSpec? GetSourceGenerationSpec(ImmutableArray invocations) + public SourceGenerationSpec? GetSourceGenerationSpec(ImmutableArray invocations, CancellationToken cancellationToken) { if (!_langVersionIsSupported) { - ReportDiagnostic(DiagnosticDescriptors.LanguageVersionNotSupported, location: Location.None); + RecordDiagnostic(DiagnosticDescriptors.LanguageVersionNotSupported, trimmedLocation: Location.None.GetTrimmedLocation()); return null; } @@ -60,6 +43,20 @@ public Parser(CompilationData compilationData) return null; } + ParseInvocations(invocations); + CreateTypeSpecs(cancellationToken); + RegisterInterceptors(); + + return new SourceGenerationSpec + { + InterceptorInfo = _interceptorInfoBuilder.ToIncrementalValue(), + BindingHelperInfo = _helperInfoBuilder.ToIncrementalValue(), + ConfigTypes = _createdTypeSpecs.Values.OrderBy(s => s.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(), + }; + } + + private void ParseInvocations(ImmutableArray invocations) + { foreach (BinderInvocation? invocation in invocations) { Debug.Assert(invocation is not null); @@ -80,113 +77,130 @@ public Parser(CompilationData compilationData) ParseInvocation_ServiceCollectionExt(invocation); } } - - return new SourceGenerationSpec - { - InterceptorInfo = _interceptorInfoBuilder.ToIncrementalValue(), - BindingHelperInfo = _helperInfoBuilder.ToIncrementalValue() - }; } - private bool IsValidRootConfigType([NotNullWhen(true)] ITypeSymbol? type) + private void CreateTypeSpecs(CancellationToken cancellationToken) { - if (type is null || - type.SpecialType is SpecialType.System_Object or SpecialType.System_Void || - !_typeSymbols.Compilation.IsSymbolAccessibleWithin(type, _typeSymbols.Compilation.Assembly) || - type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || - type.IsRefLikeType || - ContainsGenericParameters(type)) + while (_typesToParse.Count > 0) { - return false; - } + cancellationToken.ThrowIfCancellationRequested(); + + TypeParseInfo typeParseInfo = _typesToParse.Dequeue(); + ITypeSymbol typeSymbol = typeParseInfo.TypeSymbol; - return true; + if (!_createdTypeSpecs.ContainsKey(typeSymbol)) + { + _createdTypeSpecs.Add(typeSymbol, CreateTypeSpec(typeParseInfo)); + } + } } - private TypeSpec? GetTargetTypeForRootInvocation(ITypeSymbol? type, Location? invocationLocation) + private void RegisterInterceptors() { - if (!IsValidRootConfigType(type)) + TypeIndex typeIndex = new(_createdTypeSpecs.Values); + _helperInfoBuilder = new(typeIndex); + + foreach (TypeParseInfo typeParseInfo in _invocationTypeParseInfo) { - ReportDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocationLocation); - return null; - } + TypeSpec typeSpec = _createdTypeSpecs[typeParseInfo.TypeSymbol]; + MethodsToGen overload = typeParseInfo.BindingOverload; - return GetTargetTypeForRootInvocationCore(type, invocationLocation); + if ((MethodsToGen.ConfigBinder_Any & overload) is not 0) + { + RegisterInterceptor_ConfigurationBinder(typeParseInfo, typeSpec); + } + else if ((MethodsToGen.OptionsBuilderExt_Any & overload) is not 0) + { + RegisterInterceptor_OptionsBuilderExt(typeParseInfo, typeSpec); + } + else + { + Debug.Assert((MethodsToGen.ServiceCollectionExt_Any & overload) is not 0); + RegisterInterceptor_ServiceCollectionExt(typeParseInfo, typeSpec); + } + } } - public TypeSpec? GetTargetTypeForRootInvocationCore(ITypeSymbol type, Location? invocationLocation) + private void EnqueueTargetTypeForRootInvocation(ITypeSymbol? typeSymbol, MethodsToGen bindingOverload, BinderInvocation binderInvocation) { - TypeSpec? spec = GetOrCreateTypeSpec(type); - - foreach (InvocationDiagnosticInfo diag in _invocationTargetTypeDiags) + if (!IsValidRootConfigType(typeSymbol)) { - ReportDiagnostic(diag.Descriptor, invocationLocation, diag.MessageArgs); + RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, binderInvocation); } + else + { + TypeParseInfo typeParseInfo = new TypeParseInfo(typeSymbol, _typeSymbols.Compilation) + { + BindingOverload = bindingOverload, + BinderInvocation = binderInvocation, + ContainingTypeDiagnosticInfo = null, + }; - _invocationTargetTypeDiags.Clear(); - return spec; + _typesToParse.Enqueue(typeParseInfo); + _invocationTypeParseInfo.Add(typeParseInfo); + } } - private TypeSpec? GetOrCreateTypeSpec(ITypeSymbol type) + private TypeRef EnqueueTransitiveType(TypeParseInfo containingTypeParseInfo, ITypeSymbol memberTypeSymbol, DiagnosticDescriptor diagDescriptor, string? memberName = null) { - if (_createdSpecs.TryGetValue(type, out TypeSpec? spec)) + TypeParseInfo memberTypeParseInfo = containingTypeParseInfo.ToTransitiveTypeParseInfo(memberTypeSymbol, _typeSymbols.Compilation, diagDescriptor, memberName); + + if (_createdTypeSpecs.TryGetValue(memberTypeSymbol, out TypeSpec? memberTypeSpec)) { - if (_typeDiagnostics.TryGetValue(type, out HashSet? typeDiags)) + if (memberTypeSpec is UnsupportedTypeSpec unsupportedTypeSpec) { - _invocationTargetTypeDiags.AddRange(typeDiags); + RecordDiagnostic(unsupportedTypeSpec, memberTypeParseInfo); } - return spec; + return memberTypeSpec.TypeRef; } + _typesToParse.Enqueue(memberTypeParseInfo); + return new TypeRef(memberTypeSymbol); + } + + private TypeSpec CreateTypeSpec(TypeParseInfo typeParseInfo) + { + ITypeSymbol type = typeParseInfo.TypeSymbol; + TypeSpec spec; + if (IsNullable(type, out ITypeSymbol? underlyingType)) { - spec = MemberTypeIsBindable(type, underlyingType, DiagnosticDescriptors.NullableUnderlyingTypeNotSupported, out TypeSpec? underlyingTypeSpec) - ? new NullableSpec(type, underlyingTypeSpec) - : null; + TypeRef underlyingTypeRef = EnqueueTransitiveType( + typeParseInfo, + underlyingType, + DiagnosticDescriptors.NullableUnderlyingTypeNotSupported); + + spec = new NullableSpec(type, underlyingTypeRef); } else if (IsParsableFromString(type, out StringParsableTypeKind specialTypeKind)) { ParsableFromStringSpec stringParsableSpec = new(type) { StringParsableTypeKind = specialTypeKind }; - _helperInfoBuilder.RegisterStringParsableType(stringParsableSpec); spec = stringParsableSpec; } - else if (IsSupportedArrayType(type)) + else if (type.TypeKind is TypeKind.Array) { - spec = CreateArraySpec((IArrayTypeSymbol)type); + spec = CreateArraySpec(typeParseInfo); + Debug.Assert(spec is ArraySpec or UnsupportedTypeSpec); } else if (IsCollection(type)) { - spec = CreateCollectionSpec((INamedTypeSymbol)type); + spec = CreateCollectionSpec(typeParseInfo); } else if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.IConfigurationSection)) { spec = new ConfigurationSectionSpec(type); } - else if (type is INamedTypeSymbol namedType) + else if (type is INamedTypeSymbol) { - // List is used in generated code as a temp holder for formatting - // an error for config properties that don't map to object properties. - _helperInfoBuilder.Namespaces.Add("System.Collections.Generic"); - - spec = CreateObjectSpec(namedType); + spec = CreateObjectSpec(typeParseInfo); } else { - RegisterUnsupportedType(type, DiagnosticDescriptors.TypeNotSupported); - } - - foreach (InvocationDiagnosticInfo diag in _invocationTargetTypeDiags) - { - RecordTypeDiagnostic(type, diag); + spec = CreateUnsupportedTypeSpec(typeParseInfo, NotSupportedReason.UnknownType); } - if (spec is { Namespace: string @namespace } && @namespace is not "") - { - _helperInfoBuilder.Namespaces.Add(@namespace); - } - - return _createdSpecs[type] = spec; + return spec; } private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType) @@ -307,229 +321,185 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t } } - private EnumerableSpec? CreateArraySpec(IArrayTypeSymbol arrayTypeSymbol) + private TypeSpec CreateArraySpec(TypeParseInfo typeParseInfo) { - if (_typeSymbols.List is not INamedTypeSymbol listTypeSymbol) - { - return null; - } - - ITypeSymbol elementTypeSymbol = arrayTypeSymbol.ElementType; + IArrayTypeSymbol typeSymbol = (IArrayTypeSymbol)typeParseInfo.TypeSymbol; - if (!MemberTypeIsBindable(arrayTypeSymbol, elementTypeSymbol, DiagnosticDescriptors.ElementTypeNotSupported, out TypeSpec? elementTypeSpec)) + if (typeSymbol.Rank > 1) { - return null; + return CreateUnsupportedTypeSpec(typeParseInfo, NotSupportedReason.MultiDimArraysNotSupported); } - // We want a BindCore method for List as a temp holder for the array values. - // Since the element type is supported, we can certainly a list of elements. - EnumerableSpec listTypeSpec = (EnumerableSpec)GetOrCreateTypeSpec(listTypeSymbol.Construct(elementTypeSymbol))!; + TypeRef elementTypeRef = EnqueueTransitiveType( + typeParseInfo, + typeSymbol.ElementType, + DiagnosticDescriptors.ElementTypeNotSupported); - EnumerableSpec spec = new EnumerableSpec(arrayTypeSymbol) + return new ArraySpec(typeSymbol) { - ElementType = elementTypeSpec, - InstantiationStrategy = InstantiationStrategy.Array, - PopulationStrategy = CollectionPopulationStrategy.Cast_Then_Add, // Using the concrete list type as a temp holder. - TypeToInstantiate = listTypeSpec, - PopulationCastType = null, + ElementTypeRef = elementTypeRef, }; - - bool registeredForBindCore = _helperInfoBuilder.TryRegisterTypeForBindCoreGen(listTypeSpec) && - _helperInfoBuilder.TryRegisterTypeForBindCoreGen(spec); - Debug.Assert(registeredForBindCore); - - return spec; } - private CollectionSpec? CreateCollectionSpec(INamedTypeSymbol type) + private TypeSpec CreateCollectionSpec(TypeParseInfo typeParseInfo) { - CollectionSpec? spec; + INamedTypeSymbol type = (INamedTypeSymbol)typeParseInfo.TypeSymbol; + + TypeSpec spec; if (IsCandidateDictionary(type, out ITypeSymbol? keyType, out ITypeSymbol? elementType)) { - spec = CreateDictionarySpec(type, keyType, elementType); - Debug.Assert(spec is null or DictionarySpec { KeyType: null or ParsableFromStringSpec }); + spec = CreateDictionarySpec(typeParseInfo, keyType, elementType); + Debug.Assert(spec is DictionarySpec or UnsupportedTypeSpec); } else { - spec = CreateEnumerableSpec(type); - } - - if (spec is null) - { - return null; + spec = CreateEnumerableSpec(typeParseInfo); + Debug.Assert(spec is EnumerableSpec or UnsupportedTypeSpec); } - bool registerForBindCoreGen = _helperInfoBuilder.TryRegisterTypeForBindCoreGen(spec); - Debug.Assert(registerForBindCoreGen); return spec; } - private DictionarySpec? CreateDictionarySpec(INamedTypeSymbol type, ITypeSymbol keyType, ITypeSymbol elementType) + private TypeSpec CreateDictionarySpec(TypeParseInfo typeParseInfo, ITypeSymbol keyTypeSymbol, ITypeSymbol elementTypeSymbol) { - if (!MemberTypeIsBindable(type, keyType, DiagnosticDescriptors.DictionaryKeyNotSupported, out TypeSpec? keySpec) || - !MemberTypeIsBindable(type, elementType, DiagnosticDescriptors.ElementTypeNotSupported, out TypeSpec? elementSpec)) - { - return null; - } - - if (keySpec.SpecKind is not TypeSpecKind.ParsableFromString) - { - RegisterUnsupportedType(type, DiagnosticDescriptors.DictionaryKeyNotSupported); - return null; - } + INamedTypeSymbol type = (INamedTypeSymbol)typeParseInfo.TypeSymbol; - InstantiationStrategy constructionStrategy; - CollectionPopulationStrategy populationStrategy; - INamedTypeSymbol? typeToInstantiate = null; - INamedTypeSymbol? populationCastType = null; + CollectionInstantiationStrategy instantiationStrategy; + CollectionInstantiationConcreteType instantiationConcreteType; + CollectionPopulationCastType populationCastType; if (HasPublicParameterLessCtor(type)) { - constructionStrategy = InstantiationStrategy.ParameterlessConstructor; + instantiationStrategy = CollectionInstantiationStrategy.ParameterlessConstructor; + instantiationConcreteType = CollectionInstantiationConcreteType.Self; - if (HasAddMethod(type, keyType, elementType)) + if (HasAddMethod(type, keyTypeSymbol, elementTypeSymbol)) { - populationStrategy = CollectionPopulationStrategy.Add; + populationCastType = CollectionPopulationCastType.NotApplicable; } - else if (GetInterface(type, _typeSymbols.GenericIDictionary_Unbound) is not null) + else if (_typeSymbols.GenericIDictionary is not null && GetInterface(type, _typeSymbols.GenericIDictionary_Unbound) is not null) { - populationCastType = _typeSymbols.GenericIDictionary; - populationStrategy = CollectionPopulationStrategy.Cast_Then_Add; + populationCastType = CollectionPopulationCastType.IDictionary; } else { - RegisterUnsupportedType(type, DiagnosticDescriptors.CollectionNotSupported); - return null; + return CreateUnsupportedCollectionSpec(typeParseInfo); } } - else if (IsInterfaceMatch(type, _typeSymbols.GenericIDictionary_Unbound) || IsInterfaceMatch(type, _typeSymbols.IDictionary)) + else if (_typeSymbols.Dictionary is not null && + (IsInterfaceMatch(type, _typeSymbols.GenericIDictionary_Unbound) || IsInterfaceMatch(type, _typeSymbols.IDictionary))) { - typeToInstantiate = _typeSymbols.Dictionary; - constructionStrategy = InstantiationStrategy.ParameterlessConstructor; - populationStrategy = CollectionPopulationStrategy.Add; + instantiationStrategy = CollectionInstantiationStrategy.ParameterlessConstructor; + instantiationConcreteType = CollectionInstantiationConcreteType.Dictionary; + populationCastType = CollectionPopulationCastType.NotApplicable; } - else if (IsInterfaceMatch(type, _typeSymbols.IReadOnlyDictionary_Unbound)) + else if (_typeSymbols.Dictionary is not null && IsInterfaceMatch(type, _typeSymbols.IReadOnlyDictionary_Unbound)) { - typeToInstantiate = _typeSymbols.Dictionary; - populationCastType = _typeSymbols.GenericIDictionary; - constructionStrategy = InstantiationStrategy.ToEnumerableMethod; - populationStrategy = CollectionPopulationStrategy.Cast_Then_Add; - _helperInfoBuilder.Namespaces.Add("System.Linq"); + instantiationStrategy = CollectionInstantiationStrategy.LinqToDictionary; + instantiationConcreteType = CollectionInstantiationConcreteType.Dictionary; + populationCastType = CollectionPopulationCastType.IDictionary; } else { - RegisterUnsupportedType(type, DiagnosticDescriptors.CollectionNotSupported); - return null; + return CreateUnsupportedCollectionSpec(typeParseInfo); } - Debug.Assert(!(populationStrategy is CollectionPopulationStrategy.Cast_Then_Add && populationCastType is null)); + TypeRef keyTypeRef = EnqueueTransitiveType(typeParseInfo, keyTypeSymbol, DiagnosticDescriptors.DictionaryKeyNotSupported); + TypeRef elementTypeRef = EnqueueTransitiveType(typeParseInfo, elementTypeSymbol, DiagnosticDescriptors.ElementTypeNotSupported); - DictionarySpec spec = new(type) + return new DictionarySpec(type) { - KeyType = (ParsableFromStringSpec)keySpec, - ElementType = elementSpec, - InstantiationStrategy = constructionStrategy, - PopulationStrategy = populationStrategy, - TypeToInstantiate = ConstructGenericCollectionSpecIfRequired(typeToInstantiate, keyType, elementType) as DictionarySpec, - PopulationCastType = ConstructGenericCollectionSpecIfRequired(populationCastType, keyType, elementType) as DictionarySpec, + KeyTypeRef = keyTypeRef, + ElementTypeRef = elementTypeRef, + InstantiationStrategy = instantiationStrategy, + InstantiationConcreteType = instantiationConcreteType, + PopulationCastType = populationCastType, }; - - return spec; } - private EnumerableSpec? CreateEnumerableSpec(INamedTypeSymbol type) + private TypeSpec CreateEnumerableSpec(TypeParseInfo typeParseInfo) { - if (!TryGetElementType(type, out ITypeSymbol? elementType) || - !MemberTypeIsBindable(type, elementType, DiagnosticDescriptors.ElementTypeNotSupported, out TypeSpec? elementSpec)) + INamedTypeSymbol type = (INamedTypeSymbol)typeParseInfo.TypeSymbol; + + if (!TryGetElementType(type, out ITypeSymbol? elementType)) { - return null; + return CreateUnsupportedCollectionSpec(typeParseInfo); } - InstantiationStrategy instantiationStrategy; - CollectionPopulationStrategy populationStrategy; - INamedTypeSymbol? typeToInstantiate = null; - INamedTypeSymbol? populationCastType = null; + CollectionInstantiationStrategy instantiationStrategy; + CollectionInstantiationConcreteType instantiationConcreteType; + CollectionPopulationCastType populationCastType; if (HasPublicParameterLessCtor(type)) { - instantiationStrategy = InstantiationStrategy.ParameterlessConstructor; + instantiationStrategy = CollectionInstantiationStrategy.ParameterlessConstructor; + instantiationConcreteType = CollectionInstantiationConcreteType.Self; if (HasAddMethod(type, elementType)) { - populationStrategy = CollectionPopulationStrategy.Add; + populationCastType = CollectionPopulationCastType.NotApplicable; } - else if (GetInterface(type, _typeSymbols.GenericICollection_Unbound) is not null) + else if (_typeSymbols.GenericICollection is not null && GetInterface(type, _typeSymbols.GenericICollection_Unbound) is not null) { - populationCastType = _typeSymbols.GenericICollection; - populationStrategy = CollectionPopulationStrategy.Cast_Then_Add; + populationCastType = CollectionPopulationCastType.ICollection; } else { - RegisterUnsupportedType(type, DiagnosticDescriptors.CollectionNotSupported); - return null; + return CreateUnsupportedCollectionSpec(typeParseInfo); } } - else if (IsInterfaceMatch(type, _typeSymbols.GenericICollection_Unbound) || - IsInterfaceMatch(type, _typeSymbols.GenericIList_Unbound)) + else if ((IsInterfaceMatch(type, _typeSymbols.GenericICollection_Unbound) || IsInterfaceMatch(type, _typeSymbols.GenericIList_Unbound))) { - typeToInstantiate = _typeSymbols.List; - instantiationStrategy = InstantiationStrategy.ParameterlessConstructor; - populationStrategy = CollectionPopulationStrategy.Add; + instantiationStrategy = CollectionInstantiationStrategy.ParameterlessConstructor; + instantiationConcreteType = CollectionInstantiationConcreteType.List; + populationCastType = CollectionPopulationCastType.NotApplicable; } else if (IsInterfaceMatch(type, _typeSymbols.GenericIEnumerable_Unbound)) { - typeToInstantiate = _typeSymbols.List; - populationCastType = _typeSymbols.GenericICollection; - instantiationStrategy = InstantiationStrategy.ParameterizedConstructor; - populationStrategy = CollectionPopulationStrategy.Cast_Then_Add; + instantiationStrategy = CollectionInstantiationStrategy.CopyConstructor; + instantiationConcreteType = CollectionInstantiationConcreteType.List; + populationCastType = CollectionPopulationCastType.ICollection; } else if (IsInterfaceMatch(type, _typeSymbols.ISet_Unbound)) { - typeToInstantiate = _typeSymbols.HashSet; - instantiationStrategy = InstantiationStrategy.ParameterlessConstructor; - populationStrategy = CollectionPopulationStrategy.Add; + instantiationStrategy = CollectionInstantiationStrategy.ParameterlessConstructor; + instantiationConcreteType = CollectionInstantiationConcreteType.HashSet; + populationCastType = CollectionPopulationCastType.NotApplicable; } else if (IsInterfaceMatch(type, _typeSymbols.IReadOnlySet_Unbound)) { - typeToInstantiate = _typeSymbols.HashSet; - populationCastType = _typeSymbols.ISet; - instantiationStrategy = InstantiationStrategy.ParameterizedConstructor; - populationStrategy = CollectionPopulationStrategy.Cast_Then_Add; + instantiationStrategy = CollectionInstantiationStrategy.CopyConstructor; + instantiationConcreteType = CollectionInstantiationConcreteType.HashSet; + populationCastType = CollectionPopulationCastType.ISet; } else if (IsInterfaceMatch(type, _typeSymbols.IReadOnlyList_Unbound) || IsInterfaceMatch(type, _typeSymbols.IReadOnlyCollection_Unbound)) { - typeToInstantiate = _typeSymbols.List; - populationCastType = _typeSymbols.GenericICollection; - instantiationStrategy = InstantiationStrategy.ParameterizedConstructor; - populationStrategy = CollectionPopulationStrategy.Cast_Then_Add; + instantiationStrategy = CollectionInstantiationStrategy.CopyConstructor; + instantiationConcreteType = CollectionInstantiationConcreteType.List; + populationCastType = CollectionPopulationCastType.ICollection; } else { - RegisterUnsupportedType(type, DiagnosticDescriptors.CollectionNotSupported); - return null; + return CreateUnsupportedCollectionSpec(typeParseInfo); } - Debug.Assert(!(populationStrategy is CollectionPopulationStrategy.Cast_Then_Add && populationCastType is null)); + TypeRef elementTypeRef = EnqueueTransitiveType(typeParseInfo, elementType, DiagnosticDescriptors.ElementTypeNotSupported); - EnumerableSpec spec = new(type) + return new EnumerableSpec(type) { - ElementType = elementSpec, + ElementTypeRef = elementTypeRef, InstantiationStrategy = instantiationStrategy, - PopulationStrategy = populationStrategy, - TypeToInstantiate = ConstructGenericCollectionSpecIfRequired(typeToInstantiate, elementType) as EnumerableSpec, - PopulationCastType = ConstructGenericCollectionSpecIfRequired(populationCastType, elementType) as EnumerableSpec, + InstantiationConcreteType = instantiationConcreteType, + PopulationCastType = populationCastType, }; - - return spec; } - private ObjectSpec? CreateObjectSpec(INamedTypeSymbol typeSymbol) + private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) { - // Add spec to cache before traversing properties to avoid stack overflow. - _createdSpecs.Add(typeSymbol, null); - + INamedTypeSymbol typeSymbol = (INamedTypeSymbol)typeParseInfo.TypeSymbol; string typeName = typeSymbol.GetTypeName().Name; - InstantiationStrategy initStrategy = InstantiationStrategy.None; + + ObjectInstantiationStrategy initializationStrategy = ObjectInstantiationStrategy.None; DiagnosticDescriptor? initDiagDescriptor = null; string? initExceptionMessage = null; @@ -582,13 +552,13 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t } else { - initStrategy = ctor.Parameters.Length is 0 ? InstantiationStrategy.ParameterlessConstructor : InstantiationStrategy.ParameterizedConstructor; + initializationStrategy = ctor.Parameters.Length is 0 ? ObjectInstantiationStrategy.ParameterlessConstructor : ObjectInstantiationStrategy.ParameterizedConstructor; } if (initDiagDescriptor is not null) { Debug.Assert(initExceptionMessage is not null); - RegisterUnsupportedType(typeSymbol, initDiagDescriptor); + RecordDiagnostic(typeParseInfo, initDiagDescriptor); } Dictionary? properties = null; @@ -602,24 +572,18 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t if (member is IPropertySymbol { IsIndexer: false, IsImplicitlyDeclared: false } property) { string propertyName = property.Name; - TypeSpec? propertyTypeSpec = GetOrCreateTypeSpec(property.Type); + TypeRef propertyTypeRef = EnqueueTransitiveType(typeParseInfo, property.Type, DiagnosticDescriptors.PropertyNotSupported, propertyName); - if (propertyTypeSpec?.CanBindTo is not true) - { - InvocationDiagnosticInfo propertyDiagnostic = new InvocationDiagnosticInfo(DiagnosticDescriptors.PropertyNotSupported, new string[] { propertyName, typeSymbol.ToDisplayString() }); - RecordTypeDiagnostic(causingType: typeSymbol, propertyDiagnostic); - _invocationTargetTypeDiags.Add(propertyDiagnostic); - } + AttributeData? attributeData = property.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.ConfigurationKeyNameAttribute)); + string configKeyName = attributeData?.ConstructorArguments.FirstOrDefault().Value as string ?? propertyName; - if (propertyTypeSpec is not null) + PropertySpec spec = new(property) { - AttributeData? attributeData = property.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.ConfigurationKeyNameAttribute)); - string configKeyName = attributeData?.ConstructorArguments.FirstOrDefault().Value as string ?? propertyName; - PropertySpec spec = new(property) { Type = propertyTypeSpec, ConfigurationKeyName = configKeyName }; + TypeRef = propertyTypeRef, + ConfigurationKeyName = configKeyName + }; - (properties ??= new(StringComparer.OrdinalIgnoreCase))[propertyName] = spec; - _helperInfoBuilder.Register_AsConfigWithChildren_HelperForGen_IfRequired(propertyTypeSpec); - } + (properties ??= new(StringComparer.OrdinalIgnoreCase))[propertyName] = spec; } } current = current.BaseType; @@ -627,7 +591,7 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t List? ctorParams = null; - if (initStrategy is InstantiationStrategy.ParameterizedConstructor) + if (initializationStrategy is ObjectInstantiationStrategy.ParameterizedConstructor) { Debug.Assert(ctor is not null); List? missingParameters = null; @@ -649,7 +613,7 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t { ParameterSpec paramSpec = new ParameterSpec(parameter) { - Type = propertySpec.Type, + TypeRef = propertySpec.TypeRef, ConfigurationKeyName = propertySpec.ConfigurationKeyName, }; @@ -666,7 +630,7 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t { if (typeSymbol.IsValueType) { - initStrategy = InstantiationStrategy.ParameterlessConstructor; + initializationStrategy = ObjectInstantiationStrategy.ParameterlessConstructor; } else { @@ -677,40 +641,24 @@ private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind t static string FormatParams(List names) => string.Join(",", names); } - ObjectSpec typeSpec = new(typeSymbol) - { - InstantiationStrategy = initStrategy, - Properties = properties?.Values.OrderBy(p => p.Name).ToImmutableEquatableArray(), - ConstructorParameters = ctorParams?.ToImmutableEquatableArray(), - InitExceptionMessage = initExceptionMessage - }; - - if (typeSpec is { InstantiationStrategy: InstantiationStrategy.ParameterizedConstructor, CanInstantiate: true }) - { - _helperInfoBuilder.RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.Initialize, typeSpec); - } - - Debug.Assert((typeSpec.CanInstantiate && initExceptionMessage is null) || - (!typeSpec.CanInstantiate && initExceptionMessage is not null) || - (!typeSpec.CanInstantiate && (typeSymbol.IsAbstract || typeSymbol.TypeKind is TypeKind.Interface))); - - _helperInfoBuilder.TryRegisterTypeForBindCoreGen(typeSpec); - return typeSpec; + return new ObjectSpec( + typeSymbol, + initializationStrategy, + properties: properties?.Values.OrderBy(p => p.Name).ToImmutableEquatableArray(), + constructorParameters: ctorParams?.ToImmutableEquatableArray(), + initExceptionMessage); } - private bool MemberTypeIsBindable(ITypeSymbol containingTypeSymbol, ITypeSymbol memberTypeSymbol, DiagnosticDescriptor containingTypeDiagDescriptor, [NotNullWhen(true)] out TypeSpec? memberTypeSpec) + private UnsupportedTypeSpec CreateUnsupportedTypeSpec(TypeParseInfo typeParseInfo, NotSupportedReason reason) { - if (GetOrCreateTypeSpec(memberTypeSymbol) is TypeSpec { CanBindTo: true } spec) - { - memberTypeSpec = spec; - return true; - } - - RegisterUnsupportedType(containingTypeSymbol, containingTypeDiagDescriptor); - memberTypeSpec = null; - return false; + UnsupportedTypeSpec type = new(typeParseInfo.TypeSymbol) { NotSupportedReason = reason }; + RecordDiagnostic(type, typeParseInfo); + return type; } + private UnsupportedTypeSpec CreateUnsupportedCollectionSpec(TypeParseInfo typeParseInfo) + => CreateUnsupportedTypeSpec(typeParseInfo, NotSupportedReason.CollectionNotSupported); + private bool TryGetElementType(INamedTypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? elementType) { INamedTypeSymbol? candidate = GetInterface(type, _typeSymbols.GenericIEnumerable_Unbound); @@ -751,22 +699,6 @@ private bool IsCandidateDictionary(INamedTypeSymbol type, [NotNullWhen(true)] ou private bool IsCollection(ITypeSymbol type) => type is INamedTypeSymbol namedType && GetInterface(namedType, _typeSymbols.IEnumerable) is not null; - private bool IsSupportedArrayType(ITypeSymbol type) - { - if (type is not IArrayTypeSymbol arrayType) - { - return false; - } - - if (arrayType.Rank > 1) - { - RegisterUnsupportedType(arrayType, DiagnosticDescriptors.MultiDimArraysNotSupported); - return false; - } - - return true; - } - private static INamedTypeSymbol? GetInterface(INamedTypeSymbol type, INamedTypeSymbol? @interface) { if (@interface is null) @@ -863,49 +795,44 @@ private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol key, ITypeSy private static bool IsEnum(ITypeSymbol type) => type is INamedTypeSymbol { EnumUnderlyingType: INamedTypeSymbol { } }; - private CollectionSpec? ConstructGenericCollectionSpecIfRequired(INamedTypeSymbol? collectionType, params ITypeSymbol[] parameters) => - (collectionType is not null ? ConstructGenericCollectionSpec(collectionType, parameters) : null); - - private CollectionSpec? ConstructGenericCollectionSpec(INamedTypeSymbol type, params ITypeSymbol[] parameters) + private void RecordDiagnostic(UnsupportedTypeSpec type, TypeParseInfo typeParseInfo) { - Debug.Assert(type.IsGenericType); - INamedTypeSymbol constructedType = type.Construct(parameters); - return CreateCollectionSpec(constructedType); + DiagnosticDescriptor descriptor = DiagnosticDescriptors.GetNotSupportedDescriptor(type.NotSupportedReason); + RecordDiagnostic(typeParseInfo, descriptor); } - private void RegisterUnsupportedType(ITypeSymbol type, DiagnosticDescriptor descriptor) + private void RecordDiagnostic(TypeParseInfo typeParseInfo, DiagnosticDescriptor descriptor) { - InvocationDiagnosticInfo diagInfo = new(descriptor, new string[] { type.ToDisplayString() }); + string typeName = typeParseInfo.TypeSymbol.GetName(); + BinderInvocation binderInvocation = typeParseInfo.BinderInvocation; + + RecordDiagnostic(descriptor, binderInvocation, new object?[] { typeName }); - if (!_unsupportedTypes.Contains(type)) + if (typeParseInfo.ContainingTypeDiagnosticInfo is ContainingTypeDiagnosticInfo containingTypeDiagInfo) { - RecordTypeDiagnostic(type, diagInfo); - _unsupportedTypes.Add(type); - } + object[] messageArgs = containingTypeDiagInfo.MemberName is string memberName + ? new[] { memberName, typeName } + : new[] { typeName }; - _invocationTargetTypeDiags.Add(diagInfo); + RecordDiagnostic(containingTypeDiagInfo.Descriptor, binderInvocation, messageArgs); + } } - private void RecordTypeDiagnostic(ITypeSymbol causingType, InvocationDiagnosticInfo info) + private void RecordDiagnostic(DiagnosticDescriptor descriptor, BinderInvocation binderInvocation, params object?[]? messageArgs) { - bool typeHadDiags = _typeDiagnostics.TryGetValue(causingType, out HashSet? typeDiags); - typeDiags ??= new HashSet(); - typeDiags.Add(info); - - if (!typeHadDiags) - { - _typeDiagnostics[causingType] = typeDiags; - } + Location trimmedLocation = InvocationLocationInfo.GetTrimmedLocation(binderInvocation.Operation); + RecordDiagnostic(descriptor, trimmedLocation, messageArgs); } - private void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location, params object?[]? messageArgs) => + private void RecordDiagnostic(DiagnosticDescriptor descriptor, Location trimmedLocation, params object?[]? messageArgs) + { (Diagnostics ??= new()).Add(new DiagnosticInfo { Descriptor = descriptor, - //Location = location?.GetTrimmedLocation(), - Location = location, + Location = trimmedLocation, MessageArgs = messageArgs ?? Array.Empty(), }); + } } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs index d2a6a50f6a22a..ec4b234a61045 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. //#define LAUNCH_DEBUGGER +using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using SourceGenerators; @@ -16,6 +17,8 @@ public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerato { private static readonly string ProjectName = Emitter.s_assemblyName.Name!; + public const string GenSpecTrackingName = nameof(SourceGenerationSpec); + public void Initialize(IncrementalGeneratorInitializationContext context) { #if LAUNCH_DEBUGGER @@ -44,17 +47,29 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return (null, null); } - Parser parser = new(compilationData); - SourceGenerationSpec? spec = parser.GetSourceGenerationSpec(tuple.Left); - ImmutableEquatableArray? diagnostics = parser.Diagnostics?.ToImmutableEquatableArray(); - return (spec, diagnostics); + try + { + Parser parser = new(compilationData); + SourceGenerationSpec? spec = parser.GetSourceGenerationSpec(tuple.Left, cancellationToken); + ImmutableEquatableArray? diagnostics = parser.Diagnostics?.ToImmutableEquatableArray(); + return (spec, diagnostics); + } + catch (Exception ex) + { + throw ex; + } }) - .WithTrackingName(nameof(SourceGenerationSpec)); + .WithTrackingName(GenSpecTrackingName); context.RegisterSourceOutput(genSpec, ReportDiagnosticsAndEmitSource); } - private static void ReportDiagnosticsAndEmitSource(SourceProductionContext sourceProductionContext, (SourceGenerationSpec? SourceGenerationSpec, ImmutableEquatableArray? Diagnostics) input) + /// + /// Instrumentation helper for unit tests. + /// + public Action? OnSourceEmitting { get; init; } + + private void ReportDiagnosticsAndEmitSource(SourceProductionContext sourceProductionContext, (SourceGenerationSpec? SourceGenerationSpec, ImmutableEquatableArray? Diagnostics) input) { if (input.Diagnostics is ImmutableEquatableArray diagnostics) { @@ -66,6 +81,7 @@ private static void ReportDiagnosticsAndEmitSource(SourceProductionContext sourc if (input.SourceGenerationSpec is SourceGenerationSpec spec) { + OnSourceEmitting?.Invoke(spec); Emitter emitter = new(spec); emitter.Emit(sourceProductionContext); } @@ -89,11 +105,5 @@ public CompilationData(CSharpCompilation compilation) } } } - - internal sealed record SourceGenerationSpec - { - public required InterceptorInfo InterceptorInfo { get; init; } - public required BindingHelperInfo BindingHelperInfo { get; init; } - } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs index c75fe9e333bb1..7d723139bde3e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/ConfigurationBinder.cs @@ -139,7 +139,7 @@ void EmitMethods(ImmutableEquatableArray? interc EmitInterceptsLocationAnnotations(locations); EmitStartBlock($"public static void {Identifier.Bind}_{type.IdentifierCompatibleSubstring}(this {Identifier.IConfiguration} {Identifier.configuration}, {additionalParams})"); - if (type.HasBindableMembers) + if (_typeIndex.HasBindableMembers(type)) { Debug.Assert(!type.IsValueType); string binderOptionsArg = configureOptions ? $"{Identifier.GetBinderOptions}({Identifier.configureOptions})" : $"{Identifier.binderOptions}: null"; @@ -147,7 +147,7 @@ void EmitMethods(ImmutableEquatableArray? interc EmitCheckForNullArgument_WithBlankLine(Identifier.configuration); EmitCheckForNullArgument_WithBlankLine(Identifier.instance, voidReturn: true); _writer.WriteLine($$""" - var {{Identifier.typedObj}} = ({{type.EffectiveType.DisplayString}}){{Identifier.instance}}; + var {{Identifier.typedObj}} = ({{type.DisplayString}}){{Identifier.instance}}; {{nameof(MethodsToGen_CoreBindingHelper.BindCore)}}({{configExpression}}, ref {{Identifier.typedObj}}, defaultValueIfNotFound: false, {{binderOptionsArg}}); """); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs index 6b4b3bc83e74d..768756545c9e3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -51,7 +51,7 @@ private void EmitConfigurationKeyCaches() continue; } - Debug.Assert(objectType.HasBindableMembers); + Debug.Assert(_typeIndex.HasBindableMembers(objectType)); HashSet? keys = null; static string GetCacheElement(MemberSpec member) => $@"""{member.ConfigurationKeyName}"""; @@ -102,8 +102,7 @@ private void EmitGetCoreMethod() bool isFirstType = true; foreach (TypeSpec type in targetTypes) { - TypeSpec effectiveType = type.EffectiveType; - TypeSpecKind kind = effectiveType.SpecKind; + TypeSpec effectiveType = _typeIndex.GetEffectiveTypeSpec(type); string conditionKindExpr = GetConditionKindExpr(ref isFirstType); EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))"); @@ -131,7 +130,7 @@ private void EmitGetCoreMethod() break; case ComplexTypeSpec complexType: { - if (complexType.CanInstantiate) + if (_typeIndex.CanInstantiate(complexType)) { EmitBindingLogic(complexType, Identifier.instance, Identifier.configuration, InitializationKind.Declaration, ValueDefaulting.CallSetter); _writer.WriteLine($"return {Identifier.instance};"); @@ -191,7 +190,7 @@ private void EmitGetValueCoreMethod() EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))"); EmitBindingLogic( - (ParsableFromStringSpec)type.EffectiveType, + (ParsableFromStringSpec)_typeIndex.GetEffectiveTypeSpec(type), Identifier.value, Expression.sectionPath, writeOnSuccess: (parsedValueExpr) => _writer.WriteLine($"return {parsedValueExpr};"), @@ -225,8 +224,8 @@ private void EmitBindCoreMainMethod() bool isFirstType = true; foreach (ComplexTypeSpec type in targetTypes) { - ComplexTypeSpec effectiveType = (ComplexTypeSpec)type.EffectiveType; - Debug.Assert(effectiveType.HasBindableMembers); + ComplexTypeSpec effectiveType = (ComplexTypeSpec)_typeIndex.GetEffectiveTypeSpec(type); + Debug.Assert(_typeIndex.HasBindableMembers(effectiveType)); string conditionKindExpr = GetConditionKindExpr(ref isFirstType); EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))"); @@ -250,7 +249,7 @@ private void EmitBindCoreMethods() foreach (ComplexTypeSpec type in types) { - Debug.Assert(type.HasBindableMembers); + Debug.Assert(_typeIndex.HasBindableMembers(type)); EmitBlankLineIfRequired(); EmitBindCoreMethod(type); } @@ -261,26 +260,35 @@ private void EmitBindCoreMethod(ComplexTypeSpec type) string objParameterExpression = $"ref {type.DisplayString} {Identifier.instance}"; EmitStartBlock(@$"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCore)}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression}, bool defaultValueIfNotFound, {Identifier.BinderOptions}? {Identifier.binderOptions})"); - ComplexTypeSpec effectiveType = (ComplexTypeSpec)type.EffectiveType; - if (effectiveType is EnumerableSpec enumerable) - { - if (effectiveType.InstantiationStrategy is InstantiationStrategy.Array) - { - Debug.Assert(type == effectiveType); - EmitPopulationImplForArray((EnumerableSpec)type); - } - else - { - EmitPopulationImplForEnumerableWithAdd(enumerable); - } - } - else if (effectiveType is DictionarySpec dictionary) - { - EmitBindCoreImplForDictionary(dictionary); - } - else + ComplexTypeSpec effectiveType = (ComplexTypeSpec)_typeIndex.GetEffectiveTypeSpec(type); + + switch (effectiveType) { - EmitBindCoreImplForObject((ObjectSpec)effectiveType); + case ArraySpec arrayType: + { + EmitBindCoreImplForArray(arrayType); + } + break; + case EnumerableSpec enumerableType: + { + EmitBindCoreImplForEnumerableWithAdd(enumerableType); + } + break; + case DictionarySpec dictionaryType: + { + EmitBindCoreImplForDictionary(dictionaryType); + } + break; + case ObjectSpec objectType: + { + EmitBindCoreImplForObject(objectType); + } + break; + default: + { + Debug.Fail($"Unsupported spec for bind core gen: {effectiveType.GetType()}"); + } + break; } EmitEndBlock(); @@ -302,8 +310,8 @@ private void EmitInitializeMethods() private void EmitInitializeMethod(ObjectSpec type) { - Debug.Assert(type.InstantiationStrategy is InstantiationStrategy.ParameterizedConstructor); - Debug.Assert(type.CanInstantiate); + Debug.Assert(type.InstantiationStrategy is ObjectInstantiationStrategy.ParameterizedConstructor); + Debug.Assert(_typeIndex.CanInstantiate(type)); Debug.Assert( type is { Properties: not null, ConstructorParameters: not null }, $"Expecting type for init method, {type.DisplayString}, to have both properties and ctor params."); @@ -361,7 +369,7 @@ private void EmitInitializeMethod(ObjectSpec type) void EmitBindImplForMember(MemberSpec member) { - TypeSpec memberType = member.Type; + TypeSpec memberType = _typeIndex.GetTypeSpec(member.TypeRef); string parsedMemberDeclarationLhs = $"{memberType.DisplayString} {member.Name}"; string configKeyName = member.ConfigurationKeyName; string parsedMemberAssignmentLhsExpr; @@ -644,7 +652,7 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.getPath}()}}", $"{{typeof({typeDisplayString})}}"); - EmitStartBlock($"public static {typeDisplayString} {type.ParseMethodName}(string {Identifier.value}, Func {Identifier.getPath})"); + EmitStartBlock($"public static {typeDisplayString} {GetParseMethodName(type)}(string {Identifier.value}, Func {Identifier.getPath})"); EmitEndBlock($$""" try { @@ -657,14 +665,19 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) """); } - private void EmitPopulationImplForArray(EnumerableSpec type) + private void EmitBindCoreImplForArray(ArraySpec type) { - Debug.Assert(type.TypeToInstantiate is { CanBindTo: true }); - EnumerableSpec typeToInstantiate = (EnumerableSpec)type.TypeToInstantiate; + TypeRef elementTypeRef = type.ElementTypeRef; + string elementTypeDisplayString = _typeIndex.GetTypeSpec(elementTypeRef).DisplayString; + string tempIdentifier = Identifier.temp; - // Create list and bind elements. - string tempIdentifier = GetIncrementalIdentifier(Identifier.temp); - EmitBindingLogic(typeToInstantiate, tempIdentifier, Identifier.configuration, InitializationKind.Declaration, ValueDefaulting.None); + // Create temp list. + _writer.WriteLine($"var {tempIdentifier} = new List<{elementTypeDisplayString}>();"); + _writer.WriteLine(); + + // Bind elements to temp list. + EmitBindingLogicForEnumerableWithAdd(elementTypeRef, tempIdentifier); + _writer.WriteLine(); // Resize array and add binded elements. _writer.WriteLine($$""" @@ -674,15 +687,19 @@ private void EmitPopulationImplForArray(EnumerableSpec type) """); } - private void EmitPopulationImplForEnumerableWithAdd(EnumerableSpec type) + private void EmitBindCoreImplForEnumerableWithAdd(EnumerableSpec type) { EmitCollectionCastIfRequired(type, out string instanceIdentifier); + EmitBindingLogicForEnumerableWithAdd(type.ElementTypeRef, instanceIdentifier); + } + private void EmitBindingLogicForEnumerableWithAdd(TypeRef elementTypeRef, string enumerableIdentifier) + { Emit_Foreach_Section_In_ConfigChildren_StartBlock(); - string addExpr = $"{instanceIdentifier}.{Identifier.Add}"; + string addExpr = $"{enumerableIdentifier}.{Identifier.Add}"; - switch (type.ElementType) + switch (_typeIndex.GetEffectiveTypeSpec(elementTypeRef)) { case ParsableFromStringSpec stringParsableType: { @@ -701,7 +718,7 @@ private void EmitPopulationImplForEnumerableWithAdd(EnumerableSpec type) _writer.WriteLine($"{addExpr}({Identifier.section});"); } break; - case ComplexTypeSpec { CanInstantiate: true } complexType: + case ComplexTypeSpec complexType when _typeIndex.CanInstantiate(complexType): { EmitBindingLogic(complexType, Identifier.value, Identifier.section, InitializationKind.Declaration, ValueDefaulting.None); _writer.WriteLine($"{addExpr}({Identifier.value});"); @@ -718,8 +735,8 @@ private void EmitBindCoreImplForDictionary(DictionarySpec type) Emit_Foreach_Section_In_ConfigChildren_StartBlock(); - ParsableFromStringSpec keyType = type.KeyType; - TypeSpec elementType = type.ElementType; + ParsableFromStringSpec keyType = (ParsableFromStringSpec)_typeIndex.GetEffectiveTypeSpec(type.KeyTypeRef); + TypeSpec elementType = _typeIndex.GetTypeSpec(type.ElementTypeRef); // Parse key EmitBindingLogic( @@ -754,7 +771,7 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) break; case ComplexTypeSpec complexElementType: { - Debug.Assert(complexElementType.CanInstantiate); + Debug.Assert(_typeIndex.CanBindTo(complexElementType)); if (keyType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) { @@ -776,12 +793,28 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) conditionToUseExistingElement += $" && {expressionForElementIsNotNull}"; } - EmitStartBlock($"if (!({conditionToUseExistingElement}))"); - EmitObjectInit(complexElementType, Identifier.element, InitializationKind.SimpleAssignment, Identifier.section); - EmitEndBlock(); + if (_typeIndex.CanInstantiate(complexElementType)) + { + EmitStartBlock($"if (!({conditionToUseExistingElement}))"); + EmitObjectInit(complexElementType, Identifier.element, InitializationKind.SimpleAssignment, Identifier.section); + EmitEndBlock(); - EmitBindingLogic(complexElementType, Identifier.element, Identifier.section, InitializationKind.None, ValueDefaulting.None); - _writer.WriteLine($"{instanceIdentifier}[{parsedKeyExpr}] = {Identifier.element};"); + EmitBindingLogic(); + } + else + { + EmitStartBlock($"if ({conditionToUseExistingElement})"); + EmitBindingLogic(); + EmitEndBlock(); + } + + void EmitBindingLogic() => this.EmitBindingLogic( + complexElementType, + Identifier.element, + Identifier.section, + InitializationKind.None, + ValueDefaulting.None, + writeOnSuccess: parsedValueExpr => _writer.WriteLine($"{instanceIdentifier}[{parsedKeyExpr}] = {parsedValueExpr};")); } break; } @@ -792,16 +825,17 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) private void EmitBindCoreImplForObject(ObjectSpec type) { - Debug.Assert(type is { HasBindableMembers: true, Properties: not null }); + Debug.Assert(_typeIndex.HasBindableMembers(type)); string keyCacheFieldName = GetConfigKeyCacheFieldName(type); string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.DisplayString}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});"; _writer.WriteLine(validateMethodCallExpr); - foreach (PropertySpec property in type.Properties) + foreach (PropertySpec property in type.Properties!) { - bool noSetter_And_IsReadonly = !property.CanSet && property.Type is CollectionSpec { InstantiationStrategy: InstantiationStrategy.ParameterizedConstructor }; - if (property.ShouldBindTo && !noSetter_And_IsReadonly) + //bool noSetter_And_IsReadonly = !property.CanSet && property.TypeRef is CollectionSpec { InstantiationStrategy: ObjectInstantiationStrategy.ParameterizedConstructor }; + //if (property.ShouldBindTo && !noSetter_And_IsReadonly) + if (property.ShouldBindTo) { string containingTypeRef = property.IsStatic ? type.DisplayString : Identifier.instance; EmitBindImplForMember( @@ -821,11 +855,9 @@ private bool EmitBindImplForMember( bool canSet, InitializationKind initializationKind) { - TypeSpec effectiveMemberType = member.Type.EffectiveType; - string sectionParseExpr = GetSectionFromConfigurationExpression(member.ConfigurationKeyName); - switch (effectiveMemberType) + switch (_typeIndex.GetEffectiveTypeSpec(member.TypeRef)) { case ParsableFromStringSpec stringParsableType: { @@ -834,8 +866,8 @@ private bool EmitBindImplForMember( bool useDefaultValueIfSectionValueIsNull = initializationKind == InitializationKind.Declaration && member is PropertySpec && - member.Type.IsValueType && - member.Type.SpecKind is not TypeSpecKind.Nullable; + member.TypeRef.IsValueType && + _typeIndex.GetTypeSpec(member.TypeRef) is not NullableSpec; EmitBlankLineIfRequired(); EmitBindingLogic( @@ -870,7 +902,7 @@ member is PropertySpec && EmitBindingLogicForComplexMember(member, memberAccessExpr, sectionIdentifier, canSet); EmitEndBlock(); - return complexType.CanInstantiate; + return _typeIndex.CanInstantiate(complexType); } default: return false; @@ -884,8 +916,8 @@ private void EmitBindingLogicForComplexMember( bool canSet) { - TypeSpec memberType = member.Type; - ComplexTypeSpec effectiveMemberType = (ComplexTypeSpec)memberType.EffectiveType; + TypeSpec memberType = _typeIndex.GetTypeSpec(member.TypeRef); + ComplexTypeSpec effectiveMemberType = (ComplexTypeSpec)_typeIndex.GetEffectiveTypeSpec(memberType); string tempIdentifier = GetIncrementalIdentifier(Identifier.temp); InitializationKind initKind; @@ -902,7 +934,7 @@ private void EmitBindingLogicForComplexMember( string effectiveMemberTypeDisplayString = effectiveMemberType.DisplayString; initKind = InitializationKind.None; - if (memberType.SpecKind is TypeSpecKind.Nullable) + if (memberType is NullableSpec) { string nullableTempIdentifier = GetIncrementalIdentifier(Identifier.temp); @@ -957,11 +989,11 @@ private void EmitBindingLogic( ValueDefaulting valueDefaulting, Action? writeOnSuccess = null) { - if (!type.HasBindableMembers) + if (!_typeIndex.HasBindableMembers(type)) { if (initKind is not InitializationKind.None) { - if (type.CanInstantiate) + if (_typeIndex.CanInstantiate(type)) { EmitObjectInit(type, memberAccessExpr, initKind, configArgExpr); } @@ -995,7 +1027,7 @@ void EmitBindingLogic(string instanceToBindExpr, InitializationKind initKind) { string bindCoreCall = $@"{nameof(MethodsToGen_CoreBindingHelper.BindCore)}({configArgExpr}, ref {instanceToBindExpr}, defaultValueIfNotFound: {FormatDefaultValueIfNotFound()}, {Identifier.binderOptions});"; - if (type.CanInstantiate) + if (_typeIndex.CanInstantiate(type)) { if (initKind is not InitializationKind.None) { @@ -1048,7 +1080,7 @@ private void EmitBindingLogic( { StringParsableTypeKind.AssignFromSectionValue => stringValueToParse_Expr, StringParsableTypeKind.Enum => $"ParseEnum<{type.DisplayString}>({stringValueToParse_Expr}, () => {sectionPathExpr})", - _ => $"{type.ParseMethodName}({stringValueToParse_Expr}, () => {sectionPathExpr})", + _ => $"{GetParseMethodName(type)}({stringValueToParse_Expr}, () => {sectionPathExpr})", }; if (!checkForNullSectionValue) @@ -1076,30 +1108,44 @@ private void EmitBindingLogic( private bool EmitObjectInit(ComplexTypeSpec type, string memberAccessExpr, InitializationKind initKind, string configArgExpr) { CollectionSpec? collectionType = type as CollectionSpec; + ObjectSpec? objectType = type as ObjectSpec; string initExpr; string effectiveDisplayString = type.DisplayString; if (collectionType is not null) { - if (collectionType is EnumerableSpec { InstantiationStrategy: InstantiationStrategy.Array }) + if (collectionType is ArraySpec) { initExpr = $"new {s_arrayBracketsRegex.Replace(effectiveDisplayString, "[0]", 1)}"; } else { - effectiveDisplayString = (collectionType.TypeToInstantiate ?? collectionType).DisplayString; - initExpr = $"new {effectiveDisplayString}()"; + CollectionWithCtorInitSpec collectionWithCtorInitType = (CollectionWithCtorInitSpec)collectionType; + + // Prepend with a cast to make sure we call the right BindCore method. + string castExpr = collectionWithCtorInitType.InstantiationConcreteType is CollectionInstantiationConcreteType.Self + ? string.Empty + : $"({collectionWithCtorInitType.DisplayString})"; + + effectiveDisplayString = _typeIndex.GetInstantiationTypeDisplayString(collectionWithCtorInitType); + initExpr = $"{castExpr}new {effectiveDisplayString}()"; } } - else if (type.InstantiationStrategy is InstantiationStrategy.ParameterlessConstructor) - { - initExpr = $"new {effectiveDisplayString}()"; - } else { - Debug.Assert(type.InstantiationStrategy is InstantiationStrategy.ParameterizedConstructor); - string initMethodIdentifier = GetInitalizeMethodDisplayString(((ObjectSpec)type)); - initExpr = $"{initMethodIdentifier}({configArgExpr}, {Identifier.binderOptions})"; + Debug.Assert(objectType is not null); + ObjectInstantiationStrategy strategy = objectType.InstantiationStrategy; + + if (strategy is ObjectInstantiationStrategy.ParameterlessConstructor) + { + initExpr = $"new {effectiveDisplayString}()"; + } + else + { + Debug.Assert(strategy is ObjectInstantiationStrategy.ParameterizedConstructor); + string initMethodIdentifier = GetInitalizeMethodDisplayString(((ObjectSpec)type)); + initExpr = $"{initMethodIdentifier}({configArgExpr}, {Identifier.binderOptions})"; + } } switch (initKind) @@ -1112,20 +1158,17 @@ private bool EmitObjectInit(ComplexTypeSpec type, string memberAccessExpr, Initi break; case InitializationKind.AssignmentWithNullCheck: { - if (collectionType is CollectionSpec + + if (collectionType is CollectionWithCtorInitSpec { - InstantiationStrategy: InstantiationStrategy.ParameterizedConstructor or InstantiationStrategy.ToEnumerableMethod - }) + InstantiationStrategy: CollectionInstantiationStrategy.CopyConstructor or CollectionInstantiationStrategy.LinqToDictionary + } collectionWithCtorInit) { - if (collectionType.InstantiationStrategy is InstantiationStrategy.ParameterizedConstructor) - { - _writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr} is null ? {initExpr} : new {effectiveDisplayString}({memberAccessExpr});"); - } - else - { - Debug.Assert(collectionType is DictionarySpec); - _writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr} is null ? {initExpr} : {memberAccessExpr}.ToDictionary(pair => pair.Key, pair => pair.Value);"); - } + string assignmentValueIfMemberNull = collectionWithCtorInit.InstantiationStrategy is CollectionInstantiationStrategy.CopyConstructor + ? $"new {effectiveDisplayString}({memberAccessExpr})" + : $"{memberAccessExpr}.ToDictionary(pair => pair.Key, pair => pair.Value)"; + + _writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr} is null ? {initExpr} : {assignmentValueIfMemberNull};"); } else { @@ -1160,20 +1203,25 @@ private void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn) _writer.WriteLine(); } - private void EmitCollectionCastIfRequired(CollectionSpec type, out string instanceIdentifier) + private void EmitCollectionCastIfRequired(CollectionWithCtorInitSpec type, out string instanceIdentifier) { - instanceIdentifier = Identifier.instance; - if (type.PopulationStrategy is CollectionPopulationStrategy.Cast_Then_Add) + if (type.PopulationCastType is CollectionPopulationCastType.NotApplicable) { - instanceIdentifier = Identifier.temp; - _writer.WriteLine($$""" - if ({{Identifier.instance}} is not {{type.PopulationCastType!.DisplayString}} {{instanceIdentifier}}) + instanceIdentifier = Identifier.instance; + return; + } + + string castTypeDisplayString = _typeIndex.GetPopulationCastTypeDisplayString(type); + instanceIdentifier = Identifier.temp; + + _writer.WriteLine($$""" + if ({{Identifier.instance}} is not {{castTypeDisplayString}} {{instanceIdentifier}}) { return; } """); - _writer.WriteLine(); - } + _writer.WriteLine(); + } private void Emit_Foreach_Section_In_ConfigChildren_StartBlock() => @@ -1204,6 +1252,12 @@ private static string GetConditionKindExpr(ref bool isFirstType) private static string GetConfigKeyCacheFieldName(ObjectSpec type) => $"s_configKeys_{type.IdentifierCompatibleSubstring}"; + + private static string GetParseMethodName(ParsableFromStringSpec type) + { + Debug.Assert(type.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue); + return $"Parse{type.IdentifierCompatibleSubstring}"; + } } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs index 5a372889b2f46..34a97d3c64c76 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs @@ -1,11 +1,9 @@ // 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; -using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { 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 f72a9f4f9a3e8..6b1940639665b 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 @@ -1,7 +1,6 @@ netstandard2.0 - $(NetCoreAppToolCurrent) false false true @@ -28,6 +27,7 @@ + @@ -42,6 +42,7 @@ + @@ -51,9 +52,9 @@ + + - - diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs index de8e32054e1c7..649d9db34e772 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis; +using System.Diagnostics; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -87,20 +88,17 @@ private void ParseBindInvocation_ConfigurationBinder(BinderInvocation invocation if (!IsValidRootConfigType(type)) { - ReportDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); + RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation); return; } if (type.IsValueType) { - ReportDiagnostic(DiagnosticDescriptors.ValueTypesInvalidForBind, invocation.Location, type); + RecordDiagnostic(DiagnosticDescriptors.ValueTypesInvalidForBind, invocation, messageArgs: new object[] { type }); return; } - if (GetTargetTypeForRootInvocationCore(type, invocation.Location) is ComplexTypeSpec typeSpec) - { - _interceptorInfoBuilder.RegisterInterceptor_ConfigBinder_Bind(overload, typeSpec, invocation.Operation); - } + EnqueueTargetTypeForRootInvocation(type, overload, invocation); static ITypeSymbol? ResolveType(IOperation conversionOperation) => conversionOperation switch @@ -184,12 +182,7 @@ private void ParseGetInvocation(BinderInvocation invocation) } } - if (GetTargetTypeForRootInvocation(type, invocation.Location) is TypeSpec typeSpec) - { - _interceptorInfoBuilder.RegisterInterceptor(overload, operation); - _helperInfoBuilder.RegisterTypeForGetCoreGen(typeSpec); - } - + EnqueueTargetTypeForRootInvocation(type, overload, invocation); } private void ParseGetValueInvocation(BinderInvocation invocation) @@ -246,17 +239,47 @@ private void ParseGetValueInvocation(BinderInvocation invocation) if (!IsValidRootConfigType(type)) { - ReportDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); + RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation); return; } ITypeSymbol effectiveType = IsNullable(type, out ITypeSymbol? underlyingType) ? underlyingType : type; - if (IsParsableFromString(effectiveType, out _) && - GetTargetTypeForRootInvocationCore(type, invocation.Location) is TypeSpec typeSpec) + if (IsParsableFromString(effectiveType, out _)) + { + EnqueueTargetTypeForRootInvocation(type, overload, invocation); + } + } + + private void RegisterInterceptor_ConfigurationBinder(TypeParseInfo typeParseInfo, TypeSpec typeSpec) + { + MethodsToGen overload = typeParseInfo.BindingOverload; + IInvocationOperation invocationOperation = typeParseInfo.BinderInvocation!.Operation; + Debug.Assert((MethodsToGen.ConfigBinder_Any & overload) is not 0); + + if ((MethodsToGen.ConfigBinder_Bind & overload) is not 0) { - _interceptorInfoBuilder.RegisterInterceptor(overload, operation); - _helperInfoBuilder.RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, typeSpec); + if (typeSpec is ComplexTypeSpec complexTypeSpec) + { + _interceptorInfoBuilder.RegisterInterceptor_ConfigBinder_Bind(overload, complexTypeSpec, invocationOperation); + _helperInfoBuilder.RegisterComplexTypeForMethodGen(complexTypeSpec); + } + } + else + { + Debug.Assert((MethodsToGen.ConfigBinder_Get & overload) is not 0 || + (MethodsToGen.ConfigBinder_GetValue & overload) is not 0); + + _interceptorInfoBuilder.RegisterInterceptor(overload, invocationOperation); + + if ((MethodsToGen.ConfigBinder_Get & overload) is not 0) + { + _helperInfoBuilder.RegisterTypeForGetCoreGen(typeSpec); + } + else + { + _helperInfoBuilder.RegisterTypeForGetValueCoreGen(typeSpec); + } } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs index 59d3d3afa28d0..3f694c78be830 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/DiagnosticDescriptors.cs @@ -62,6 +62,20 @@ private static DiagnosticDescriptor CreateTypeNotSupportedDescriptor(string name category: ProjectName, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); + + public static DiagnosticDescriptor GetNotSupportedDescriptor(NotSupportedReason reason) => + reason switch + { + NotSupportedReason.UnknownType => TypeNotSupported, + NotSupportedReason.MissingPublicInstanceConstructor => MissingPublicInstanceConstructor, + NotSupportedReason.CollectionNotSupported => CollectionNotSupported, + NotSupportedReason.DictionaryKeyNotSupported => DictionaryKeyNotSupported, + NotSupportedReason.ElementTypeNotSupported => ElementTypeNotSupported, + NotSupportedReason.MultipleParameterizedConstructors => MultipleParameterizedConstructors, + NotSupportedReason.MultiDimArraysNotSupported => MultiDimArraysNotSupported, + NotSupportedReason.NullableUnderlyingTypeNotSupported => NullableUnderlyingTypeNotSupported, + _ => throw new InvalidOperationException() + }; } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs index 3f6b2aec1b8d4..54750fee3903b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs @@ -68,9 +68,12 @@ public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type) public static (string? Namespace, string DisplayString, string Name) GetTypeName(this ITypeSymbol type) { string? @namespace = type.ContainingNamespace?.ToDisplayString(); + @namespace = @namespace is not "" ? @namespace : null; string displayString = type.ToDisplayString(s_minimalDisplayFormat); string name = (@namespace is null ? string.Empty : @namespace + ".") + displayString.Replace(".", "+"); return (@namespace, displayString, name); } + + public static string GetName(this ITypeSymbol type) => GetTypeName(type).Name; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Helpers.cs new file mode 100644 index 0000000000000..3f3eba9363e7f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Helpers.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerator + { + internal sealed partial class Parser + { + private readonly struct InvocationDiagnosticInfo + { + public InvocationDiagnosticInfo(DiagnosticDescriptor descriptor, object[]? messageArgs) => + (Descriptor, MessageArgs) = (descriptor, messageArgs); + + public DiagnosticDescriptor Descriptor { get; } + public object[]? MessageArgs { get; } + } + + private readonly struct TypeParseInfo + { + public ITypeSymbol TypeSymbol { get; } + public required MethodsToGen BindingOverload { get; init; } + public required BinderInvocation BinderInvocation { get; init; } + public required ContainingTypeDiagnosticInfo? ContainingTypeDiagnosticInfo { get; init; } + + public TypeParseInfo(ITypeSymbol type, Compilation compilation) + { + Debug.Assert(compilation is not null); + // Trim compile-time erased metadata such as tuple labels and NRT annotations. + //TypeSymbol = compilation.EraseCompileTimeMetadata(type); + TypeSymbol = type; + } + + public TypeParseInfo ToTransitiveTypeParseInfo(ITypeSymbol memberType, Compilation compilation, DiagnosticDescriptor? diagDescriptor = null, string? memberName = null) + { + ContainingTypeDiagnosticInfo? diagnosticInfo = diagDescriptor is null + ? null + : new ContainingTypeDiagnosticInfo + { + TypeName = TypeSymbol.GetTypeName().Name, + Descriptor = diagDescriptor, + MemberName = memberName, + }; + + return new TypeParseInfo(memberType, compilation) + { + BindingOverload = BindingOverload, + BinderInvocation = BinderInvocation, + ContainingTypeDiagnosticInfo = diagnosticInfo, + }; + } + } + + private readonly struct ContainingTypeDiagnosticInfo + { + public required string TypeName { get; init; } + public required string? MemberName { get; init; } + public required DiagnosticDescriptor Descriptor { get; init; } + } + + private bool IsValidRootConfigType([NotNullWhen(true)] ITypeSymbol? type) + { + if (type is null || + type.SpecialType is SpecialType.System_Object or SpecialType.System_Void || + !_typeSymbols.Compilation.IsSymbolAccessibleWithin(type, _typeSymbols.Compilation.Assembly) || + type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || + type.IsRefLikeType || + ContainsGenericParameters(type)) + { + return false; + } + + return true; + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs index 23aed6a7ae364..7b02999b0b7ba 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs @@ -29,22 +29,17 @@ private void ParseInvocation_OptionsBuilderExt(BinderInvocation invocation) // This would violate generic type constraint; any such invocation could not have been included in the initial parser. Debug.Assert(typeSymbol?.IsValueType is not true); - if (GetTargetTypeForRootInvocation(typeSymbol, invocation.Location) is not ComplexTypeSpec typeSpec) - { - return; - } - if (targetMethod.Name is "Bind") { - ParseBindInvocation_OptionsBuilderExt(invocation, typeSpec); + ParseBindInvocation_OptionsBuilderExt(invocation, typeSymbol); } else if (targetMethod.Name is "BindConfiguration") { - ParseBindConfigurationInvocation(invocation, typeSpec); + ParseBindConfigurationInvocation(invocation, typeSymbol); } } - private void ParseBindInvocation_OptionsBuilderExt(BinderInvocation invocation, ComplexTypeSpec typeSpec) + private void ParseBindInvocation_OptionsBuilderExt(BinderInvocation invocation, ITypeSymbol? type) { IInvocationOperation operation = invocation.Operation!; IMethodSymbol targetMethod = operation.TargetMethod; @@ -66,14 +61,13 @@ 3 when SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, _ => MethodsToGen.None }; - if (overload is not MethodsToGen.None && - TryRegisterTypeForMethodGen(MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions, typeSpec)) + if (overload is not MethodsToGen.None) { - RegisterInvocation(overload, operation); + EnqueueTargetTypeForRootInvocation(type, overload, invocation); } } - private void ParseBindConfigurationInvocation(BinderInvocation invocation, ComplexTypeSpec typeSpec) + private void ParseBindConfigurationInvocation(BinderInvocation invocation, ITypeSymbol? type) { IMethodSymbol targetMethod = invocation.Operation.TargetMethod; ImmutableArray @params = targetMethod.Parameters; @@ -83,16 +77,32 @@ private void ParseBindConfigurationInvocation(BinderInvocation invocation, Compl if (paramCount is 3 && @params[1].Type.SpecialType is SpecialType.System_String && - SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type) && - _helperInfoBuilder.TryRegisterTypeForBindCoreMainGen(typeSpec)) + SymbolEqualityComparer.Default.Equals(_typeSymbols.ActionOfBinderOptions, @params[2].Type)) { - RegisterInvocation(MethodsToGen.OptionsBuilderExt_BindConfiguration_T_path_BinderOptions, invocation.Operation); + EnqueueTargetTypeForRootInvocation(type, MethodsToGen.OptionsBuilderExt_BindConfiguration_T_path_BinderOptions, invocation); } } - private void RegisterInvocation(MethodsToGen overload, IInvocationOperation operation) + private void RegisterInterceptor_OptionsBuilderExt(TypeParseInfo typeParseInfo, TypeSpec typeSpec) { - _interceptorInfoBuilder.RegisterInterceptor(overload, operation); + MethodsToGen overload = typeParseInfo.BindingOverload; + Debug.Assert((MethodsToGen.OptionsBuilderExt_Any & overload) is not 0); + + if (typeSpec is not ComplexTypeSpec complexTypeSpec) + { + return; + } + + if ((MethodsToGen.OptionsBuilderExt_Bind & overload) is not 0) + { + RegisterTypeForOverloadGen_ServiceCollectionExt(MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions, complexTypeSpec); + } + else + { + _helperInfoBuilder.TryRegisterTypeForBindCoreMainGen(complexTypeSpec); + } + + _interceptorInfoBuilder.RegisterInterceptor(typeParseInfo.BindingOverload, typeParseInfo.BinderInvocation.Operation); // Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource. _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.Options"); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs index 3e7a5cd086403..e21e80b894baa 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs @@ -73,26 +73,29 @@ @params[1].Type.SpecialType is SpecialType.System_String && // This would violate generic type constraint; any such invocation could not have been included in the initial parser. Debug.Assert(typeSymbol?.IsValueType is not true); - if (GetTargetTypeForRootInvocation(typeSymbol, invocation.Location) is ComplexTypeSpec typeSpec && - TryRegisterTypeForMethodGen(overload, typeSpec)) + EnqueueTargetTypeForRootInvocation(typeSymbol, overload, invocation); + } + + private void RegisterInterceptor_ServiceCollectionExt(TypeParseInfo typeParseInfo, TypeSpec typeSpec) + { + MethodsToGen overload = typeParseInfo.BindingOverload; + + if (typeSpec is ComplexTypeSpec complexTypeSpec) { - _interceptorInfoBuilder.RegisterInterceptor(overload, operation); + RegisterTypeForOverloadGen_ServiceCollectionExt(overload, complexTypeSpec); + _interceptorInfoBuilder.RegisterInterceptor(overload, typeParseInfo.BinderInvocation.Operation); } } - private bool TryRegisterTypeForMethodGen(MethodsToGen overload, ComplexTypeSpec typeSpec) + private void RegisterTypeForOverloadGen_ServiceCollectionExt(MethodsToGen overload, ComplexTypeSpec typeSpec) { Debug.Assert((MethodsToGen.ServiceCollectionExt_Any & overload) is not 0); - if (_helperInfoBuilder.TryRegisterTypeForBindCoreMainGen(typeSpec)) - { - _interceptorInfoBuilder.MethodsToGen |= overload; - _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); - // Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource, IConfigureOptions<>, ConfigureNamedOptions<>. - _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.Options"); - return true; - } + _helperInfoBuilder.TryRegisterTypeForBindCoreMainGen(typeSpec); - return false; + _interceptorInfoBuilder.MethodsToGen |= overload; + _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); + // Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource, IConfigureOptions<>, ConfigureNamedOptions<>. + _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.Options"); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs index f1eaea3ab588e..21cb6b146d89e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs @@ -3,13 +3,12 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal sealed record BindingHelperInfo + public sealed record BindingHelperInfo { public required ImmutableEquatableArray Namespaces { get; init; } public required bool EmitConfigurationKeyCaches { get; init; } @@ -22,7 +21,7 @@ internal sealed record BindingHelperInfo public required ImmutableEquatableArray? TypesForGen_Initialize { get; init; } public required ImmutableEquatableArray? TypesForGen_ParsePrimitive { get; init; } - public sealed class Builder + internal sealed class Builder(TypeIndex typeIndex) { private MethodsToGen_CoreBindingHelper _methodsToGen; private bool _emitConfigurationKeyCaches; @@ -38,69 +37,79 @@ public sealed class Builder "Microsoft.Extensions.Configuration", }; - public bool TryRegisterTypeForBindCoreMainGen(ComplexTypeSpec type) + public void RegisterComplexTypeForMethodGen(ComplexTypeSpec type) { - if (type.HasBindableMembers) + if (type is ObjectSpec { InstantiationStrategy: ObjectInstantiationStrategy.ParameterizedConstructor } objectType + && typeIndex.CanInstantiate(objectType)) + { + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.Initialize, type); + } + else if (type is DictionarySpec { InstantiationStrategy: CollectionInstantiationStrategy.LinqToDictionary }) { - bool registeredForBindCoreGen = TryRegisterTypeForBindCoreGen(type); - Debug.Assert(registeredForBindCoreGen); + Namespaces.Add("System.Linq"); + } + RegisterTypeForBindCoreGen(type); + } + + public bool TryRegisterTypeForBindCoreMainGen(ComplexTypeSpec type) + { + if (typeIndex.HasBindableMembers(type)) + { RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreMain, type); - Register_AsConfigWithChildren_HelperForGen_IfRequired(type); + RegisterTypeForBindCoreGen(type); + RegisterForGen_AsConfigWithChildrenHelper(); return true; } return false; } - public bool TryRegisterTypeForBindCoreGen(ComplexTypeSpec type) + public void RegisterTypeForBindCoreGen(ComplexTypeSpec type) { - if (type.HasBindableMembers) + if (typeIndex.HasBindableMembers(type)) { RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, type); if (type is ObjectSpec) { _emitConfigurationKeyCaches = true; - } - return true; + // List is used in generated code as a temp holder for formatting + // an error for config properties that don't map to object properties. + Namespaces.Add("System.Collections.Generic"); + } } - - return false; } public void RegisterTypeForGetCoreGen(TypeSpec type) { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, type); - Register_AsConfigWithChildren_HelperForGen_IfRequired(type); - } + ComplexTypeSpec? complexType = type as ComplexTypeSpec; - public void RegisterStringParsableType(ParsableFromStringSpec type) - { - if (type.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) + if (complexType is null || typeIndex.CanInstantiate(complexType)) { - _methodsToGen |= MethodsToGen_CoreBindingHelper.ParsePrimitive; - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.ParsePrimitive, type); + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, type); } - } - public void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type) - { - if (!_typesForGen.TryGetValue(method, out HashSet? types)) + if (complexType is not null) { - _typesForGen[method] = types = new HashSet(); + RegisterComplexTypeForMethodGen(complexType); } + } - types.Add(type); - _methodsToGen |= method; + public void RegisterTypeForGetValueCoreGen(TypeSpec type) + { + ParsableFromStringSpec effectiveType = (ParsableFromStringSpec)typeIndex.GetEffectiveTypeSpec(type); + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, type); + RegisterStringParsableType(effectiveType); } - public void Register_AsConfigWithChildren_HelperForGen_IfRequired(TypeSpec type) + public void RegisterStringParsableType(ParsableFromStringSpec type) { - if (type is ComplexTypeSpec) + if (type.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) { - _methodsToGen |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren; + _methodsToGen |= MethodsToGen_CoreBindingHelper.ParsePrimitive; + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.ParsePrimitive, type); } } @@ -139,8 +148,81 @@ public BindingHelperInfo ToIncrementalValue() static ImmutableEquatableArray GetTypesForGen(IEnumerable types) where TSpec : TypeSpec, IEquatable => - types.OrderBy(t => t.AssemblyQualifiedName).ToImmutableEquatableArray(); + types.OrderBy(t => t.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(); } + + private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type) + { + if (!_typesForGen.TryGetValue(method, out HashSet? types)) + { + _typesForGen[method] = types = new HashSet(); + } + + if (types.Add(type)) + { + _methodsToGen |= method; + + if (type is { Namespace: string @namespace }) + { + Namespaces.Add(@namespace); + } + + RegisterTransitiveTypesForMethodGen(type); + } + } + + private void RegisterTransitiveTypesForMethodGen(TypeSpec typeSpec) + { + switch (typeSpec) + { + case NullableSpec nullableTypeSpec: + { + RegisterTransitiveTypesForMethodGen(typeIndex.GetEffectiveTypeSpec(nullableTypeSpec)); + } + break; + case ArraySpec: + case EnumerableSpec: + { + RegisterComplexTypeForMethodGen(((CollectionSpec)typeSpec).ElementTypeRef); + } + break; + case DictionarySpec dictionaryTypeSpec: + { + RegisterComplexTypeForMethodGen(dictionaryTypeSpec.KeyTypeRef); + RegisterComplexTypeForMethodGen(dictionaryTypeSpec.ElementTypeRef); + } + break; + case ObjectSpec objectType when typeIndex.HasBindableMembers(objectType): + { + foreach (PropertySpec property in objectType.Properties!) + { + RegisterComplexTypeForMethodGen(property.TypeRef); + } + } + break; + } + + void RegisterComplexTypeForMethodGen(TypeRef transitiveTypeRef) + { + TypeSpec effectiveTypeSpec = typeIndex.GetTypeSpec(transitiveTypeRef); + + if (effectiveTypeSpec is ParsableFromStringSpec parsableFromStringSpec) + { + RegisterStringParsableType(parsableFromStringSpec); + } + else if (effectiveTypeSpec is ComplexTypeSpec complexEffectiveTypeSpec) + { + if (typeIndex.HasBindableMembers(complexEffectiveTypeSpec)) + { + RegisterForGen_AsConfigWithChildrenHelper(); + } + + this.RegisterComplexTypeForMethodGen(complexEffectiveTypeSpec); + } + } + } + + private void RegisterForGen_AsConfigWithChildrenHelper() => _methodsToGen |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren; } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs index 7e143d86b17e7..b12dd3fef000d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs @@ -12,7 +12,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal sealed record InterceptorInfo + public sealed record InterceptorInfo { public required MethodsToGen MethodsToGen { get; init; } @@ -49,9 +49,9 @@ internal sealed record InterceptorInfo internal sealed class Builder { - private TypeInterceptorInfoBuildler? _configBinder_InfoBuilder_Bind_instance; - private TypeInterceptorInfoBuildler? _configBinder_InfoBuilder_Bind_instance_BinderOptions; - private TypeInterceptorInfoBuildler? _configBinder_InfoBuilder_Bind_key_instance; + private TypedInterceptorInfoBuildler? _configBinder_InfoBuilder_Bind_instance; + private TypedInterceptorInfoBuildler? _configBinder_InfoBuilder_Bind_instance_BinderOptions; + private TypedInterceptorInfoBuildler? _configBinder_InfoBuilder_Bind_key_instance; private List? _interceptors_configBinder; private List? _interceptors_OptionsBuilderExt; @@ -78,7 +78,7 @@ public void RegisterInterceptor_ConfigBinder_Bind(MethodsToGen overload, Complex MethodsToGen |= overload; - void RegisterInterceptor(ref TypeInterceptorInfoBuildler? infoBuilder) => + void RegisterInterceptor(ref TypedInterceptorInfoBuildler? infoBuilder) => (infoBuilder ??= new()).RegisterInterceptor(overload, type, invocation); } @@ -122,7 +122,7 @@ public InterceptorInfo ToIncrementalValue() => } } - internal sealed class TypeInterceptorInfoBuildler + internal sealed class TypedInterceptorInfoBuildler { private readonly Dictionary _invocationInfoBuilderCache = new(); @@ -139,11 +139,11 @@ public void RegisterInterceptor(MethodsToGen overload, ComplexTypeSpec type, IIn public ImmutableEquatableArray? ToIncrementalValue() => _invocationInfoBuilderCache.Values .Select(b => b.ToIncrementalValue()) - .OrderBy(i => i.TargetType.AssemblyQualifiedName) + .OrderBy(i => i.TargetType.TypeRef.FullyQualifiedName) .ToImmutableEquatableArray(); } - internal sealed record TypedInterceptorInvocationInfo + public sealed record TypedInterceptorInvocationInfo { public required ComplexTypeSpec TargetType { get; init; } public required ImmutableEquatableArray Locations { get; init; } @@ -166,32 +166,45 @@ public void RegisterInvocation(IInvocationOperation invocation) => } } - internal sealed record InvocationLocationInfo + public sealed record InvocationLocationInfo { public InvocationLocationInfo(MethodsToGen interceptor, IInvocationOperation invocation) { - MemberAccessExpressionSyntax memberAccessExprSyntax = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)invocation.Syntax).Expression); - SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; - TextSpan memberNameSpan = memberAccessExprSyntax.Name.Span; - FileLinePositionSpan linePosSpan = operationSyntaxTree.GetLineSpan(memberNameSpan); - + FileLinePositionSpan linePosSpan = GetLocationSpanInfo(invocation).LinePostionSpan; Interceptor = interceptor; LineNumber = linePosSpan.StartLinePosition.Line + 1; CharacterNumber = linePosSpan.StartLinePosition.Character + 1; - FilePath = GetInterceptorFilePath(); - - // Use 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 - string GetInterceptorFilePath() - { - SourceReferenceResolver? sourceReferenceResolver = invocation.SemanticModel?.Compilation.Options.SourceReferenceResolver; - return sourceReferenceResolver?.NormalizePath(operationSyntaxTree.FilePath, baseFilePath: null) ?? operationSyntaxTree.FilePath; - } + FilePath = GetFilePath(invocation); } public MethodsToGen Interceptor { get; } public string FilePath { get; } public int LineNumber { get; } public int CharacterNumber { get; } + + public static Location GetTrimmedLocation(IInvocationOperation invocation) + { + string filePath = GetFilePath(invocation); + (TextSpan memberNameSpan, FileLinePositionSpan linePosSpan) = GetLocationSpanInfo(invocation); + return Location.Create(filePath, memberNameSpan, linePosSpan.Span); + } + + // Use 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 GetFilePath(IInvocationOperation invocation) + { + SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; + SourceReferenceResolver? sourceReferenceResolver = invocation.SemanticModel?.Compilation.Options.SourceReferenceResolver; + return sourceReferenceResolver?.NormalizePath(operationSyntaxTree.FilePath, baseFilePath: null) ?? operationSyntaxTree.FilePath; + } + + private static (TextSpan MemberNameSpan, FileLinePositionSpan LinePostionSpan) GetLocationSpanInfo(IInvocationOperation invocation) + { + MemberAccessExpressionSyntax memberAccessExprSyntax = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)invocation.Syntax).Expression); + TextSpan memberNameSpan = memberAccessExprSyntax.Name.Span; + SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; + FileLinePositionSpan linePosSpan = operationSyntaxTree.GetLineSpan(memberNameSpan); + return (memberNameSpan, linePosSpan); + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs index effd550482595..dc5b03087ac87 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/MemberSpec.cs @@ -3,10 +3,11 @@ using System.Diagnostics; using Microsoft.CodeAnalysis; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal abstract record MemberSpec + public abstract record MemberSpec { public MemberSpec(ISymbol member) { @@ -18,7 +19,7 @@ public MemberSpec(ISymbol member) public string Name { get; } public string DefaultValueExpr { get; protected set; } - public required TypeSpec Type { get; init; } + public required TypeRef TypeRef { get; init; } public required string ConfigurationKeyName { get; init; } public abstract bool CanGet { get; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/ParameterSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/ParameterSpec.cs index d7e560ad3542f..62c781e1f1631 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/ParameterSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/ParameterSpec.cs @@ -6,7 +6,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal sealed record ParameterSpec : MemberSpec + public sealed record ParameterSpec : MemberSpec { public ParameterSpec(IParameterSymbol parameter) : base(parameter) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs index 4e9c468c4e335..fec66376df142 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs @@ -5,7 +5,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal sealed record PropertySpec : MemberSpec + public sealed record PropertySpec : MemberSpec { public PropertySpec(IPropertySymbol property) : base(property) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs index ff77a099c91c5..af2a33fa6c2f8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/MethodsToGen.cs @@ -6,7 +6,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { [Flags] - internal enum MethodsToGen_CoreBindingHelper + public enum MethodsToGen_CoreBindingHelper { None = 0x0, BindCore = 0x1, @@ -23,7 +23,7 @@ internal enum MethodsToGen_CoreBindingHelper /// Methods on Microsoft.Extensions.Configuration.ConfigurationBinder /// [Flags] - internal enum MethodsToGen + public enum MethodsToGen { None = 0x0, Any = ConfigBinder_Any | OptionsBuilderExt_Any | ServiceCollectionExt_Any, diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs new file mode 100644 index 0000000000000..4f57316429e2b --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs @@ -0,0 +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 SourceGenerators; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + public sealed record SourceGenerationSpec + { + public required InterceptorInfo InterceptorInfo { get; init; } + public required BindingHelperInfo BindingHelperInfo { get; init; } + public required ImmutableEquatableArray ConfigTypes { get; init; } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs new file mode 100644 index 0000000000000..5d1d8ade4a49b --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs @@ -0,0 +1,98 @@ +// 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.Linq; +using SourceGenerators; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + internal sealed class TypeIndex + { + private readonly Dictionary _index; + + public TypeIndex(IEnumerable typeSpecs) => _index = typeSpecs.ToDictionary(spec => spec.TypeRef); + + public bool CanBindTo(TypeRef typeRef) => CanBindTo(GetEffectiveTypeSpec(typeRef)); + + public bool CanBindTo(TypeSpec typeSpec) => typeSpec switch + { + SimpleTypeSpec => true, + ComplexTypeSpec complexTypeSpec => CanInstantiate(complexTypeSpec) || HasBindableMembers(complexTypeSpec), + _ => throw new InvalidOperationException(), + }; + + public bool CanInstantiate(ComplexTypeSpec typeSpec) + { + return CanInstantiate(typeSpec.TypeRef); + + bool CanInstantiate(TypeRef typeRef) => GetEffectiveTypeSpec(typeRef) switch + { + ObjectSpec objectType => objectType is { InstantiationStrategy: not ObjectInstantiationStrategy.None, InitExceptionMessage: null }, + DictionarySpec dictionaryType => GetTypeSpec(dictionaryType.TypeRef) is ParsableFromStringSpec, + CollectionSpec => true, + _ => throw new InvalidOperationException(), + }; + } + + public bool HasBindableMembers(ComplexTypeSpec typeSpec) => + typeSpec switch + { + ObjectSpec objectSpec => objectSpec.Properties?.Any(p => p.ShouldBindTo) is true, + ArraySpec or EnumerableSpec => CanBindTo(((CollectionSpec)typeSpec).ElementTypeRef), + DictionarySpec dictSpec => GetTypeSpec(dictSpec.KeyTypeRef) is ParsableFromStringSpec && CanBindTo(dictSpec.ElementTypeRef), + _ => throw new InvalidOperationException(), + }; + + public TypeSpec GetEffectiveTypeSpec(TypeRef typeRef) + { + TypeSpec typeSpec = GetTypeSpec(typeRef); + return GetEffectiveTypeSpec(typeSpec); + } + + public TypeSpec GetEffectiveTypeSpec(TypeSpec typeSpec) + { + TypeRef effectiveRef = typeSpec.EffectiveTypeRef; + TypeSpec effectiveSpec = effectiveRef == typeSpec.TypeRef ? typeSpec : _index[effectiveRef]; + return effectiveSpec; + } + + public TypeSpec GetTypeSpec(TypeRef typeRef) => _index[typeRef]; + + public string GetInstantiationTypeDisplayString(CollectionWithCtorInitSpec type) + { + CollectionInstantiationConcreteType concreteType = type.InstantiationConcreteType; + Debug.Assert(concreteType is not CollectionInstantiationConcreteType.None); + + if (concreteType is CollectionInstantiationConcreteType.Self) + { + return type.DisplayString; + } + + return GetGenericTypeDisplayString(type, concreteType); + } + + public string GetPopulationCastTypeDisplayString(CollectionWithCtorInitSpec type) + { + CollectionPopulationCastType castType = type.PopulationCastType; + Debug.Assert(castType is not CollectionPopulationCastType.NotApplicable); + return GetGenericTypeDisplayString(type, castType); + } + + public string GetGenericTypeDisplayString(CollectionWithCtorInitSpec type, Enum genericProxyTypeName) + { + string proxyTypeNameStr = genericProxyTypeName.ToString(); + string elementTypeDisplayString = GetTypeSpec(type.ElementTypeRef).DisplayString; + + if (type is EnumerableSpec) + { + return $"{proxyTypeNameStr}<{elementTypeDisplayString}>"; + } + + string keyTypeDisplayString = GetTypeSpec(((DictionarySpec)type).KeyTypeRef).DisplayString; + return $"{proxyTypeNameStr}<{keyTypeDisplayString}, {elementTypeDisplayString}>"; + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs index 93745a1219511..3f320f278f7db 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs @@ -2,50 +2,69 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.CodeAnalysis; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { internal abstract record CollectionSpec : ComplexTypeSpec { - public CollectionSpec(ITypeSymbol type) : base(type) { } + protected CollectionSpec(ITypeSymbol type) : base(type) { } - public sealed override bool CanInstantiate => TypeToInstantiate?.CanInstantiate ?? InstantiationStrategy is not InstantiationStrategy.None; + public required TypeRef ElementTypeRef { get; init; } - public sealed override required InstantiationStrategy InstantiationStrategy { get; set; } + } - public required TypeSpec ElementType { get; init; } + internal abstract record CollectionWithCtorInitSpec : CollectionSpec + { + protected CollectionWithCtorInitSpec(ITypeSymbol type) : base(type) { } - public required CollectionPopulationStrategy PopulationStrategy { get; init; } + public required CollectionInstantiationStrategy InstantiationStrategy { get; init; } - public required CollectionSpec? TypeToInstantiate { get; init; } + public required CollectionInstantiationConcreteType InstantiationConcreteType { get; init; } - public required CollectionSpec? PopulationCastType { get; init; } + public required CollectionPopulationCastType PopulationCastType { get; init; } } - internal sealed record EnumerableSpec : CollectionSpec + internal sealed record ArraySpec : CollectionSpec { - public EnumerableSpec(ITypeSymbol type) : base(type) { } - - public override TypeSpecKind SpecKind => TypeSpecKind.Enumerable; + public ArraySpec(ITypeSymbol type) : base(type) { } + } - public override bool HasBindableMembers => PopulationStrategy is not CollectionPopulationStrategy.Unknown && ElementType.CanBindTo; + internal sealed record EnumerableSpec : CollectionWithCtorInitSpec + { + public EnumerableSpec(ITypeSymbol type) : base(type) { } } - internal sealed record DictionarySpec : CollectionSpec + internal sealed record DictionarySpec : CollectionWithCtorInitSpec { public DictionarySpec(INamedTypeSymbol type) : base(type) { } - public override TypeSpecKind SpecKind => TypeSpecKind.Dictionary; + public required TypeRef KeyTypeRef { get; init; } + } - public override bool HasBindableMembers => PopulationStrategy is not CollectionPopulationStrategy.Unknown; + internal enum CollectionInstantiationStrategy + { + NotApplicable = 0, + ParameterlessConstructor = 1, + CopyConstructor = 2, + LinqToDictionary = 3, + AssignableConcreteTypeWithParameterlessCtor = 4, + } - public required ParsableFromStringSpec KeyType { get; init; } + internal enum CollectionInstantiationConcreteType + { + None = 0, + Self = 1, + Dictionary = 2, + List = 3, + HashSet = 4, } - internal enum CollectionPopulationStrategy + internal enum CollectionPopulationCastType { - Unknown = 0, - Add = 1, - Cast_Then_Add = 2, + NotApplicable = 0, + IDictionary = 1, + ICollection = 2, + ISet = 3, } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ComplexTypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ComplexTypeSpec.cs deleted file mode 100644 index 7bcd0c65072a3..0000000000000 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ComplexTypeSpec.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.CodeAnalysis; - -namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration -{ - internal abstract record ComplexTypeSpec : TypeSpec - { - public ComplexTypeSpec(ITypeSymbol type) : base(type) { } - - public virtual InstantiationStrategy InstantiationStrategy { get; set; } - - public sealed override bool CanBindTo => CanInstantiate || HasBindableMembers; - - public sealed override TypeSpec EffectiveType => this; - - public abstract bool HasBindableMembers { get; } - } - - internal enum InstantiationStrategy - { - None = 0, - ParameterlessConstructor = 1, - ParameterizedConstructor = 2, - ToEnumerableMethod = 3, - Array = 4, - } -} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs deleted file mode 100644 index 3de6d7d465ad9..0000000000000 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/NullableSpec.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.CodeAnalysis; - -namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration -{ - internal sealed record NullableSpec : TypeSpec - { - private readonly TypeSpec _underlyingType; - - public NullableSpec(ITypeSymbol type, TypeSpec underlyingType) : base(type) => _underlyingType = underlyingType; - - public override bool CanBindTo => _underlyingType.CanBindTo; - - public override bool CanInstantiate => _underlyingType.CanInstantiate; - - public override TypeSpecKind SpecKind => TypeSpecKind.Nullable; - - public override TypeSpec EffectiveType => _underlyingType; - } -} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs index 402453aa86527..abc01258d4190 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/ObjectSpec.cs @@ -1,26 +1,39 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Linq; using Microsoft.CodeAnalysis; using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal sealed record ObjectSpec : ComplexTypeSpec + public sealed record ObjectSpec : ComplexTypeSpec { - public ObjectSpec(INamedTypeSymbol type) : base(type) { } - - public override TypeSpecKind SpecKind => TypeSpecKind.Object; - - public override bool HasBindableMembers => Properties?.Any(p => p.ShouldBindTo) is true; - - public override bool CanInstantiate => InstantiationStrategy is not InstantiationStrategy.None && InitExceptionMessage is null; - - public ImmutableEquatableArray? Properties { get; set; } - - public ImmutableEquatableArray? ConstructorParameters { get; set; } + public ObjectSpec( + INamedTypeSymbol type, + ObjectInstantiationStrategy instantiationStrategy, + ImmutableEquatableArray? properties, + ImmutableEquatableArray? constructorParameters, + string? initExceptionMessage) : base(type) + { + InstantiationStrategy = instantiationStrategy; + Properties = properties; + ConstructorParameters = constructorParameters; + InitExceptionMessage = initExceptionMessage; + } + + public ObjectInstantiationStrategy InstantiationStrategy { get; } + + public ImmutableEquatableArray? Properties { get; } + + public ImmutableEquatableArray? ConstructorParameters { get; } + + public string? InitExceptionMessage { get; } + } - public string? InitExceptionMessage { get; set; } + public enum ObjectInstantiationStrategy + { + None = 0, + ParameterlessConstructor = 1, + ParameterizedConstructor = 2, } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs index 2dfe08dc5f547..70c7a8042e035 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/SimpleTypeSpec.cs @@ -1,55 +1,28 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using Microsoft.CodeAnalysis; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal abstract record SimpleTypeSpec : TypeSpec + public abstract record SimpleTypeSpec : TypeSpec { public SimpleTypeSpec(ITypeSymbol type) : base(type) { } - - public sealed override bool CanBindTo => true; - - public sealed override TypeSpec EffectiveType => this; - - public sealed override bool CanInstantiate => true; } internal sealed record ConfigurationSectionSpec : SimpleTypeSpec { public ConfigurationSectionSpec(ITypeSymbol type) : base(type) { } - - public override TypeSpecKind SpecKind => TypeSpecKind.IConfigurationSection; } - internal sealed record ParsableFromStringSpec : SimpleTypeSpec + public sealed record ParsableFromStringSpec : SimpleTypeSpec { public ParsableFromStringSpec(ITypeSymbol type) : base(type) { } - public override TypeSpecKind SpecKind => TypeSpecKind.ParsableFromString; - public required StringParsableTypeKind StringParsableTypeKind { get; init; } - - private string? _parseMethodName; - public string ParseMethodName - { - get - { - Debug.Assert(StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue); - - _parseMethodName ??= StringParsableTypeKind is StringParsableTypeKind.ByteArray - ? "ParseByteArray" - // MinimalDisplayString.Length is certainly > 2. - : $"Parse{(char.ToUpper(DisplayString[0]) + DisplayString.Substring(1)).Replace(".", "")}"; - - return _parseMethodName; - } - } } - internal enum StringParsableTypeKind + public enum StringParsableTypeKind { None = 0, diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs index 575afc75be766..803606caab5cd 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs @@ -3,23 +3,28 @@ using System.Diagnostics; using Microsoft.CodeAnalysis; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { [DebuggerDisplay("Name={DisplayString}, Kind={SpecKind}")] - internal abstract record TypeSpec + public abstract record TypeSpec { public TypeSpec(ITypeSymbol type) { + TypeRef = new TypeRef(type); (Namespace, DisplayString, Name) = type.GetTypeName(); - AssemblyQualifiedName = type.ContainingAssembly + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); IdentifierCompatibleSubstring = type.ToIdentifierCompatibleSubstring(); IsValueType = type.IsValueType; + + EffectiveTypeRef = TypeRef; // Overriden by NullableSpec. } - public string Name { get; } + public TypeRef TypeRef { get; } - public string AssemblyQualifiedName { get; } + public TypeRef EffectiveTypeRef { get; protected init; } + + public string Name { get; } public string DisplayString { get; } @@ -28,24 +33,35 @@ public TypeSpec(ITypeSymbol type) public string? Namespace { get; } public bool IsValueType { get; } + } - public abstract TypeSpecKind SpecKind { get; } + public abstract record ComplexTypeSpec : TypeSpec + { + protected ComplexTypeSpec(ITypeSymbol type) : base(type) { } + } - public abstract bool CanBindTo { get; } + internal sealed record NullableSpec : TypeSpec + { + public NullableSpec(ITypeSymbol type, TypeRef underlyingTypeRef) : base(type) => + EffectiveTypeRef = underlyingTypeRef; + } - public abstract bool CanInstantiate { get; } + internal sealed record UnsupportedTypeSpec : TypeSpec + { + public UnsupportedTypeSpec(ITypeSymbol type) : base(type) { } - public abstract TypeSpec EffectiveType { get; } + public required NotSupportedReason NotSupportedReason { get; init; } } - internal enum TypeSpecKind + public enum NotSupportedReason { - Unknown = 0, - ParsableFromString = 1, - Object = 2, - Enumerable = 3, - Dictionary = 4, - IConfigurationSection = 5, - Nullable = 6, + UnknownType = 1, + MissingPublicInstanceConstructor = 2, + CollectionNotSupported = 3, + DictionaryKeyNotSupported = 4, + ElementTypeNotSupported = 5, + MultipleParameterizedConstructors = 6, + MultiDimArraysNotSupported = 7, + NullableUnderlyingTypeNotSupported = 8, } } 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 82edf0c86de34..7e635c1f0bdaa 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -1851,19 +1851,12 @@ public void RecursiveTypeGraphs_DirectRef() var obj = configuration.Get(); Assert.Equal("Hello", obj.MyString); -#if BUILDING_SOURCE_GENERATOR_TESTS - // Record type specs don't work well with recursive type graphs. This includes indirect - // transitive props. Current behavior is to drop all such members from binding. Make TODO to doc - // this as unsupported feature for v1, or to fix it in a follow-up to initial incremental PR. - Assert.Null(obj.MyClass); -#else var nested = obj.MyClass; Assert.Equal("World", nested.MyString); var deeplyNested = nested.MyClass; Assert.Equal("World", deeplyNested.MyString); Assert.Null(deeplyNested.MyClass); -#endif } [Fact] @@ -1887,19 +1880,12 @@ public void RecursiveTypeGraphs_IndirectRef() var obj = configuration.Get(); Assert.Equal("Hello", obj.MyString); -#if BUILDING_SOURCE_GENERATOR_TESTS - // Record type specs don't work well with recursive type graphs. This includes indirect - // transitive props. Current behavior is to drop all such members from binding. Make TODO to doc - // this as unsupported feature for v1, or to fix it in a follow-up to initial incremental PR. - Assert.Null(obj.MyList); -#else var nested = obj.MyList[0]; Assert.Equal("World", nested.MyString); var deeplyNested = nested.MyList[0]; Assert.Equal("World", deeplyNested.MyString); Assert.Null(deeplyNested.MyList); -#endif } [Fact] diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs new file mode 100644 index 0000000000000..f7bb3f36ef5f0 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs @@ -0,0 +1,126 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Extensions.Configuration.Binder.SourceGeneration; +using SourceGenerators.Tests; +using Xunit; + +namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)] + public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase + { + internal sealed class ConfigBindingGenTestDriver + { + private readonly CSharpParseOptions _parseOptions; + private GeneratorDriver _generatorDriver; + private SourceGenerationSpec? _genSpec; + + private readonly LanguageVersion _langVersion; + private readonly IEnumerable? _assemblyReferences; + private Compilation _compilation = null; + + public ConfigBindingGenTestDriver( + LanguageVersion langVersion = LanguageVersion.LatestMajor, + IEnumerable? assemblyReferences = null) + { + _langVersion = langVersion; + + _assemblyReferences = assemblyReferences ?? s_compilationAssemblyRefs; + + _parseOptions = new CSharpParseOptions(langVersion).WithFeatures(new[] { + new KeyValuePair("InterceptorsPreview", "") , + new KeyValuePair("InterceptorsPreviewNamespaces", "Microsoft.Extensions.Configuration.Binder.SourceGeneration") + }); + + ConfigurationBindingGenerator generator = new() { OnSourceEmitting = spec => _genSpec = spec }; + _generatorDriver = CSharpGeneratorDriver.Create( + new ISourceGenerator[] { generator.AsSourceGenerator() }, + parseOptions: _parseOptions, + driverOptions: new GeneratorDriverOptions( + disabledOutputs: IncrementalGeneratorOutputKind.None, + trackIncrementalGeneratorSteps: true)); + } + + public async Task RunGeneratorAndUpdateCompilation(string? source = null) + { + await UpdateCompilationWithSource(source); + Assert.NotNull(_compilation); + + _generatorDriver = _generatorDriver.RunGeneratorsAndUpdateCompilation(_compilation, out Compilation outputCompilation, out _, CancellationToken.None); + GeneratorDriverRunResult runResult = _generatorDriver.GetRunResult(); + //_compilation = outputCompilation; + + return new ConfigBindingGenRunResult + { + OutputCompilation = outputCompilation, + Diagnostics = runResult.Diagnostics, + GeneratedSources = runResult.Results[0].GeneratedSources, + TrackedSteps = runResult.Results[0].TrackedSteps[ConfigurationBindingGenerator.GenSpecTrackingName], + GenerationSpec = _genSpec + }; + } + + private async Task UpdateCompilationWithSource(string? source = null) + { + if (_compilation is not null && source is not null) + { + SyntaxTree newTree = CSharpSyntaxTree.ParseText(source, _parseOptions); + _compilation = _compilation.ReplaceSyntaxTree(_compilation.SyntaxTrees.First(), newTree); + } + else if (_compilation is null) + { + Assert.True(source is not null, "Generator test requires input source."); + using AdhocWorkspace workspace = RoslynTestUtils.CreateTestWorkspace(); + + Project project = RoslynTestUtils.CreateTestProject(workspace, _assemblyReferences, langVersion: _langVersion) + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithNullableContextOptions(NullableContextOptions.Annotations)) + .WithParseOptions(_parseOptions) + .WithDocuments(new string[] { source }); + Assert.True(project.Solution.Workspace.TryApplyChanges(project.Solution)); + + _compilation = (await project.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false))!; + } + } + } + + internal struct ConfigBindingGenRunResult + { + public required Compilation OutputCompilation { get; init; } + + public required ImmutableArray GeneratedSources { get; init; } + + /// + /// Diagnostics produced by the generator alone. Doesn't include any from other build participants. + /// + public required ImmutableArray Diagnostics { get; init; } + + public required ImmutableArray TrackedSteps { get; init; } + + public required SourceGenerationSpec? GenerationSpec { get; init; } + } + } + + internal static class ConfigBindingGenDriverExtensions + { + public static void ValidateIncrementalResult( + this ConfigurationBindingGeneratorTests.ConfigBindingGenRunResult result, + IncrementalStepRunReason inputReason, + IncrementalStepRunReason outputReason) + { + Assert.Collection(result.TrackedSteps, step => + { + Assert.Collection(step.Inputs, source => Assert.Equal(inputReason, source.Source.Outputs[source.OutputIndex].Reason)); + Assert.Collection(step.Outputs, output => Assert.Equal(outputReason, output.Reason)); + }); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs index 35395d29d8f57..5b4492da74eb8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs @@ -654,7 +654,7 @@ public class MyClass2 }" ; - ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source); + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source); Assert.Empty(result.GeneratedSources); Assert.Empty(result.Diagnostics); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs index 2dd980e3ee08e..1177d0bfe3876 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs @@ -110,8 +110,8 @@ private static async Task VerifyAgainstBaselineUsingFile( string[] expectedLines = baseline.Replace("%VERSION%", typeof(ConfigurationBindingGenerator).Assembly.GetName().Version?.ToString()) .Split(Environment.NewLine); - ConfigBindingGenDriver genDriver = new(); - ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(testSourceCode); + ConfigBindingGenTestDriver genDriver = new(); + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(testSourceCode); GeneratedSourceResult generatedSource = Assert.Single(result.GeneratedSources); SourceText resultSourceText = generatedSource.SourceText; @@ -140,14 +140,14 @@ private static async Task VerifyAgainstBaselineUsingFile( Assert.True(resultEqualsBaseline, errorMessage); } - private static async Task RunGeneratorAndUpdateCompilation( + private static async Task RunGeneratorAndUpdateCompilation( string source, LanguageVersion langVersion = LanguageVersion.CSharp12, IEnumerable? assemblyReferences = null, bool validateCompilationDiagnostics = false) { - ConfigBindingGenDriver driver = new ConfigBindingGenDriver(langVersion, assemblyReferences); - ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(source); + ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(langVersion, assemblyReferences); + ConfigBindingGenRunResult result = await driver.RunGeneratorAndUpdateCompilation(source); if (validateCompilationDiagnostics) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs index 5a7e75149716c..bfcd19000fa8f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Configuration.Binder.SourceGeneration; +using SourceGenerators.Tests; using Xunit; namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests @@ -9,116 +12,129 @@ namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)] public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase { - [ActiveIssue("")] - [Fact] - public async Task RunWithNoDiags_Then_NoEdit() + public sealed class IncrementalTests { - ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + [Fact] + public async Task CompilingTheSameSourceResultsInEqualModels() + { + SourceGenerationSpec spec1 = (await new ConfigBindingGenTestDriver().RunGeneratorAndUpdateCompilation(BindCallSampleCode)).GenerationSpec; + SourceGenerationSpec spec2 = (await new ConfigBindingGenTestDriver().RunGeneratorAndUpdateCompilation(BindCallSampleCode)).GenerationSpec; - ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); - result.AssertHasSourceAndNoDiagnostics(); + Assert.NotSame(spec1, spec2); + GeneratorTestHelpers.AssertStructurallyEqual(spec1, spec2); - result = await driver.RunGeneratorAndUpdateCompilation(); - result.AssertEmpty(); - } + Assert.Equal(spec1, spec2); + Assert.Equal(spec1.GetHashCode(), spec2.GetHashCode()); + } - [ActiveIssue("")] - [Fact] - public async Task RunWithNoDiags_Then_NoOpEdit() - { - ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + [Fact] + public async Task RunWithNoDiags_Then_NoEdit() + { + ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(); - ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); - result.AssertHasSourceAndNoDiagnostics(); + ConfigBindingGenRunResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); + result.ValidateIncrementalResult(IncrementalStepRunReason.New, IncrementalStepRunReason.New); - result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_ReorderedInvocations); - result.AssertEmpty(); + result = await driver.RunGeneratorAndUpdateCompilation(); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Unchanged); + } - result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_ReorderedConfigTypeMembers); - result.AssertEmpty(); - } + [Fact] + public async Task RunWithNoDiags_Then_ChangeInputOrder() + { + ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(); - [ActiveIssue("")] - [Fact] - public async Task RunWithNoDiags_Then_EditWithNoDiags() - { - ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + ConfigBindingGenRunResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); + result.ValidateIncrementalResult(IncrementalStepRunReason.New, IncrementalStepRunReason.New); - ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); - result.AssertHasSourceAndNoDiagnostics(); + // We expect different spec because diag locations are different. + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_ReorderedInvocations); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified); - result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithDifferentConfigTypeName); - result.AssertEmpty(); - } + // No diff from previous step because type model is the same. + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_ReorderedConfigTypeMembers); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Unchanged); + } - [ActiveIssue("")] - [Fact] - public async Task RunWithNoDiags_Then_EditWithDiags() - { - ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + [Fact] + public async Task RunWithNoDiags_Then_EditWithNoDiags() + { + ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(); - ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); - result.AssertHasSourceAndNoDiagnostics(); + ConfigBindingGenRunResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); + result.ValidateIncrementalResult(IncrementalStepRunReason.New, IncrementalStepRunReason.New); - result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); - result.AssertEmpty(); - } + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithDifferentConfigTypeName); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified); + } - [ActiveIssue("")] - [Fact] - public async Task RunWithDiags_Then_NoEdit() - { - ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + [Fact] + public async Task RunWithNoDiags_Then_EditWithDiags() + { + ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(); - ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); - result.AssertHasSourceAndDiagnostics(); + ConfigBindingGenRunResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); + result.ValidateIncrementalResult(IncrementalStepRunReason.New, IncrementalStepRunReason.New); - result = await driver.RunGeneratorAndUpdateCompilation(); - result.AssertEmpty(); - } + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified); + } - [ActiveIssue("")] - [Fact] - public async Task RunWithDiags_Then_NoOpEdit() - { - ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + [Fact] + public async Task RunWithDiags_Then_NoEdit() + { + ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(); - ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); - result.AssertHasSourceAndDiagnostics(); + ConfigBindingGenRunResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); + result.ValidateIncrementalResult(IncrementalStepRunReason.New, IncrementalStepRunReason.New); - result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_ReorderedInvocations); - result.AssertEmpty(); + result = await driver.RunGeneratorAndUpdateCompilation(); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Unchanged); + } - result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_ReorderedConfigTypeMembers); - result.AssertEmpty(); - } + [Fact] + public async Task RunWithDiags_Then_NoOpEdit() + { + ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(); - [ActiveIssue("")] - [Fact] - public async Task RunWithDiags_Then_EditWithNoDiags() - { - ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + ConfigBindingGenRunResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); + result.ValidateIncrementalResult(IncrementalStepRunReason.New, IncrementalStepRunReason.New); - ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); - result.AssertHasSourceAndDiagnostics(); + // We expect different spec because diag locations are different. + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_ReorderedInvocations); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified); - result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); - result.AssertEmpty(); - } + // No diff from previous step because type model is the same. + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_ReorderedConfigTypeMembers); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Unchanged); + } - [ActiveIssue("")] - [Fact] - public async Task RunWithDiags_Then_EditWithDiags() - { - ConfigBindingGenDriver driver = new ConfigBindingGenDriver(); + [Fact] + public async Task RunWithDiags_Then_EditWithNoDiags() + { + ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(); - ConfigBindingGenResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); - result.AssertHasSourceAndDiagnostics(); + ConfigBindingGenRunResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); + result.ValidateIncrementalResult(IncrementalStepRunReason.New, IncrementalStepRunReason.New); - result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_WithDiffMemberName); - result.AssertHasSourceAndDiagnostics(); + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCode); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified); + } + + [Fact] + public async Task RunWithDiags_Then_EditWithDiags() + { + ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(); + + ConfigBindingGenRunResult result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember); + result.ValidateIncrementalResult(IncrementalStepRunReason.New, IncrementalStepRunReason.New); + + result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_WithDiffMemberName); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified); + } } + #region Incremental test sources. /// /// Keep in sync with . /// @@ -292,9 +308,9 @@ public static void Main() IConfigurationRoot config = configurationBuilder.Build(); MyClass configObj = new(); - config.Bind(configObj); - config.Bind(configObj, options => { }); config.Bind("key", configObj); + config.Bind(configObj); + config.Bind(configObj, options => { }); } public class MyClass @@ -341,5 +357,6 @@ public class MyClass public class MyClass2 { } } """; + #endregion Incremental test sources. } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs index 8d5bd39161a9a..4fddfd15e82ef 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs @@ -27,7 +27,7 @@ public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTes [InlineData(LanguageVersion.CSharp10)] public async Task LangVersionMustBeCharp12OrHigher(LanguageVersion langVersion) { - ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(BindCallSampleCode, langVersion: langVersion); + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(BindCallSampleCode, langVersion: langVersion); Assert.Empty(result.GeneratedSources); Diagnostic diagnostic = Assert.Single(result.Diagnostics); @@ -75,7 +75,7 @@ public record struct MyRecordStruct { } } """; - ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source); + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source); Assert.Empty(result.GeneratedSources); Assert.Equal(7, result.Diagnostics.Count()); @@ -111,7 +111,7 @@ public record struct MyRecordStruct { } } """; - ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source); + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source); Assert.Empty(result.GeneratedSources); Assert.Equal(2, result.Diagnostics.Count()); @@ -163,7 +163,7 @@ public class MyClass { } } """; - ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source); + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source); Assert.Empty(result.GeneratedSources); Assert.Equal(6, result.Diagnostics.Count()); @@ -218,7 +218,7 @@ public class MyClass0 { } async Task Test(bool expectOutput) { - ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetFilteredAssemblyRefs(exclusions)); + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetFilteredAssemblyRefs(exclusions)); Assert.Empty(result.Diagnostics); Assert.Equal(expectOutput ? 1 : 0, result.GeneratedSources.Length); } @@ -274,7 +274,7 @@ public class AnotherGraphWithUnsupportedMembers } """; - ConfigBindingGenResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ImmutableArray<>), typeof(Encoding), typeof(JsonSerializer))); + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ImmutableArray<>), typeof(Encoding), typeof(JsonSerializer))); Assert.Single(result.GeneratedSources); Assert.True(result.Diagnostics.Any(diag => diag.Id == Diagnostics.TypeNotSupported.Id)); Assert.True(result.Diagnostics.Any(diag => diag.Id == Diagnostics.PropertyNotSupported.Id)); 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 adb25342fbbfe..d202c3968a4bf 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 @@ -2,8 +2,10 @@ $(NetCoreAppCurrent);$(NetFrameworkMinimum) true - - SYSLIB1100,SYSLIB1101 + + $(NoWarn);SYSLIB1100,SYSLIB1101 + + $(NoWarn);SYSLIB1103,SYSLIB1104 $(Features);InterceptorsPreview $(InterceptorsPreviewNamespaces);Microsoft.Extensions.Configuration.Binder.SourceGeneration @@ -22,6 +24,7 @@ + @@ -30,7 +33,7 @@ - + @@ -48,10 +51,7 @@ - + PreserveNewest diff --git a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs index a143883cdd2de..41aa8616f346e 100644 --- a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs @@ -25,8 +25,6 @@ internal static class RoslynExtensions return compilation.GetBestTypeByMetadataName(type.FullName); } - public static string GetFullyQualifiedName(this ITypeSymbol type) => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - public static Location? GetLocation(this ISymbol typeSymbol) => typeSymbol.Locations.Length > 0 ? typeSymbol.Locations[0] : null; @@ -42,61 +40,6 @@ internal static class RoslynExtensions public static bool ContainsLocation(this Compilation compilation, Location location) => location.SourceTree != null && compilation.ContainsSyntaxTree(location.SourceTree); - /// - /// Removes any type metadata that is erased at compile time, such as NRT annotations and tuple labels. - /// - public static ITypeSymbol EraseCompileTimeMetadata(this Compilation compilation, ITypeSymbol type) - { - if (type.NullableAnnotation is NullableAnnotation.Annotated) - { - type = type.WithNullableAnnotation(NullableAnnotation.None); - } - - if (type is INamedTypeSymbol namedType) - { - if (namedType.IsTupleType) - { - if (namedType.TupleElements.Length < 2) - { - return type; - } - - ImmutableArray erasedElements = namedType.TupleElements - .Select(e => compilation.EraseCompileTimeMetadata(e.Type)) - .ToImmutableArray(); - - type = compilation.CreateTupleTypeSymbol(erasedElements); - } - else if (namedType.IsGenericType) - { - if (namedType.IsUnboundGenericType) - { - return namedType; - } - - ImmutableArray typeArguments = namedType.TypeArguments; - INamedTypeSymbol? containingType = namedType.ContainingType; - - if (containingType?.IsGenericType == true) - { - containingType = (INamedTypeSymbol)compilation.EraseCompileTimeMetadata(containingType); - type = namedType = containingType.GetTypeMembers().First(t => t.Name == namedType.Name && t.Arity == namedType.Arity); - } - - if (typeArguments.Length > 0) - { - ITypeSymbol[] erasedTypeArgs = typeArguments - .Select(compilation.EraseCompileTimeMetadata) - .ToArray(); - - type = namedType.ConstructedFrom.Construct(erasedTypeArgs); - } - } - } - - return type; - } - public static bool CanUseDefaultConstructorForDeserialization(this ITypeSymbol type, out IMethodSymbol? constructorInfo) { if (type.IsAbstract || type.TypeKind is TypeKind.Interface || type is not INamedTypeSymbol namedType) diff --git a/src/libraries/System.Text.Json/gen/Model/ContextGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/ContextGenerationSpec.cs index 1e2ee2d737e00..00c7192c3ae58 100644 --- a/src/libraries/System.Text.Json/gen/Model/ContextGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/ContextGenerationSpec.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Text.Json.Serialization; +using SourceGenerators; namespace System.Text.Json.SourceGeneration { diff --git a/src/libraries/System.Text.Json/gen/Model/ParameterGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/ParameterGenerationSpec.cs index 2945b20b730b1..68e32d0153156 100644 --- a/src/libraries/System.Text.Json/gen/Model/ParameterGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/ParameterGenerationSpec.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 SourceGenerators; + namespace System.Text.Json.SourceGeneration { /// diff --git a/src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs index 56b42970f6889..214c32b4d19e2 100644 --- a/src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Text.Json.Serialization; +using SourceGenerators; namespace System.Text.Json.SourceGeneration { diff --git a/src/libraries/System.Text.Json/gen/Model/PropertyInitializerGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/PropertyInitializerGenerationSpec.cs index 9fc68a1192847..608ce8e887d72 100644 --- a/src/libraries/System.Text.Json/gen/Model/PropertyInitializerGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/PropertyInitializerGenerationSpec.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 SourceGenerators; + namespace System.Text.Json.SourceGeneration { /// diff --git a/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs b/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs index 7e94f824bae8c..83b587fb962f7 100644 --- a/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json.Serialization; +using SourceGenerators; namespace System.Text.Json.SourceGeneration { diff --git a/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs index 189295bcb971c..9b71bf16438b8 100644 --- a/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Text.Json.Serialization; using Microsoft.CodeAnalysis; +using SourceGenerators; namespace System.Text.Json.SourceGeneration { diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets index 4020a05cb421d..87c060388ad19 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets @@ -32,6 +32,7 @@ + @@ -74,6 +75,5 @@ - diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorIncrementalTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorIncrementalTests.cs index 7a38a7e5fb512..daa6498cbc9b2 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorIncrementalTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorIncrementalTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; +using SourceGenerators.Tests; using Xunit; namespace System.Text.Json.SourceGeneration.UnitTests @@ -29,7 +30,7 @@ public static void CompilingTheSameSourceResultsInEqualModels(Func ContextGenerationSpec ctx2 = result2.ContextGenerationSpecs[i]; Assert.NotSame(ctx1, ctx2); - AssertStructurallyEqual(ctx1, ctx2); + GeneratorTestHelpers.AssertStructurallyEqual(ctx1, ctx2); Assert.Equal(ctx1, ctx2); Assert.Equal(ctx1.GetHashCode(), ctx2.GetHashCode()); @@ -86,7 +87,7 @@ public partial class JsonContext : JsonSerializerContext { } ContextGenerationSpec ctx2 = result2.ContextGenerationSpecs[0]; Assert.NotSame(ctx1, ctx2); - AssertStructurallyEqual(ctx1, ctx2); + GeneratorTestHelpers.AssertStructurallyEqual(ctx1, ctx2); Assert.Equal(ctx1, ctx2); Assert.Equal(ctx1.GetHashCode(), ctx2.GetHashCode()); @@ -377,74 +378,5 @@ public static IEnumerable GetCompilationHelperFactories() .Where(m => m.ReturnType == typeof(Compilation) && m.GetParameters().Length == 0) .Select(m => new object[] { Delegate.CreateDelegate(typeof(Func), m) }); } - - /// - /// Asserts for structural equality, returning a path to the mismatching data when not equal. - /// - private static void AssertStructurallyEqual(T expected, T actual) - { - CheckAreEqualCore(expected, actual, new()); - static void CheckAreEqualCore(object expected, object actual, Stack path) - { - if (expected is null || actual is null) - { - if (expected is not null || actual is not null) - { - FailNotEqual(); - } - - return; - } - - Type type = expected.GetType(); - if (type != actual.GetType()) - { - FailNotEqual(); - return; - } - - if (expected is IEnumerable leftCollection) - { - if (actual is not IEnumerable rightCollection) - { - FailNotEqual(); - return; - } - - object?[] expectedValues = leftCollection.Cast().ToArray(); - object?[] actualValues = rightCollection.Cast().ToArray(); - - for (int i = 0; i < Math.Max(expectedValues.Length, actualValues.Length); i++) - { - object? expectedElement = i < expectedValues.Length ? expectedValues[i] : ""; - object? actualElement = i < actualValues.Length ? actualValues[i] : ""; - - path.Push($"[{i}]"); - CheckAreEqualCore(expectedElement, actualElement, path); - path.Pop(); - } - } - - if (type.GetProperty("EqualityContract", BindingFlags.Instance | BindingFlags.NonPublic, null, returnType: typeof(Type), types: Array.Empty(), null) != null) - { - // Type is a C# record, run pointwise equality comparison. - foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - path.Push("." + property.Name); - CheckAreEqualCore(property.GetValue(expected), property.GetValue(actual), path); - path.Pop(); - } - - return; - } - - if (!expected.Equals(actual)) - { - FailNotEqual(); - } - - void FailNotEqual() => Assert.Fail($"Value not equal in ${string.Join("", path.Reverse())}: expected {expected}, but was {actual}."); - } - } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/System.Text.Json.SourceGeneration.Unit.Tests.targets b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/System.Text.Json.SourceGeneration.Unit.Tests.targets index a700b2a9f3a38..56bf105dc1fdd 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/System.Text.Json.SourceGeneration.Unit.Tests.targets +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/System.Text.Json.SourceGeneration.Unit.Tests.targets @@ -12,6 +12,7 @@ + From 80c668f76fad3a101376598397f676582bcef54b Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 22 Sep 2023 13:34:45 -0700 Subject: [PATCH 05/11] Address failing tests --- ...rosoft.Extensions.Configuration.Binder.sln | 66 ++++++----- .../ConfigurationBindingGenerator.Parser.cs | 2 +- .../gen/Emitter/CoreBindingHelpers.cs | 4 +- ...nfiguration.Binder.SourceGeneration.csproj | 5 + .../gen/Parser/ConfigurationBinder.cs | 6 +- .../OptionsBuilderConfigurationExtensions.cs | 6 +- ...onfigurationServiceCollectionExtensions.cs | 6 +- .../gen/Specs/BindingHelperInfo.cs | 12 +- .../gen/Specs/TypeIndex.cs | 40 +++---- .../Bind_Instance.generated.txt | 71 ++++++------ .../Bind_Instance_BinderOptions.generated.txt | 71 ++++++------ ...ind_ParseTypeFromMethodParam.generated.txt | 1 - .../GetValue_T_Key_DefaultValue.generated.txt | 4 +- .../ConfigurationBinder/Get_T.generated.txt | 96 +++++++++------- .../Get_T_BinderOptions.generated.txt | 96 +++++++++------- .../BindConfiguration.generated.txt | 26 ++--- .../Bind_T_BinderOptions.generated.txt | 26 ++--- .../ConfigBindingGenDriver.cs | 108 ------------------ .../ConfigBindingGenTestDriver.cs | 2 +- .../GeneratorTests.Helpers.cs | 8 +- 20 files changed, 282 insertions(+), 374 deletions(-) delete mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenDriver.cs diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/Microsoft.Extensions.Configuration.Binder.sln b/src/libraries/Microsoft.Extensions.Configuration.Binder/Microsoft.Extensions.Configuration.Binder.sln index ac38e3aec99f6..d4eb7fe34621a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/Microsoft.Extensions.Configuration.Binder.sln +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/Microsoft.Extensions.Configuration.Binder.sln @@ -1,4 +1,8 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34004.107 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{5FB89358-3575-45AA-ACFE-EF3598B9AB7E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.AsyncInterfaces", "..\Microsoft.Bcl.AsyncInterfaces\ref\Microsoft.Bcl.AsyncInterfaces.csproj", "{64CB04AC-E5B9-4FB8-A1AF-636A6DF23829}" @@ -111,11 +115,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{94EEF122-C30 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{CC3961B0-C62D-44B9-91DB-11D94A3F91A5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "tools\gen", "{04FEFFC9-5F1D-47CA-885C-3D7F9643D63D}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{04FEFFC9-5F1D-47CA-885C-3D7F9643D63D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "tools\src", "{948EC8B5-A259-4D8C-B911-EDE19F5BAE40}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{948EC8B5-A259-4D8C-B911-EDE19F5BAE40}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{8F2204E7-8124-4F7C-B663-8731C7959001}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{8F2204E7-8124-4F7C-B663-8731C7959001}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{B343C975-AC74-4843-899C-623B6E7A6321}" EndProject @@ -339,62 +343,66 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {5FB89358-3575-45AA-ACFE-EF3598B9AB7E} = {AAE738F3-89AC-4406-B1D9-A61A2C3A1CF0} - {56F4D38E-41A0-45E2-9F04-9E670D002E71} = {AAE738F3-89AC-4406-B1D9-A61A2C3A1CF0} - {75BA0154-A24D-421E-9046-C7949DF12A55} = {AAE738F3-89AC-4406-B1D9-A61A2C3A1CF0} {64CB04AC-E5B9-4FB8-A1AF-636A6DF23829} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {3E443131-1C04-48F8-9635-99ABD9DA0E6A} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {CB09105A-F475-4A91-8836-434FA175F4F9} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {958E5946-FD21-4167-B471-8CEC2CB8D75D} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {D4B3EEA1-7394-49EA-A088-897C0CD26D11} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {F05212B2-FD66-4E8E-AEA0-FAC06A0D6808} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {8C7443D8-864A-4DAE-8835-108580C289F5} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {56F4D38E-41A0-45E2-9F04-9E670D002E71} = {AAE738F3-89AC-4406-B1D9-A61A2C3A1CF0} + {75BA0154-A24D-421E-9046-C7949DF12A55} = {AAE738F3-89AC-4406-B1D9-A61A2C3A1CF0} {CB5C87BF-7E0D-4BAC-86BB-06FFC7D1CEE1} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {C5073E23-D3CD-43D3-9AE3-87E570B1B930} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {A519F26A-3187-40C3-BBDC-FD22EE1FC47B} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {45DB88E7-487F-49F2-A309-64315FB218B8} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {39D99379-E744-4295-9CA8-B5C6DE286EA0} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {5177A566-05AF-4DF0-93CC-D2876F7E6EBB} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {CB1C57BA-64C0-4626-A156-4E89A1995C31} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {56C28B9C-C3FA-4A22-A304-1549F681CE7F} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {A9D0ED56-4B34-4010-A5C3-4E36EE8E9FDF} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {7F71CC3D-D4DA-42C4-8470-72C22806FD89} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {42DAD2C1-0FD4-442E-B218-9CDACE6EEB01} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {091844B8-A79E-4DF4-9A73-B2F01968E982} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {A86A8B8E-CC45-46F4-BB95-29410AA7B6C2} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {7F2A8B79-8E07-4D70-A7A3-144E71647110} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {98760FB8-A548-4BEE-914B-4D5D0D13FA09} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {6C8B4068-73D3-47A0-B9A7-840475175FC6} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {FB70235B-6F82-4495-9011-183E65C78CCF} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {856C015E-982A-4834-88F7-3F8FFD33981C} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {F132C649-6660-4583-8AD1-36D38711A994} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {2D5B1B6B-1557-4A14-BC00-C36A21BC97C4} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {78D796C3-227B-4648-9BCB-2A000852B2F7} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {3C288A6A-17C9-40A9-A1AA-E0FC3927DFE3} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {9B21B87F-084B-411B-A513-C22B5B961BF3} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {9EB52984-66B5-40C4-8BE7-D75CA612948F} = {F43534CA-C419-405E-B239-CDE2BDC703BE} {4A94F874-A405-4B2E-912B-81887987103B} = {F43534CA-C419-405E-B239-CDE2BDC703BE} {88A22638-3937-4AD2-965B-0A69B616F41F} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {ACD597F0-0A1A-476A-8AE5-2FB6A0039ACD} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {71F7FC59-D0B0-459C-9AFB-0A41B7B73E4C} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {DA0EC3F6-1D31-41DA-B2A5-CEC8D32E1023} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {3E443131-1C04-48F8-9635-99ABD9DA0E6A} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {958E5946-FD21-4167-B471-8CEC2CB8D75D} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {8C7443D8-864A-4DAE-8835-108580C289F5} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {C5073E23-D3CD-43D3-9AE3-87E570B1B930} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {45DB88E7-487F-49F2-A309-64315FB218B8} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {5177A566-05AF-4DF0-93CC-D2876F7E6EBB} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {56C28B9C-C3FA-4A22-A304-1549F681CE7F} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {7F71CC3D-D4DA-42C4-8470-72C22806FD89} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {091844B8-A79E-4DF4-9A73-B2F01968E982} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {7F2A8B79-8E07-4D70-A7A3-144E71647110} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {6C8B4068-73D3-47A0-B9A7-840475175FC6} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {856C015E-982A-4834-88F7-3F8FFD33981C} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {78D796C3-227B-4648-9BCB-2A000852B2F7} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {9B21B87F-084B-411B-A513-C22B5B961BF3} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {5DF5B4A7-D78A-400E-A49E-54F33CA51006} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {94C9AA55-C6AD-41CC-B6C8-9186C27A6FFE} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {D4B3EEA1-7394-49EA-A088-897C0CD26D11} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} - {F132C649-6660-4583-8AD1-36D38711A994} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {9E2C43CF-2E01-4570-8C02-F678607DDAFF} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {05B7F752-4991-4DC8-9B06-8269211E7817} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {6E58AF2F-AB35-4279-9135-67E97BCE1432} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} + {ACD597F0-0A1A-476A-8AE5-2FB6A0039ACD} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {71F7FC59-D0B0-459C-9AFB-0A41B7B73E4C} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {5DF5B4A7-D78A-400E-A49E-54F33CA51006} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {D2FE6BEA-70F5-4AE3-8D29-DB6568A9DE49} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {47E22F0C-5F36-450B-8020-B83B36936597} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {4A2E7449-5480-4872-91B3-B5A479D6A787} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} + {DA0EC3F6-1D31-41DA-B2A5-CEC8D32E1023} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {94C9AA55-C6AD-41CC-B6C8-9186C27A6FFE} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {AA7E297B-0BD9-4E6B-96FE-BC69FC65DB64} = {04FEFFC9-5F1D-47CA-885C-3D7F9643D63D} {D187AB48-A05A-4C8C-900D-7F2A3D87769A} = {04FEFFC9-5F1D-47CA-885C-3D7F9643D63D} - {04FEFFC9-5F1D-47CA-885C-3D7F9643D63D} = {B343C975-AC74-4843-899C-623B6E7A6321} {44059045-A065-4123-AF8C-107D06782422} = {948EC8B5-A259-4D8C-B911-EDE19F5BAE40} {EDE541FC-0F9D-4D3E-A513-7E74788B8C0B} = {948EC8B5-A259-4D8C-B911-EDE19F5BAE40} - {948EC8B5-A259-4D8C-B911-EDE19F5BAE40} = {B343C975-AC74-4843-899C-623B6E7A6321} {10106702-31E6-4D1A-BDE0-BB8F9F6C258D} = {8F2204E7-8124-4F7C-B663-8731C7959001} + {04FEFFC9-5F1D-47CA-885C-3D7F9643D63D} = {B343C975-AC74-4843-899C-623B6E7A6321} + {948EC8B5-A259-4D8C-B911-EDE19F5BAE40} = {B343C975-AC74-4843-899C-623B6E7A6321} {8F2204E7-8124-4F7C-B663-8731C7959001} = {B343C975-AC74-4843-899C-623B6E7A6321} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A97DC4BF-32F0-46E8-B91C-84D1E7F2A27E} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{d187ab48-a05a-4c8c-900d-7f2a3d87769a}*SharedItemsImports = 5 + ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{ede541fc-0f9d-4d3e-a513-7e74788b8c0b}*SharedItemsImports = 5 + EndGlobalSection EndGlobal 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 fe0dbdc7989af..a89752562e3e8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -50,7 +50,7 @@ internal sealed partial class Parser(CompilationData compilationData) return new SourceGenerationSpec { InterceptorInfo = _interceptorInfoBuilder.ToIncrementalValue(), - BindingHelperInfo = _helperInfoBuilder.ToIncrementalValue(), + BindingHelperInfo = _helperInfoBuilder!.ToIncrementalValue(), ConfigTypes = _createdTypeSpecs.Values.OrderBy(s => s.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(), }; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs index 768756545c9e3..cd630e8eeb015 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -103,6 +103,8 @@ private void EmitGetCoreMethod() foreach (TypeSpec type in targetTypes) { TypeSpec effectiveType = _typeIndex.GetEffectiveTypeSpec(type); + Debug.Assert(_typeIndex.CanBindTo(effectiveType.TypeRef)); + string conditionKindExpr = GetConditionKindExpr(ref isFirstType); EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))"); @@ -771,7 +773,7 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) break; case ComplexTypeSpec complexElementType: { - Debug.Assert(_typeIndex.CanBindTo(complexElementType)); + Debug.Assert(_typeIndex.CanBindTo(complexElementType.TypeRef)); if (keyType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) { 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 6b1940639665b..7988fb31cdb6a 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 @@ -9,6 +9,11 @@ $(DefineConstants);LAUNCH_DEBUGGER + + + $(NetCoreAppToolCurrent);netstandard2.0 + + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs index 649d9db34e772..447dff239674a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs @@ -262,7 +262,7 @@ private void RegisterInterceptor_ConfigurationBinder(TypeParseInfo typeParseInfo if (typeSpec is ComplexTypeSpec complexTypeSpec) { _interceptorInfoBuilder.RegisterInterceptor_ConfigBinder_Bind(overload, complexTypeSpec, invocationOperation); - _helperInfoBuilder.RegisterComplexTypeForMethodGen(complexTypeSpec); + _helperInfoBuilder!.RegisterComplexTypeForMethodGen(complexTypeSpec); } } else @@ -274,11 +274,11 @@ private void RegisterInterceptor_ConfigurationBinder(TypeParseInfo typeParseInfo if ((MethodsToGen.ConfigBinder_Get & overload) is not 0) { - _helperInfoBuilder.RegisterTypeForGetCoreGen(typeSpec); + _helperInfoBuilder!.RegisterTypeForGetCoreGen(typeSpec); } else { - _helperInfoBuilder.RegisterTypeForGetValueCoreGen(typeSpec); + _helperInfoBuilder!.RegisterTypeForGetValueCoreGen(typeSpec); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs index 7b02999b0b7ba..9a6b4ce5a6f9a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs @@ -99,16 +99,16 @@ private void RegisterInterceptor_OptionsBuilderExt(TypeParseInfo typeParseInfo, } else { - _helperInfoBuilder.TryRegisterTypeForBindCoreMainGen(complexTypeSpec); + _helperInfoBuilder!.TryRegisterTypeForBindCoreMainGen(complexTypeSpec); } _interceptorInfoBuilder.RegisterInterceptor(typeParseInfo.BindingOverload, typeParseInfo.BinderInvocation.Operation); // Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource. - _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.Options"); + _helperInfoBuilder!.Namespaces.Add("Microsoft.Extensions.Options"); // Emitting refs to OptionsBuilder. - _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); + _helperInfoBuilder!.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs index e21e80b894baa..502a15453a2b6 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs @@ -90,12 +90,12 @@ private void RegisterInterceptor_ServiceCollectionExt(TypeParseInfo typeParseInf private void RegisterTypeForOverloadGen_ServiceCollectionExt(MethodsToGen overload, ComplexTypeSpec typeSpec) { Debug.Assert((MethodsToGen.ServiceCollectionExt_Any & overload) is not 0); - _helperInfoBuilder.TryRegisterTypeForBindCoreMainGen(typeSpec); + _helperInfoBuilder!.TryRegisterTypeForBindCoreMainGen(typeSpec); _interceptorInfoBuilder.MethodsToGen |= overload; - _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); + _helperInfoBuilder!.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); // Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource, IConfigureOptions<>, ConfigureNamedOptions<>. - _helperInfoBuilder.Namespaces.Add("Microsoft.Extensions.Options"); + _helperInfoBuilder!.Namespaces.Add("Microsoft.Extensions.Options"); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs index 21cb6b146d89e..7fbb2a5f94da5 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs @@ -84,16 +84,14 @@ public void RegisterTypeForBindCoreGen(ComplexTypeSpec type) public void RegisterTypeForGetCoreGen(TypeSpec type) { - ComplexTypeSpec? complexType = type as ComplexTypeSpec; - - if (complexType is null || typeIndex.CanInstantiate(complexType)) + if (typeIndex.CanBindTo(type.TypeRef)) { RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, type); - } - if (complexType is not null) - { - RegisterComplexTypeForMethodGen(complexType); + if (type is ComplexTypeSpec complexType) + { + RegisterComplexTypeForMethodGen(complexType); + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs index 5d1d8ade4a49b..c4b3c4fb8b70c 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs @@ -9,40 +9,33 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { - internal sealed class TypeIndex + internal sealed class TypeIndex(IEnumerable typeSpecs) { - private readonly Dictionary _index; + private readonly Dictionary _index = typeSpecs.ToDictionary(spec => spec.TypeRef); - public TypeIndex(IEnumerable typeSpecs) => _index = typeSpecs.ToDictionary(spec => spec.TypeRef); - - public bool CanBindTo(TypeRef typeRef) => CanBindTo(GetEffectiveTypeSpec(typeRef)); - - public bool CanBindTo(TypeSpec typeSpec) => typeSpec switch + public bool CanBindTo(TypeRef typeRef) => GetEffectiveTypeSpec(typeRef) switch { SimpleTypeSpec => true, ComplexTypeSpec complexTypeSpec => CanInstantiate(complexTypeSpec) || HasBindableMembers(complexTypeSpec), _ => throw new InvalidOperationException(), }; - public bool CanInstantiate(ComplexTypeSpec typeSpec) + public bool CanInstantiate(ComplexTypeSpec typeSpec) => typeSpec switch { - return CanInstantiate(typeSpec.TypeRef); - - bool CanInstantiate(TypeRef typeRef) => GetEffectiveTypeSpec(typeRef) switch - { - ObjectSpec objectType => objectType is { InstantiationStrategy: not ObjectInstantiationStrategy.None, InitExceptionMessage: null }, - DictionarySpec dictionaryType => GetTypeSpec(dictionaryType.TypeRef) is ParsableFromStringSpec, - CollectionSpec => true, - _ => throw new InvalidOperationException(), - }; - } + ObjectSpec objectType => objectType is { InstantiationStrategy: not ObjectInstantiationStrategy.None, InitExceptionMessage: null }, + DictionarySpec dictionaryType => + // Nullable not allowed; that would cause us to emit code that violates dictionary key notnull generic parameter constraint. + GetTypeSpec(dictionaryType.KeyTypeRef) is ParsableFromStringSpec, + CollectionSpec => true, + _ => throw new InvalidOperationException(), + }; public bool HasBindableMembers(ComplexTypeSpec typeSpec) => typeSpec switch { ObjectSpec objectSpec => objectSpec.Properties?.Any(p => p.ShouldBindTo) is true, - ArraySpec or EnumerableSpec => CanBindTo(((CollectionSpec)typeSpec).ElementTypeRef), DictionarySpec dictSpec => GetTypeSpec(dictSpec.KeyTypeRef) is ParsableFromStringSpec && CanBindTo(dictSpec.ElementTypeRef), + CollectionSpec collectionSpec => CanBindTo(collectionSpec.ElementTypeRef), _ => throw new InvalidOperationException(), }; @@ -66,12 +59,9 @@ public string GetInstantiationTypeDisplayString(CollectionWithCtorInitSpec type) CollectionInstantiationConcreteType concreteType = type.InstantiationConcreteType; Debug.Assert(concreteType is not CollectionInstantiationConcreteType.None); - if (concreteType is CollectionInstantiationConcreteType.Self) - { - return type.DisplayString; - } - - return GetGenericTypeDisplayString(type, concreteType); + return concreteType is CollectionInstantiationConcreteType.Self + ? type.DisplayString + : GetGenericTypeDisplayString(type, concreteType); } public string GetPopulationCastTypeDisplayString(CollectionWithCtorInitSpec type) 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 ba7b563ba83b2..acf8152bd4ff6 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 @@ -52,40 +52,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) - { - element = new Program.MyClass2(); - } - instance[section.Key] = element; - } - } - - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = value; - } - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); @@ -108,7 +74,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["MyInt"] is string value6) { - instance.MyInt = ParseInt(value6, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value6, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -129,6 +95,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -161,7 +160,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 62564974fe31d..b65e79c6bc99b 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 @@ -52,40 +52,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) - { - element = new Program.MyClass2(); - } - instance[section.Key] = element; - } - } - - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = value; - } - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); @@ -108,7 +74,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["MyInt"] is string value6) { - instance.MyInt = ParseInt(value6, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value6, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -129,6 +95,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -179,7 +178,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt index 14753a4a1f8e4..55998b03bdef0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt @@ -22,7 +22,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using Microsoft.Extensions.Configuration; using System; using System.CodeDom.Compiler; - using System.Collections.Generic; using System.Globalization; using System.Runtime.CompilerServices; 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 697f710dff302..32c192c057089 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 @@ -51,13 +51,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (type == typeof(int)) { - return ParseInt(value, () => section.Path); + return ParseInt32(value, () => section.Path); } return null; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 d7fe71cdfff9c..142ff6f4ebe7a 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 @@ -62,13 +62,47 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } - public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - var temp1 = new List(); - BindCore(configuration, ref temp1, defaultValueIfNotFound: false, binderOptions); - int originalCount = instance.Length; - Array.Resize(ref instance, originalCount + temp1.Count); - temp1.CopyTo(instance, originalCount); + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section1) + { + int[]? temp3 = instance.MyArray; + temp3 ??= new int[0]; + BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp3; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section4) + { + Dictionary? temp6 = instance.MyDictionary; + temp6 ??= new Dictionary(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp6; + } + + if (configuration["MyInt"] is string value7) + { + instance.MyInt = ParseInt32(value7, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section8) + { + List? temp10 = instance.MyList; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp10; + } + + if (configuration["MyString"] is string value11) + { + instance.MyString = value11; + } } public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) @@ -88,52 +122,26 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (section.Value is string value) { - instance.Add(ParseInt(value, () => section.Path)); + instance.Add(ParseInt32(value, () => section.Path)); } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section3) - { - int[]? temp5 = instance.MyArray; - temp5 ??= new int[0]; - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp5; - } + var temp = new List(); - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section6) - { - Dictionary? temp8 = instance.MyDictionary; - temp8 ??= new Dictionary(); - BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp8; - } - - if (configuration["MyInt"] is string value9) - { - instance.MyInt = ParseInt(value9, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section10) + foreach (IConfigurationSection section in configuration.GetChildren()) { - List? temp12 = instance.MyList; - temp12 ??= new List(); - BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp12; + if (section.Value is string value) + { + temp.Add(ParseInt32(value, () => section.Path)); + } } - if (configuration["MyString"] is string value13) - { - instance.MyString = value13; - } + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp.Count); + temp.CopyTo(instance, originalCount); } @@ -195,7 +203,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 0cfc4f33e8acc..7840028ca3061 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 @@ -62,13 +62,47 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } - public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - var temp1 = new List(); - BindCore(configuration, ref temp1, defaultValueIfNotFound: false, binderOptions); - int originalCount = instance.Length; - Array.Resize(ref instance, originalCount + temp1.Count); - temp1.CopyTo(instance, originalCount); + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section1) + { + int[]? temp3 = instance.MyArray; + temp3 ??= new int[0]; + BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp3; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section4) + { + Dictionary? temp6 = instance.MyDictionary; + temp6 ??= new Dictionary(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp6; + } + + if (configuration["MyInt"] is string value7) + { + instance.MyInt = ParseInt32(value7, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section8) + { + List? temp10 = instance.MyList; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp10; + } + + if (configuration["MyString"] is string value11) + { + instance.MyString = value11; + } } public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) @@ -88,52 +122,26 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { if (section.Value is string value) { - instance.Add(ParseInt(value, () => section.Path)); + instance.Add(ParseInt32(value, () => section.Path)); } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section3) - { - int[]? temp5 = instance.MyArray; - temp5 ??= new int[0]; - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp5; - } + var temp = new List(); - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section6) - { - Dictionary? temp8 = instance.MyDictionary; - temp8 ??= new Dictionary(); - BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp8; - } - - if (configuration["MyInt"] is string value9) - { - instance.MyInt = ParseInt(value9, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section10) + foreach (IConfigurationSection section in configuration.GetChildren()) { - List? temp12 = instance.MyList; - temp12 ??= new List(); - BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp12; + if (section.Value is string value) + { + temp.Add(ParseInt32(value, () => section.Path)); + } } - if (configuration["MyString"] is string value13) - { - instance.MyString = value13; - } + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp.Count); + temp.CopyTo(instance, originalCount); } @@ -195,7 +203,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 3ef9949f0e453..1d84c37f84f39 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 @@ -89,24 +89,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -127,6 +116,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -186,7 +186,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 45e006258c04f..dc8932679e3a0 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 @@ -93,24 +93,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -131,6 +120,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -190,7 +190,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenDriver.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenDriver.cs deleted file mode 100644 index 404b38e89b07a..0000000000000 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenDriver.cs +++ /dev/null @@ -1,108 +0,0 @@ -// 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.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.Extensions.Configuration.Binder.SourceGeneration; -using SourceGenerators.Tests; -using Xunit; - -namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests -{ - [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)] - public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase - { - internal sealed class ConfigBindingGenDriver : IDisposable - { - private readonly CSharpParseOptions _parseOptions; - private readonly CSharpGeneratorDriver _csharpGenerationDriver; - - private readonly AdhocWorkspace _workspace; - private Project _project; - - public ConfigBindingGenDriver( - LanguageVersion langVersion = LanguageVersion.LatestMajor, - IEnumerable? assemblyReferences = null) - { - assemblyReferences ??= s_compilationAssemblyRefs; - - _parseOptions = new CSharpParseOptions(langVersion).WithFeatures(new[] { - new KeyValuePair("InterceptorsPreview", "") , - new KeyValuePair("InterceptorsPreviewNamespaces", "Microsoft.Extensions.Configuration.Binder.SourceGeneration") - }); - - _workspace = RoslynTestUtils.CreateTestWorkspace(); - - _project = RoslynTestUtils.CreateTestProject(_workspace, assemblyReferences, langVersion: langVersion) - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithNullableContextOptions(NullableContextOptions.Annotations)) - .WithParseOptions(_parseOptions); - - - _csharpGenerationDriver = CSharpGeneratorDriver.Create(new[] { new ConfigurationBindingGenerator().AsSourceGenerator() }, parseOptions: _parseOptions); - } - - public async Task RunGeneratorAndUpdateCompilation(params string[]? sources) - { - if (sources is not null) - { - _project = _project.WithDocuments(sources); - Assert.True(_project.Solution.Workspace.TryApplyChanges(_project.Solution)); - } - - Compilation compilation = (await _project.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false))!; - GeneratorDriver generatorDriver = _csharpGenerationDriver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out _, CancellationToken.None); - GeneratorDriverRunResult runDriverRunResult = generatorDriver.GetRunResult(); - - return new ConfigBindingGenResult - { - OutputCompilation = outputCompilation, - Diagnostics = runDriverRunResult.Diagnostics, - GeneratedSources = runDriverRunResult.Results[0].GeneratedSources, - }; - } - - public void Dispose() => _workspace.Dispose(); - } - - internal struct ConfigBindingGenResult - { - public required Compilation OutputCompilation { get; init; } - - public required ImmutableArray GeneratedSources { get; init; } - - /// - /// Diagnostics produced by the generator alone. Doesn't include any from other build participants. - /// - public required ImmutableArray Diagnostics { get; init; } - } - } - - internal static class ConfigBindinGenDriverExtensions - { - public static void AssertHasSourceAndNoDiagnostics(this ConfigurationBindingGeneratorTests.ConfigBindingGenResult result) - { - Assert.Single(result.GeneratedSources); - Assert.NotEmpty(result.Diagnostics); - } - - public static void AssertHasSourceAndDiagnostics(this ConfigurationBindingGeneratorTests.ConfigBindingGenResult result) - { - Assert.Single(result.GeneratedSources); - Assert.NotEmpty(result.Diagnostics); - } - - public static void AssertEmpty(this ConfigurationBindingGeneratorTests.ConfigBindingGenResult result) - { - Assert.Empty(result.GeneratedSources); - Assert.Empty(result.Diagnostics); - } - } -} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs index f7bb3f36ef5f0..4dec96ac51546 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs @@ -109,7 +109,7 @@ internal struct ConfigBindingGenRunResult } } - internal static class ConfigBindingGenDriverExtensions + internal static class ConfigBindingGenTestDriverExtensions { public static void ValidateIncrementalResult( this ConfigurationBindingGeneratorTests.ConfigBindingGenRunResult result, diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs index 1177d0bfe3876..1b54b996c4b00 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs @@ -90,10 +90,10 @@ private enum ExtensionClassType private static async Task VerifyThatSourceIsGenerated(string testSourceCode) { - var (d, r) = await RunGenerator(testSourceCode); - Assert.Equal(1, r.Length); - Assert.Empty(d); - Assert.True(r[0].SourceText.Lines.Count > 10); + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(testSourceCode); + Assert.Equal(1, result.GeneratedSources.Length); + Assert.Empty(result.Diagnostics); + Assert.True(result.GeneratedSources[0].SourceText.Lines.Count > 10); } private static async Task VerifyAgainstBaselineUsingFile( From 321d65a50e35bb5c82ce896662e8daba467aa1f4 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 22 Sep 2023 15:02:05 -0700 Subject: [PATCH 06/11] Make tests pass --- .../gen/Emitter/CoreBindingHelpers.cs | 33 +++-- .../gen/Parser/ConfigurationBinder.cs | 16 ++- .../OptionsBuilderConfigurationExtensions.cs | 13 +- ...onfigurationServiceCollectionExtensions.cs | 17 ++- .../gen/Specs/BindingHelperInfo.cs | 91 ++++++++------ .../gen/Specs/Members/PropertySpec.cs | 2 - .../gen/Specs/TypeIndex.cs | 17 ++- .../gen/Specs/Types/CollectionSpec.cs | 10 +- .../gen/Specs/Types/TypeSpec.cs | 3 +- .../ConfigurationBinder/Bind.generated.txt | 71 ++++++----- .../Bind_Key_Instance.generated.txt | 71 ++++++----- .../ConfigurationBinder/Get.generated.txt | 114 ++++++++++-------- .../GetValue.generated.txt | 24 ++-- .../GetValue_T_Key.generated.txt | 4 +- .../GetValue_TypeOf_Key.generated.txt | 4 +- .../Get_TypeOf.generated.txt | 4 +- .../Get_TypeOf_BinderOptions.generated.txt | 4 +- .../Baselines/EmptyConfigType.generated.txt | 15 +-- .../OptionsBuilder/Bind_T.generated.txt | 26 ++-- .../Baselines/Primitives.generated.txt | 78 ++++++------ .../Configure_T.generated.txt | 108 ++++++++--------- .../Configure_T_BinderOptions.generated.txt | 108 ++++++++--------- .../Configure_T_name.generated.txt | 108 ++++++++--------- ...nfigure_T_name_BinderOptions.generated.txt | 108 ++++++++--------- 24 files changed, 547 insertions(+), 502 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs index cd630e8eeb015..73abbf487882c 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -102,9 +102,9 @@ private void EmitGetCoreMethod() bool isFirstType = true; foreach (TypeSpec type in targetTypes) { - TypeSpec effectiveType = _typeIndex.GetEffectiveTypeSpec(type); - Debug.Assert(_typeIndex.CanBindTo(effectiveType.TypeRef)); + Debug.Assert(_typeIndex.CanBindTo(type.TypeRef)); + TypeSpec effectiveType = _typeIndex.GetEffectiveTypeSpec(type); string conditionKindExpr = GetConditionKindExpr(ref isFirstType); EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.DisplayString}))"); @@ -141,6 +141,12 @@ private void EmitGetCoreMethod() { _writer.WriteLine($@"throw new {Identifier.InvalidOperationException}(""{exMsg}"");"); } +#if DEBUG + else + { + Debug.Fail($"Complex should not be included for GetCore gen: {complexType.DisplayString}"); + } +#endif } break; } @@ -343,7 +349,7 @@ private void EmitInitializeMethod(ObjectSpec type) foreach (PropertySpec property in initOnlyProps) { - if (property.ShouldBindTo && property.MatchingCtorParam is null) + if (_typeIndex.ShouldBindTo(property) && property.MatchingCtorParam is null) { EmitBindImplForMember(property); } @@ -835,9 +841,7 @@ private void EmitBindCoreImplForObject(ObjectSpec type) foreach (PropertySpec property in type.Properties!) { - //bool noSetter_And_IsReadonly = !property.CanSet && property.TypeRef is CollectionSpec { InstantiationStrategy: ObjectInstantiationStrategy.ParameterizedConstructor }; - //if (property.ShouldBindTo && !noSetter_And_IsReadonly) - if (property.ShouldBindTo) + if (_typeIndex.ShouldBindTo(property)) { string containingTypeRef = property.IsStatic ? type.DisplayString : Identifier.instance; EmitBindImplForMember( @@ -1111,6 +1115,8 @@ private bool EmitObjectInit(ComplexTypeSpec type, string memberAccessExpr, Initi { CollectionSpec? collectionType = type as CollectionSpec; ObjectSpec? objectType = type as ObjectSpec; + + string? castExpr = null; string initExpr; string effectiveDisplayString = type.DisplayString; @@ -1124,10 +1130,10 @@ private bool EmitObjectInit(ComplexTypeSpec type, string memberAccessExpr, Initi { CollectionWithCtorInitSpec collectionWithCtorInitType = (CollectionWithCtorInitSpec)collectionType; - // Prepend with a cast to make sure we call the right BindCore method. - string castExpr = collectionWithCtorInitType.InstantiationConcreteType is CollectionInstantiationConcreteType.Self - ? string.Empty - : $"({collectionWithCtorInitType.DisplayString})"; + if (collectionWithCtorInitType.InstantiationConcreteType is not CollectionInstantiationConcreteType.Self) + { + castExpr = $"({collectionWithCtorInitType.DisplayString})"; + } effectiveDisplayString = _typeIndex.GetInstantiationTypeDisplayString(collectionWithCtorInitType); initExpr = $"{castExpr}new {effectiveDisplayString}()"; @@ -1164,12 +1170,15 @@ private bool EmitObjectInit(ComplexTypeSpec type, string memberAccessExpr, Initi if (collectionType is CollectionWithCtorInitSpec { InstantiationStrategy: CollectionInstantiationStrategy.CopyConstructor or CollectionInstantiationStrategy.LinqToDictionary - } collectionWithCtorInit) + } collectionWithCtorInitType) { - string assignmentValueIfMemberNull = collectionWithCtorInit.InstantiationStrategy is CollectionInstantiationStrategy.CopyConstructor + string assignmentValueIfMemberNull = collectionWithCtorInitType.InstantiationStrategy is CollectionInstantiationStrategy.CopyConstructor ? $"new {effectiveDisplayString}({memberAccessExpr})" : $"{memberAccessExpr}.ToDictionary(pair => pair.Key, pair => pair.Value)"; + Debug.Assert(castExpr is not null || collectionWithCtorInitType.InstantiationConcreteType is CollectionInstantiationConcreteType.Self); + assignmentValueIfMemberNull = $"{castExpr}{assignmentValueIfMemberNull}"; + _writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr} is null ? {initExpr} : {assignmentValueIfMemberNull};"); } else diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs index 447dff239674a..3a680c5d52a20 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs @@ -259,10 +259,10 @@ private void RegisterInterceptor_ConfigurationBinder(TypeParseInfo typeParseInfo if ((MethodsToGen.ConfigBinder_Bind & overload) is not 0) { - if (typeSpec is ComplexTypeSpec complexTypeSpec) + if (typeSpec is ComplexTypeSpec complexTypeSpec && + _helperInfoBuilder!.TryRegisterComplexTypeForMethodGen(complexTypeSpec)) { _interceptorInfoBuilder.RegisterInterceptor_ConfigBinder_Bind(overload, complexTypeSpec, invocationOperation); - _helperInfoBuilder!.RegisterComplexTypeForMethodGen(complexTypeSpec); } } else @@ -270,15 +270,13 @@ private void RegisterInterceptor_ConfigurationBinder(TypeParseInfo typeParseInfo Debug.Assert((MethodsToGen.ConfigBinder_Get & overload) is not 0 || (MethodsToGen.ConfigBinder_GetValue & overload) is not 0); - _interceptorInfoBuilder.RegisterInterceptor(overload, invocationOperation); + bool registered = (MethodsToGen.ConfigBinder_Get & overload) is not 0 + ? _helperInfoBuilder!.TryRegisterTypeForGetCoreGen(typeSpec) + : _helperInfoBuilder!.TryRegisterTypeForGetValueCoreGen(typeSpec); - if ((MethodsToGen.ConfigBinder_Get & overload) is not 0) + if (registered) { - _helperInfoBuilder!.RegisterTypeForGetCoreGen(typeSpec); - } - else - { - _helperInfoBuilder!.RegisterTypeForGetValueCoreGen(typeSpec); + _interceptorInfoBuilder.RegisterInterceptor(overload, invocationOperation); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs index 9a6b4ce5a6f9a..eb0ab086bcd58 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsBuilderConfigurationExtensions.cs @@ -95,20 +95,23 @@ private void RegisterInterceptor_OptionsBuilderExt(TypeParseInfo typeParseInfo, if ((MethodsToGen.OptionsBuilderExt_Bind & overload) is not 0) { - RegisterTypeForOverloadGen_ServiceCollectionExt(MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions, complexTypeSpec); + if (!TryRegisterTypeForOverloadGen_ServiceCollectionExt(MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions, complexTypeSpec)) + { + return; + } } - else + else if (!_helperInfoBuilder!.TryRegisterTypeForBindCoreMainGen(complexTypeSpec)) { - _helperInfoBuilder!.TryRegisterTypeForBindCoreMainGen(complexTypeSpec); + return; } _interceptorInfoBuilder.RegisterInterceptor(typeParseInfo.BindingOverload, typeParseInfo.BinderInvocation.Operation); // Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource. - _helperInfoBuilder!.Namespaces.Add("Microsoft.Extensions.Options"); + _helperInfoBuilder!.RegisterNamespace("Microsoft.Extensions.Options"); // Emitting refs to OptionsBuilder. - _helperInfoBuilder!.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); + _helperInfoBuilder!.RegisterNamespace("Microsoft.Extensions.DependencyInjection"); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs index 502a15453a2b6..1ccef24bc6b71 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/OptionsConfigurationServiceCollectionExtensions.cs @@ -80,22 +80,27 @@ private void RegisterInterceptor_ServiceCollectionExt(TypeParseInfo typeParseInf { MethodsToGen overload = typeParseInfo.BindingOverload; - if (typeSpec is ComplexTypeSpec complexTypeSpec) + if (typeSpec is ComplexTypeSpec complexTypeSpec && + TryRegisterTypeForOverloadGen_ServiceCollectionExt(overload, complexTypeSpec)) { - RegisterTypeForOverloadGen_ServiceCollectionExt(overload, complexTypeSpec); _interceptorInfoBuilder.RegisterInterceptor(overload, typeParseInfo.BinderInvocation.Operation); } } - private void RegisterTypeForOverloadGen_ServiceCollectionExt(MethodsToGen overload, ComplexTypeSpec typeSpec) + private bool TryRegisterTypeForOverloadGen_ServiceCollectionExt(MethodsToGen overload, ComplexTypeSpec typeSpec) { Debug.Assert((MethodsToGen.ServiceCollectionExt_Any & overload) is not 0); - _helperInfoBuilder!.TryRegisterTypeForBindCoreMainGen(typeSpec); + + if (!_helperInfoBuilder!.TryRegisterTypeForBindCoreMainGen(typeSpec)) + { + return false; + } _interceptorInfoBuilder.MethodsToGen |= overload; - _helperInfoBuilder!.Namespaces.Add("Microsoft.Extensions.DependencyInjection"); + _helperInfoBuilder!.RegisterNamespace("Microsoft.Extensions.DependencyInjection"); // Emitting refs to IOptionsChangeTokenSource, ConfigurationChangeTokenSource, IConfigureOptions<>, ConfigureNamedOptions<>. - _helperInfoBuilder!.Namespaces.Add("Microsoft.Extensions.Options"); + _helperInfoBuilder!.RegisterNamespace("Microsoft.Extensions.Options"); + return true; } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs index 7fbb2a5f94da5..c0cad319a2b20 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using SourceGenerators; @@ -21,14 +22,14 @@ public sealed record BindingHelperInfo public required ImmutableEquatableArray? TypesForGen_Initialize { get; init; } public required ImmutableEquatableArray? TypesForGen_ParsePrimitive { get; init; } - internal sealed class Builder(TypeIndex typeIndex) + internal sealed class Builder(TypeIndex _typeIndex) { private MethodsToGen_CoreBindingHelper _methodsToGen; private bool _emitConfigurationKeyCaches; private readonly Dictionary> _typesForGen = new(); - public SortedSet Namespaces { get; } = new() + private readonly SortedSet _namespaces = new() { "System", "System.CodeDom.Compiler", @@ -37,27 +38,34 @@ internal sealed class Builder(TypeIndex typeIndex) "Microsoft.Extensions.Configuration", }; - public void RegisterComplexTypeForMethodGen(ComplexTypeSpec type) + public bool TryRegisterComplexTypeForMethodGen(ComplexTypeSpec type) { + if (!_typeIndex.CanBindTo(type.TypeRef)) + { + return false; + } + if (type is ObjectSpec { InstantiationStrategy: ObjectInstantiationStrategy.ParameterizedConstructor } objectType - && typeIndex.CanInstantiate(objectType)) + && _typeIndex.CanInstantiate(objectType)) { RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.Initialize, type); } else if (type is DictionarySpec { InstantiationStrategy: CollectionInstantiationStrategy.LinqToDictionary }) { - Namespaces.Add("System.Linq"); + _namespaces.Add("System.Linq"); } - RegisterTypeForBindCoreGen(type); + TryRegisterTypeForBindCoreGen(type); + return true; } public bool TryRegisterTypeForBindCoreMainGen(ComplexTypeSpec type) { - if (typeIndex.HasBindableMembers(type)) + if (_typeIndex.HasBindableMembers(type)) { + bool bindCoreRegistered = TryRegisterTypeForBindCoreGen(type); + Debug.Assert(bindCoreRegistered); RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreMain, type); - RegisterTypeForBindCoreGen(type); RegisterForGen_AsConfigWithChildrenHelper(); return true; } @@ -65,9 +73,9 @@ public bool TryRegisterTypeForBindCoreMainGen(ComplexTypeSpec type) return false; } - public void RegisterTypeForBindCoreGen(ComplexTypeSpec type) + public bool TryRegisterTypeForBindCoreGen(ComplexTypeSpec type) { - if (typeIndex.HasBindableMembers(type)) + if (_typeIndex.HasBindableMembers(type)) { RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, type); @@ -77,45 +85,49 @@ public void RegisterTypeForBindCoreGen(ComplexTypeSpec type) // List is used in generated code as a temp holder for formatting // an error for config properties that don't map to object properties. - Namespaces.Add("System.Collections.Generic"); + _namespaces.Add("System.Collections.Generic"); } + + return true; } + + return false; } - public void RegisterTypeForGetCoreGen(TypeSpec type) + public bool TryRegisterTypeForGetCoreGen(TypeSpec type) { - if (typeIndex.CanBindTo(type.TypeRef)) + if (!_typeIndex.CanBindTo(type.TypeRef)) { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, type); + return false; + } - if (type is ComplexTypeSpec complexType) - { - RegisterComplexTypeForMethodGen(complexType); - } + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, type); + RegisterForGen_AsConfigWithChildrenHelper(); + + if (type is ComplexTypeSpec complexType) + { + bool registered = TryRegisterComplexTypeForMethodGen(complexType); + Debug.Assert(registered); } + + return true; } - public void RegisterTypeForGetValueCoreGen(TypeSpec type) + public bool TryRegisterTypeForGetValueCoreGen(TypeSpec type) { - ParsableFromStringSpec effectiveType = (ParsableFromStringSpec)typeIndex.GetEffectiveTypeSpec(type); + ParsableFromStringSpec effectiveType = (ParsableFromStringSpec)_typeIndex.GetEffectiveTypeSpec(type); RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, type); RegisterStringParsableType(effectiveType); + return true; } - public void RegisterStringParsableType(ParsableFromStringSpec type) - { - if (type.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) - { - _methodsToGen |= MethodsToGen_CoreBindingHelper.ParsePrimitive; - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.ParsePrimitive, type); - } - } + public void RegisterNamespace(string @namespace) => _namespaces.Add(@namespace); public BindingHelperInfo ToIncrementalValue() { return new BindingHelperInfo { - Namespaces = Namespaces.ToImmutableEquatableArray(), + Namespaces = _namespaces.ToImmutableEquatableArray(), EmitConfigurationKeyCaches = _emitConfigurationKeyCaches, MethodsToGen = _methodsToGen, @@ -162,7 +174,7 @@ private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, Typ if (type is { Namespace: string @namespace }) { - Namespaces.Add(@namespace); + _namespaces.Add(@namespace); } RegisterTransitiveTypesForMethodGen(type); @@ -175,7 +187,7 @@ private void RegisterTransitiveTypesForMethodGen(TypeSpec typeSpec) { case NullableSpec nullableTypeSpec: { - RegisterTransitiveTypesForMethodGen(typeIndex.GetEffectiveTypeSpec(nullableTypeSpec)); + RegisterTransitiveTypesForMethodGen(_typeIndex.GetEffectiveTypeSpec(nullableTypeSpec)); } break; case ArraySpec: @@ -190,7 +202,7 @@ private void RegisterTransitiveTypesForMethodGen(TypeSpec typeSpec) RegisterComplexTypeForMethodGen(dictionaryTypeSpec.ElementTypeRef); } break; - case ObjectSpec objectType when typeIndex.HasBindableMembers(objectType): + case ObjectSpec objectType when _typeIndex.HasBindableMembers(objectType): { foreach (PropertySpec property in objectType.Properties!) { @@ -202,7 +214,7 @@ private void RegisterTransitiveTypesForMethodGen(TypeSpec typeSpec) void RegisterComplexTypeForMethodGen(TypeRef transitiveTypeRef) { - TypeSpec effectiveTypeSpec = typeIndex.GetTypeSpec(transitiveTypeRef); + TypeSpec effectiveTypeSpec = _typeIndex.GetTypeSpec(transitiveTypeRef); if (effectiveTypeSpec is ParsableFromStringSpec parsableFromStringSpec) { @@ -210,16 +222,25 @@ void RegisterComplexTypeForMethodGen(TypeRef transitiveTypeRef) } else if (effectiveTypeSpec is ComplexTypeSpec complexEffectiveTypeSpec) { - if (typeIndex.HasBindableMembers(complexEffectiveTypeSpec)) + if (_typeIndex.HasBindableMembers(complexEffectiveTypeSpec)) { RegisterForGen_AsConfigWithChildrenHelper(); } - this.RegisterComplexTypeForMethodGen(complexEffectiveTypeSpec); + TryRegisterComplexTypeForMethodGen(complexEffectiveTypeSpec); } } } + private void RegisterStringParsableType(ParsableFromStringSpec type) + { + if (type.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) + { + _methodsToGen |= MethodsToGen_CoreBindingHelper.ParsePrimitive; + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.ParsePrimitive, type); + } + } + private void RegisterForGen_AsConfigWithChildrenHelper() => _methodsToGen |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs index fec66376df142..443e39d32e493 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Members/PropertySpec.cs @@ -28,7 +28,5 @@ public PropertySpec(IPropertySymbol property) : base(property) public override bool CanGet { get; } public override bool CanSet { get; } - - public bool ShouldBindTo => CanGet || CanSet; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs index c4b3c4fb8b70c..9564008084ab7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs @@ -33,12 +33,25 @@ internal sealed class TypeIndex(IEnumerable typeSpecs) public bool HasBindableMembers(ComplexTypeSpec typeSpec) => typeSpec switch { - ObjectSpec objectSpec => objectSpec.Properties?.Any(p => p.ShouldBindTo) is true, + ObjectSpec objectSpec => objectSpec.Properties?.Any(ShouldBindTo) is true, DictionarySpec dictSpec => GetTypeSpec(dictSpec.KeyTypeRef) is ParsableFromStringSpec && CanBindTo(dictSpec.ElementTypeRef), CollectionSpec collectionSpec => CanBindTo(collectionSpec.ElementTypeRef), _ => throw new InvalidOperationException(), }; + public bool ShouldBindTo(PropertySpec property) + { + bool isAccessible = property.CanGet || property.CanSet; + + bool isCollectionAndCannotOverride = !property.CanSet && + GetEffectiveTypeSpec(property.TypeRef) is CollectionWithCtorInitSpec + { + InstantiationStrategy: CollectionInstantiationStrategy.CopyConstructor or CollectionInstantiationStrategy.LinqToDictionary + }; + + return isAccessible && !isCollectionAndCannotOverride; + } + public TypeSpec GetEffectiveTypeSpec(TypeRef typeRef) { TypeSpec typeSpec = GetTypeSpec(typeRef); @@ -57,8 +70,6 @@ public TypeSpec GetEffectiveTypeSpec(TypeSpec typeSpec) public string GetInstantiationTypeDisplayString(CollectionWithCtorInitSpec type) { CollectionInstantiationConcreteType concreteType = type.InstantiationConcreteType; - Debug.Assert(concreteType is not CollectionInstantiationConcreteType.None); - return concreteType is CollectionInstantiationConcreteType.Self ? type.DisplayString : GetGenericTypeDisplayString(type, concreteType); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs index 3f320f278f7db..f891328f77af7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/CollectionSpec.cs @@ -48,16 +48,14 @@ internal enum CollectionInstantiationStrategy ParameterlessConstructor = 1, CopyConstructor = 2, LinqToDictionary = 3, - AssignableConcreteTypeWithParameterlessCtor = 4, } internal enum CollectionInstantiationConcreteType { - None = 0, - Self = 1, - Dictionary = 2, - List = 3, - HashSet = 4, + Self = 0, + Dictionary = 1, + List = 2, + HashSet = 3, } internal enum CollectionPopulationCastType diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs index 803606caab5cd..1c243ae1cdc7c 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs @@ -13,11 +13,10 @@ public abstract record TypeSpec public TypeSpec(ITypeSymbol type) { TypeRef = new TypeRef(type); + EffectiveTypeRef = TypeRef; // Overriden by NullableSpec. (Namespace, DisplayString, Name) = type.GetTypeName(); IdentifierCompatibleSubstring = type.ToIdentifierCompatibleSubstring(); IsValueType = type.IsValueType; - - EffectiveTypeRef = TypeRef; // Overriden by NullableSpec. } public TypeRef TypeRef { get; } 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 568eb56f9ed8b..97ea6db5885bb 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 @@ -88,40 +88,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) - { - element = new Program.MyClass2(); - } - instance[section.Key] = element; - } - } - - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = value; - } - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); @@ -144,7 +110,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["MyInt"] is string value6) { - instance.MyInt = ParseInt(value6, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value6, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -165,6 +131,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -215,7 +214,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 84e94ea6470ac..2e91c870d1dc7 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 @@ -52,40 +52,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) - { - element = new Program.MyClass2(); - } - instance[section.Key] = element; - } - } - - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = value; - } - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); @@ -108,7 +74,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["MyInt"] is string value6) { - instance.MyInt = ParseInt(value6, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value6, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -129,6 +95,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -161,7 +160,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 fa23f19a0b882..d41c4adf5479d 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 @@ -81,77 +81,46 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } - public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - var temp2 = new List(); - BindCore(configuration, ref temp2, defaultValueIfNotFound: false, binderOptions); - int originalCount = instance.Length; - Array.Resize(ref instance, originalCount + temp2.Count); - temp2.CopyTo(instance, originalCount); - } - - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = value; - } - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section4) + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section2) { - int[]? temp6 = instance.MyArray; - temp6 ??= new int[0]; - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp6; + int[]? temp4 = instance.MyArray; + temp4 ??= new int[0]; + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp4; } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section7) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) { - Dictionary? temp9 = instance.MyDictionary; - temp9 ??= new Dictionary(); - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp9; + Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; } - if (configuration["MyInt"] is string value10) + if (configuration["MyInt"] is string value8) { - instance.MyInt = ParseInt(value10, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value8, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section11) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section9) { - List? temp13 = instance.MyList; - temp13 ??= new List(); - BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp13; + List? temp11 = instance.MyList; + temp11 ??= new List(); + BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp11; } - if (configuration["MyString"] is string value14) + if (configuration["MyString"] is string value12) { - instance.MyString = value14; + instance.MyString = value12; } } @@ -159,9 +128,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value15) + if (configuration["MyInt"] is string value13) { - instance.MyInt = ParseInt(value15, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value13, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -169,6 +138,45 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + + public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + var temp = new List(); + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + temp.Add(ParseInt32(value, () => section.Path)); + } + } + + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp.Count); + temp.CopyTo(instance, originalCount); + } + /// 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) @@ -228,7 +236,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 c2037aecf05f0..17660edfae642 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 @@ -61,13 +61,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - if (type == typeof(byte[])) + if (type == typeof(bool?)) { - return ParseByteArray(value, () => section.Path); + return ParseBoolean(value, () => section.Path); } - else if (type == typeof(bool?)) + else if (type == typeof(byte[])) { - return ParseBool(value, () => section.Path); + return ParseByteArray(value, () => section.Path); } else if (type == typeof(CultureInfo)) { @@ -75,33 +75,33 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } else if (type == typeof(int)) { - return ParseInt(value, () => section.Path); + return ParseInt32(value, () => section.Path); } return null; } - public static byte[] ParseByteArray(string value, Func getPath) + public static bool ParseBoolean(string value, Func getPath) { try { - return Convert.FromBase64String(value); + return bool.Parse(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); } } - public static bool ParseBool(string value, Func getPath) + public static byte[] ParseByteArray(string value, Func getPath) { try { - return bool.Parse(value); + return Convert.FromBase64String(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); } } @@ -117,7 +117,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 b86915b78303b..4d3bde81dcab3 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 @@ -51,13 +51,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (type == typeof(int)) { - return ParseInt(value, () => section.Path); + return ParseInt32(value, () => section.Path); } return null; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 b5a22e71ccefb..cb86c3581112a 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 @@ -51,13 +51,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (type == typeof(bool?)) { - return ParseBool(value, () => section.Path); + return ParseBoolean(value, () => section.Path); } return null; } - public static bool ParseBool(string value, Func getPath) + public static bool ParseBoolean(string value, Func getPath) { try { 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 eca2001123ff0..c838ae5aa61de 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 @@ -68,7 +68,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -135,7 +135,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 7883f4a50da0f..f4c9cb643836f 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 @@ -68,7 +68,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -135,7 +135,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt index 404ce7561cc47..73121ed621a24 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt @@ -30,6 +30,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration file static class BindingExtensions { #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 18, 23)] + public static void Bind_ListAbstractType_CannotInit(this IConfiguration configuration, object? instance) + { + } + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. [InterceptsLocation(@"src-0.cs", 12, 23)] public static void Bind_TypeWithNoMembers(this IConfiguration configuration, object? instance) @@ -90,15 +96,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } } - - public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) - { - foreach (IConfigurationSection _ in configuration.GetChildren()) - { - return configuration; - } - return null; - } #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 0fd5b77d800e6..6c0903bf75b55 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 @@ -99,24 +99,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -137,6 +126,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -196,7 +196,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 f6e1f3eee898f..949d2a46ae273 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 @@ -58,7 +58,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop0"] is string value0) { - instance.Prop0 = ParseBool(value0, () => configuration.GetSection("Prop0").Path); + instance.Prop0 = ParseBoolean(value0, () => configuration.GetSection("Prop0").Path); } else if (defaultValueIfNotFound) { @@ -76,7 +76,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop10"] is string value2) { - instance.Prop10 = ParseFloat(value2, () => configuration.GetSection("Prop10").Path); + instance.Prop10 = ParseSingle(value2, () => configuration.GetSection("Prop10").Path); } else if (defaultValueIfNotFound) { @@ -103,7 +103,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop13"] is string value5) { - instance.Prop13 = ParseUshort(value5, () => configuration.GetSection("Prop13").Path); + instance.Prop13 = ParseUInt16(value5, () => configuration.GetSection("Prop13").Path); } else if (defaultValueIfNotFound) { @@ -112,7 +112,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop14"] is string value6) { - instance.Prop14 = ParseUint(value6, () => configuration.GetSection("Prop14").Path); + instance.Prop14 = ParseUInt32(value6, () => configuration.GetSection("Prop14").Path); } else if (defaultValueIfNotFound) { @@ -121,7 +121,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop15"] is string value7) { - instance.Prop15 = ParseUlong(value7, () => configuration.GetSection("Prop15").Path); + instance.Prop15 = ParseUInt64(value7, () => configuration.GetSection("Prop15").Path); } else if (defaultValueIfNotFound) { @@ -158,7 +158,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop2"] is string value12) { - instance.Prop2 = ParseSbyte(value12, () => configuration.GetSection("Prop2").Path); + instance.Prop2 = ParseSByte(value12, () => configuration.GetSection("Prop2").Path); } else if (defaultValueIfNotFound) { @@ -236,7 +236,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop29"] is string value22) { - instance.Prop29 = ParseInt(value22, () => configuration.GetSection("Prop29").Path); + instance.Prop29 = ParseInt32(value22, () => configuration.GetSection("Prop29").Path); } else if (defaultValueIfNotFound) { @@ -277,7 +277,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop6"] is string value27) { - instance.Prop6 = ParseInt(value27, () => configuration.GetSection("Prop6").Path); + instance.Prop6 = ParseInt32(value27, () => configuration.GetSection("Prop6").Path); } else if (defaultValueIfNotFound) { @@ -295,7 +295,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop8"] is string value29) { - instance.Prop8 = ParseShort(value29, () => configuration.GetSection("Prop8").Path); + instance.Prop8 = ParseInt16(value29, () => configuration.GetSection("Prop8").Path); } else if (defaultValueIfNotFound) { @@ -304,7 +304,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop9"] is string value30) { - instance.Prop9 = ParseLong(value30, () => configuration.GetSection("Prop9").Path); + instance.Prop9 = ParseInt64(value30, () => configuration.GetSection("Prop9").Path); } else if (defaultValueIfNotFound) { @@ -335,39 +335,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static byte[] ParseByteArray(string value, Func getPath) + public static bool ParseBoolean(string value, Func getPath) { try { - return Convert.FromBase64String(value); + return bool.Parse(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); } } - public static bool ParseBool(string value, Func getPath) + public static byte ParseByte(string value, Func getPath) { try { - return bool.Parse(value); + return byte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte)}'.", exception); } } - public static byte ParseByte(string value, Func getPath) + public static byte[] ParseByteArray(string value, Func getPath) { try { - return byte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return Convert.FromBase64String(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); } } @@ -407,7 +407,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static float ParseFloat(string value, Func getPath) + public static float ParseSingle(string value, Func getPath) { try { @@ -555,6 +555,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static Uri ParseUri(string value, Func getPath) + { + try + { + return new Uri(value, UriKind.RelativeOrAbsolute); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Uri)}'.", exception); + } + } + public static Version ParseVersion(string value, Func getPath) { try @@ -567,7 +579,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { @@ -579,7 +591,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static long ParseLong(string value, Func getPath) + public static long ParseInt64(string value, Func getPath) { try { @@ -591,7 +603,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static sbyte ParseSbyte(string value, Func getPath) + public static sbyte ParseSByte(string value, Func getPath) { try { @@ -603,7 +615,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static short ParseShort(string value, Func getPath) + public static short ParseInt16(string value, Func getPath) { try { @@ -615,7 +627,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static uint ParseUint(string value, Func getPath) + public static uint ParseUInt32(string value, Func getPath) { try { @@ -627,7 +639,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static ulong ParseUlong(string value, Func getPath) + public static ulong ParseUInt64(string value, Func getPath) { try { @@ -639,7 +651,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static ushort ParseUshort(string value, Func getPath) + public static ushort ParseUInt16(string value, Func getPath) { try { @@ -650,18 +662,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ushort)}'.", exception); } } - - public static Uri ParseUri(string value, Func getPath) - { - try - { - return new Uri(value, UriKind.RelativeOrAbsolute); - } - catch (Exception exception) - { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Uri)}'.", 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 5c8e2bffda59c..145093ea55aac 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 @@ -86,78 +86,46 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = value; - } - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - var value = new Program.MyClass2(); - BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); - instance.Add(value); - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section2) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section1) { - Dictionary? temp4 = instance.MyDictionary; - temp4 ??= new Dictionary(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp4; + Dictionary? temp3 = instance.MyDictionary; + temp3 ??= new Dictionary(); + BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp3; } - if (configuration["MyInt"] is string value5) + if (configuration["MyInt"] is string value4) { - instance.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value4, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) { - List? temp8 = instance.MyList; - temp8 ??= new List(); - BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp8; + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section9) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - List? temp11 = instance.MyList2; - temp11 ??= new List(); - BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp11; + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; } - if (configuration["MyString"] is string value12) + if (configuration["MyString"] is string value11) { - instance.MyString = value12; + instance.MyString = value11; } } @@ -165,9 +133,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value13) + if (configuration["MyInt"] is string value12) { - instance.MyInt = ParseInt(value13, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value12, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -175,6 +143,38 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -234,7 +234,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 c49be31cfdc6f..4aa428bba74f2 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 @@ -86,78 +86,46 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = value; - } - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - var value = new Program.MyClass2(); - BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); - instance.Add(value); - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section2) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section1) { - Dictionary? temp4 = instance.MyDictionary; - temp4 ??= new Dictionary(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp4; + Dictionary? temp3 = instance.MyDictionary; + temp3 ??= new Dictionary(); + BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp3; } - if (configuration["MyInt"] is string value5) + if (configuration["MyInt"] is string value4) { - instance.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value4, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) { - List? temp8 = instance.MyList; - temp8 ??= new List(); - BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp8; + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section9) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - List? temp11 = instance.MyList2; - temp11 ??= new List(); - BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp11; + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; } - if (configuration["MyString"] is string value12) + if (configuration["MyString"] is string value11) { - instance.MyString = value12; + instance.MyString = value11; } } @@ -165,9 +133,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value13) + if (configuration["MyInt"] is string value12) { - instance.MyInt = ParseInt(value13, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value12, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -175,6 +143,38 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -234,7 +234,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 de3bbce3bec8f..d456f83046649 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 @@ -86,78 +86,46 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = value; - } - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - var value = new Program.MyClass2(); - BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); - instance.Add(value); - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section2) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section1) { - Dictionary? temp4 = instance.MyDictionary; - temp4 ??= new Dictionary(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp4; + Dictionary? temp3 = instance.MyDictionary; + temp3 ??= new Dictionary(); + BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp3; } - if (configuration["MyInt"] is string value5) + if (configuration["MyInt"] is string value4) { - instance.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value4, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) { - List? temp8 = instance.MyList; - temp8 ??= new List(); - BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp8; + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section9) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - List? temp11 = instance.MyList2; - temp11 ??= new List(); - BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp11; + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; } - if (configuration["MyString"] is string value12) + if (configuration["MyString"] is string value11) { - instance.MyString = value12; + instance.MyString = value11; } } @@ -165,9 +133,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value13) + if (configuration["MyInt"] is string value12) { - instance.MyInt = ParseInt(value13, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value12, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -175,6 +143,38 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -234,7 +234,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { 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 30c6882448122..6c483b07dc5b3 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 @@ -80,78 +80,46 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = value; - } - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - var value = new Program.MyClass2(); - BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); - instance.Add(value); - } - } - - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section2) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section1) { - Dictionary? temp4 = instance.MyDictionary; - temp4 ??= new Dictionary(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp4; + Dictionary? temp3 = instance.MyDictionary; + temp3 ??= new Dictionary(); + BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp3; } - if (configuration["MyInt"] is string value5) + if (configuration["MyInt"] is string value4) { - instance.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value4, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) { - List? temp8 = instance.MyList; - temp8 ??= new List(); - BindCore(section6, ref temp8, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp8; + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section9) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - List? temp11 = instance.MyList2; - temp11 ??= new List(); - BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp11; + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; } - if (configuration["MyString"] is string value12) + if (configuration["MyString"] is string value11) { - instance.MyString = value12; + instance.MyString = value11; } } @@ -159,9 +127,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - if (configuration["MyInt"] is string value13) + if (configuration["MyInt"] is string value12) { - instance.MyInt = ParseInt(value13, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt32(value12, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -169,6 +137,38 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance[section.Key] = value; + } + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); + } + } + + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt32(value, () => section.Path)); + } + } + } + /// 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) @@ -228,7 +228,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt(string value, Func getPath) + public static int ParseInt32(string value, Func getPath) { try { From 23abc0de323dc37601abc1bc570c6306d5928e74 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 22 Sep 2023 15:52:54 -0700 Subject: [PATCH 07/11] Suppress diagnostic --- .../src/Microsoft.Extensions.Logging.Console.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 8ae9d3eaa61cf..ee8bc96621436 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 @@ -11,7 +11,8 @@ $(InterceptorsPreviewNamespaces);Microsoft.Extensions.Configuration.Binder.SourceGeneration true - true + + $(NoWarn);SYSLIB1100;SYSLIB1101 Console logger provider implementation for Microsoft.Extensions.Logging. From 90be738918a08005103aefa278200e8252be3fe2 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 22 Sep 2023 16:27:06 -0700 Subject: [PATCH 08/11] Address feedback on diag info creation --- .../src/Roslyn/DiagnosticDescriptorHelper.cs | 6 -- .../src/SourceGenerators/DiagnosticInfo.cs | 22 ++++- .../ConfigurationBindingGenerator.Parser.cs | 24 ++---- .../gen/Parser/ConfigurationBinder.cs | 6 +- .../gen/Specs/InterceptorInfo.cs | 41 +++------ ...ation.Binder.SourceGeneration.Tests.csproj | 6 +- .../gen/Helpers/DiagnosticInfo.cs | 43 ---------- .../gen/Helpers/ImmutableEquatableArray.cs | 86 ------------------- .../gen/JsonSourceGenerator.Parser.cs | 7 +- .../gen/JsonSourceGenerator.Roslyn3.11.cs | 1 + .../gen/JsonSourceGenerator.Roslyn4.0.cs | 1 + .../System.Text.Json.SourceGeneration.targets | 4 +- 12 files changed, 51 insertions(+), 196 deletions(-) delete mode 100644 src/libraries/System.Text.Json/gen/Helpers/DiagnosticInfo.cs delete mode 100644 src/libraries/System.Text.Json/gen/Helpers/ImmutableEquatableArray.cs diff --git a/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs b/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs index f684ae76038df..07a3fbf52bb0b 100644 --- a/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs +++ b/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs @@ -19,11 +19,5 @@ public static DiagnosticDescriptor Create( return new DiagnosticDescriptor(id, title, messageFormat, category, defaultSeverity, isEnabledByDefault, description, helpLink, customTags); } - - /// - /// Creates a copy of the Location instance that does not capture a reference to Compilation. - /// - public static Location GetTrimmedLocation(this Location location) - => Location.Create(location.SourceTree?.FilePath ?? "", location.SourceSpan, location.GetLineSpan().Span); } } diff --git a/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs b/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs index de8e5e748d1b3..74f44f99c62ba 100644 --- a/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs +++ b/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs @@ -14,9 +14,25 @@ namespace SourceGenerators; /// internal readonly struct DiagnosticInfo : IEquatable { - public required DiagnosticDescriptor Descriptor { get; init; } - public required object?[] MessageArgs { get; init; } - public required Location? Location { get; init; } + public DiagnosticDescriptor Descriptor { get; private init; } + public object?[] MessageArgs { get; private init; } + public Location? Location { get; private init; } + + public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, Location? location, object?[]? messageArgs) + { + Location? trimmedLocation = location is null ? null : GetTrimmedLocation(location); + + return new DiagnosticInfo + { + Descriptor = descriptor, + Location = trimmedLocation, + MessageArgs = messageArgs ?? Array.Empty() + }; + + // Creates a copy of the Location instance that does not capture a reference to Compilation. + static Location GetTrimmedLocation(Location location) + => Location.Create(location.SourceTree?.FilePath ?? "", location.SourceSpan, location.GetLineSpan().Span); + } public Diagnostic CreateDiagnostic() => Diagnostic.Create(Descriptor, Location, MessageArgs); 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 a89752562e3e8..f2ec002bb3234 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -34,7 +34,7 @@ internal sealed partial class Parser(CompilationData compilationData) { if (!_langVersionIsSupported) { - RecordDiagnostic(DiagnosticDescriptors.LanguageVersionNotSupported, trimmedLocation: Location.None.GetTrimmedLocation()); + RecordDiagnostic(DiagnosticDescriptors.LanguageVersionNotSupported, trimmedLocation: Location.None); return null; } @@ -125,7 +125,7 @@ private void EnqueueTargetTypeForRootInvocation(ITypeSymbol? typeSymbol, Methods { if (!IsValidRootConfigType(typeSymbol)) { - RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, binderInvocation); + RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, binderInvocation.Location); } else { @@ -804,9 +804,9 @@ private void RecordDiagnostic(UnsupportedTypeSpec type, TypeParseInfo typeParseI private void RecordDiagnostic(TypeParseInfo typeParseInfo, DiagnosticDescriptor descriptor) { string typeName = typeParseInfo.TypeSymbol.GetName(); - BinderInvocation binderInvocation = typeParseInfo.BinderInvocation; + Location invocationLocation = typeParseInfo.BinderInvocation.Location; - RecordDiagnostic(descriptor, binderInvocation, new object?[] { typeName }); + RecordDiagnostic(descriptor, invocationLocation, new object?[] { typeName }); if (typeParseInfo.ContainingTypeDiagnosticInfo is ContainingTypeDiagnosticInfo containingTypeDiagInfo) { @@ -814,24 +814,14 @@ private void RecordDiagnostic(TypeParseInfo typeParseInfo, DiagnosticDescriptor ? new[] { memberName, typeName } : new[] { typeName }; - RecordDiagnostic(containingTypeDiagInfo.Descriptor, binderInvocation, messageArgs); + RecordDiagnostic(containingTypeDiagInfo.Descriptor, invocationLocation, messageArgs); } } - private void RecordDiagnostic(DiagnosticDescriptor descriptor, BinderInvocation binderInvocation, params object?[]? messageArgs) - { - Location trimmedLocation = InvocationLocationInfo.GetTrimmedLocation(binderInvocation.Operation); - RecordDiagnostic(descriptor, trimmedLocation, messageArgs); - } - private void RecordDiagnostic(DiagnosticDescriptor descriptor, Location trimmedLocation, params object?[]? messageArgs) { - (Diagnostics ??= new()).Add(new DiagnosticInfo - { - Descriptor = descriptor, - Location = trimmedLocation, - MessageArgs = messageArgs ?? Array.Empty(), - }); + Diagnostics ??= new List(); + Diagnostics.Add(DiagnosticInfo.Create(descriptor, trimmedLocation, messageArgs)); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs index 3a680c5d52a20..075aefd3367b2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs @@ -88,13 +88,13 @@ private void ParseBindInvocation_ConfigurationBinder(BinderInvocation invocation if (!IsValidRootConfigType(type)) { - RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation); + RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); return; } if (type.IsValueType) { - RecordDiagnostic(DiagnosticDescriptors.ValueTypesInvalidForBind, invocation, messageArgs: new object[] { type }); + RecordDiagnostic(DiagnosticDescriptors.ValueTypesInvalidForBind, invocation.Location, messageArgs: new object[] { type }); return; } @@ -239,7 +239,7 @@ private void ParseGetValueInvocation(BinderInvocation invocation) if (!IsValidRootConfigType(type)) { - RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation); + RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); return; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs index b12dd3fef000d..a9a28632db736 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs @@ -170,41 +170,28 @@ public sealed record InvocationLocationInfo { public InvocationLocationInfo(MethodsToGen interceptor, IInvocationOperation invocation) { - FileLinePositionSpan linePosSpan = GetLocationSpanInfo(invocation).LinePostionSpan; + MemberAccessExpressionSyntax memberAccessExprSyntax = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)invocation.Syntax).Expression); + SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; + TextSpan memberNameSpan = memberAccessExprSyntax.Name.Span; + FileLinePositionSpan linePosSpan = operationSyntaxTree.GetLineSpan(memberNameSpan); + Interceptor = interceptor; LineNumber = linePosSpan.StartLinePosition.Line + 1; CharacterNumber = linePosSpan.StartLinePosition.Character + 1; - FilePath = GetFilePath(invocation); + FilePath = GetInterceptorFilePath(); + + // Use 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 + string GetInterceptorFilePath() + { + SourceReferenceResolver? sourceReferenceResolver = invocation.SemanticModel?.Compilation.Options.SourceReferenceResolver; + return sourceReferenceResolver?.NormalizePath(operationSyntaxTree.FilePath, baseFilePath: null) ?? operationSyntaxTree.FilePath; + } } public MethodsToGen Interceptor { get; } public string FilePath { get; } public int LineNumber { get; } public int CharacterNumber { get; } - - public static Location GetTrimmedLocation(IInvocationOperation invocation) - { - string filePath = GetFilePath(invocation); - (TextSpan memberNameSpan, FileLinePositionSpan linePosSpan) = GetLocationSpanInfo(invocation); - return Location.Create(filePath, memberNameSpan, linePosSpan.Span); - } - - // Use 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 GetFilePath(IInvocationOperation invocation) - { - SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; - SourceReferenceResolver? sourceReferenceResolver = invocation.SemanticModel?.Compilation.Options.SourceReferenceResolver; - return sourceReferenceResolver?.NormalizePath(operationSyntaxTree.FilePath, baseFilePath: null) ?? operationSyntaxTree.FilePath; - } - - private static (TextSpan MemberNameSpan, FileLinePositionSpan LinePostionSpan) GetLocationSpanInfo(IInvocationOperation invocation) - { - MemberAccessExpressionSyntax memberAccessExprSyntax = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)invocation.Syntax).Expression); - TextSpan memberNameSpan = memberAccessExprSyntax.Name.Span; - SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; - FileLinePositionSpan linePosSpan = operationSyntaxTree.GetLineSpan(memberNameSpan); - return (memberNameSpan, linePosSpan); - } } } 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 d202c3968a4bf..f4b493becf711 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 @@ -33,9 +33,6 @@ - - - @@ -55,10 +52,13 @@ PreserveNewest + + + diff --git a/src/libraries/System.Text.Json/gen/Helpers/DiagnosticInfo.cs b/src/libraries/System.Text.Json/gen/Helpers/DiagnosticInfo.cs deleted file mode 100644 index 493f79191d437..0000000000000 --- a/src/libraries/System.Text.Json/gen/Helpers/DiagnosticInfo.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq; -using System.Numerics.Hashing; -using Microsoft.CodeAnalysis; - -namespace System.Text.Json.SourceGeneration -{ - /// - /// Descriptor for diagnostic instances using structural equality comparison. - /// Provides a work-around for https://github.com/dotnet/roslyn/issues/68291. - /// - public readonly struct DiagnosticInfo : IEquatable - { - public required DiagnosticDescriptor Descriptor { get; init; } - public required object?[] MessageArgs { get; init; } - public required Location? Location { get; init; } - - public Diagnostic CreateDiagnostic() - => Diagnostic.Create(Descriptor, Location, MessageArgs); - - public override readonly bool Equals(object? obj) => obj is DiagnosticInfo info && Equals(info); - public readonly bool Equals(DiagnosticInfo other) - { - return Descriptor.Equals(other.Descriptor) && - MessageArgs.SequenceEqual(other.MessageArgs) && - Location == other.Location; - } - - public override readonly int GetHashCode() - { - int hashCode = Descriptor.GetHashCode(); - foreach (object? messageArg in MessageArgs) - { - hashCode = HashHelpers.Combine(hashCode, messageArg?.GetHashCode() ?? 0); - } - - hashCode = HashHelpers.Combine(hashCode, Location?.GetHashCode() ?? 0); - return hashCode; - } - } -} diff --git a/src/libraries/System.Text.Json/gen/Helpers/ImmutableEquatableArray.cs b/src/libraries/System.Text.Json/gen/Helpers/ImmutableEquatableArray.cs deleted file mode 100644 index ac3aa804fdd9d..0000000000000 --- a/src/libraries/System.Text.Json/gen/Helpers/ImmutableEquatableArray.cs +++ /dev/null @@ -1,86 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Linq; -using System.Numerics.Hashing; - -namespace System.Text.Json.SourceGeneration -{ - /// - /// Provides an immutable list implementation which implements sequence equality. - /// - public sealed class ImmutableEquatableArray : IEquatable>, IReadOnlyList - where T : IEquatable - { - public static ImmutableEquatableArray Empty { get; } = new ImmutableEquatableArray(Array.Empty()); - - private readonly T[] _values; - public T this[int index] => _values[index]; - public int Count => _values.Length; - - public ImmutableEquatableArray(IEnumerable values) - => _values = values.ToArray(); - - public bool Equals(ImmutableEquatableArray? other) - => other != null && ((ReadOnlySpan)_values).SequenceEqual(other._values); - - public override bool Equals(object? obj) - => obj is ImmutableEquatableArray other && Equals(other); - - public override int GetHashCode() - { - int hash = 0; - foreach (T value in _values) - { - hash = HashHelpers.Combine(hash, value is null ? 0 : value.GetHashCode()); - } - - return hash; - } - - public Enumerator GetEnumerator() => new Enumerator(_values); - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_values).GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); - - public struct Enumerator - { - private readonly T[] _values; - private int _index; - - internal Enumerator(T[] values) - { - _values = values; - _index = -1; - } - - public bool MoveNext() - { - int newIndex = _index + 1; - - if ((uint)newIndex < (uint)_values.Length) - { - _index = newIndex; - return true; - } - - return false; - } - - public readonly T Current => _values[_index]; - } - } - - public static class ImmutableEquatableArray - { - public static ImmutableEquatableArray Empty() where T : IEquatable - => ImmutableEquatableArray.Empty; - - public static ImmutableEquatableArray ToImmutableEquatableArray(this IEnumerable values) where T : IEquatable - => new(values); - - public static ImmutableEquatableArray Create(params T[] values) where T : IEquatable - => values is { Length: > 0 } ? new(values) : ImmutableEquatableArray.Empty; - } -} diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 49ee900b6b712..4396bd1f349d5 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -61,12 +61,7 @@ public void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location location = _contextClassLocation; } - Diagnostics.Add(new DiagnosticInfo - { - Descriptor = descriptor, - Location = location.GetTrimmedLocation(), - MessageArgs = messageArgs ?? Array.Empty(), - }); + Diagnostics.Add(DiagnosticInfo.Create(descriptor, location, messageArgs ?? Array.Empty())); } public Parser(KnownTypeSymbols knownSymbols) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs index 37fb04bafc38a..3fd7785a1b27a 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using SourceGenerators; #pragma warning disable RS1035 // IIncrementalGenerator isn't available for the target configuration diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs index e3f8b4aacf6c5..447f54c7f0782 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs @@ -9,6 +9,7 @@ #if !ROSLYN4_4_OR_GREATER using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; #endif +using SourceGenerators; namespace System.Text.Json.SourceGeneration { diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets index 87c060388ad19..23add6278d7c0 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets @@ -30,6 +30,8 @@ + + @@ -55,9 +57,7 @@ - - From 1b11a800fc8fc490a64cb4234993b7203e65b8f6 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Mon, 25 Sep 2023 08:56:15 -0700 Subject: [PATCH 09/11] Refactor member access expr parsing to indicate assumptions --- .../gen/ConfigurationBindingGenerator.Parser.cs | 1 - .../gen/Specs/InterceptorInfo.cs | 10 +++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) 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 f2ec002bb3234..1f5f5beca2a2d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs index a9a28632db736..2854ae3643af0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -170,7 +171,14 @@ public sealed record InvocationLocationInfo { public InvocationLocationInfo(MethodsToGen interceptor, IInvocationOperation invocation) { - MemberAccessExpressionSyntax memberAccessExprSyntax = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)invocation.Syntax).Expression); + Debug.Assert(BinderInvocation.IsBindingOperation(invocation)); + + if (invocation.Syntax is not InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccessExprSyntax }) + { + const string InvalidInvocationErrMsg = "The invocation should have been validated upstream when selecting invocations to emit interceptors for."; + throw new ArgumentException(InvalidInvocationErrMsg, nameof(invocation)); + } + SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; TextSpan memberNameSpan = memberAccessExprSyntax.Name.Span; FileLinePositionSpan linePosSpan = operationSyntaxTree.GetLineSpan(memberNameSpan); From 10f2579be491b541ad50820ae04c3a93cec80a50 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Mon, 25 Sep 2023 10:13:32 -0700 Subject: [PATCH 10/11] Address feedback & do misc clean up --- .../src/Roslyn/DiagnosticDescriptorHelper.cs | 2 + .../ImmutableEquatableArray.cs | 3 - .../src/SourceGenerators/TypeModelHelper.cs | 55 ------------- ...rosoft.Extensions.Configuration.Binder.sln | 66 +++++++-------- .../ConfigurationBindingGenerator.Parser.cs | 29 ++++--- ...nfiguration.Binder.SourceGeneration.csproj | 1 - .../gen/Parser/Extensions.cs | 49 ++++++++++- .../gen/Parser/Helpers.cs | 81 ------------------- .../gen/Specs/InterceptorInfo.cs | 16 +--- .../ConfigBindingGenTestDriver.cs | 5 +- .../GeneratorTests.Baselines.cs | 4 +- .../GeneratorTests.Helpers.cs | 9 ++- .../GeneratorTests.Incremental.cs | 2 +- .../SourceGenerationTests/GeneratorTests.cs | 13 +-- .../gen/Helpers/RoslynExtensions.cs | 55 +++++++++++++ 15 files changed, 174 insertions(+), 216 deletions(-) delete mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Helpers.cs diff --git a/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs b/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs index 07a3fbf52bb0b..8c5783ff9bd0b 100644 --- a/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.cs +++ b/src/libraries/Common/src/Roslyn/DiagnosticDescriptorHelper.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 Microsoft.CodeAnalysis; + namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions { internal static partial class DiagnosticDescriptorHelper diff --git a/src/libraries/Common/src/SourceGenerators/ImmutableEquatableArray.cs b/src/libraries/Common/src/SourceGenerators/ImmutableEquatableArray.cs index f1ab196357518..47fdde1751882 100644 --- a/src/libraries/Common/src/SourceGenerators/ImmutableEquatableArray.cs +++ b/src/libraries/Common/src/SourceGenerators/ImmutableEquatableArray.cs @@ -75,9 +75,6 @@ public bool MoveNext() internal static class ImmutableEquatableArray { - public static ImmutableEquatableArray Empty() where T : IEquatable - => ImmutableEquatableArray.Empty; - public static ImmutableEquatableArray ToImmutableEquatableArray(this IEnumerable values) where T : IEquatable => new(values); } diff --git a/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs b/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs index 1c00634fc48d3..7a3a3e98fd7fd 100644 --- a/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs +++ b/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs @@ -36,60 +36,5 @@ void TraverseContainingTypes(INamedTypeSymbol current) } public static string GetFullyQualifiedName(this ITypeSymbol type) => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - - /// - /// Removes any type metadata that is erased at compile time, such as NRT annotations and tuple labels. - /// - public static ITypeSymbol EraseCompileTimeMetadata(this Compilation compilation, ITypeSymbol type) - { - if (type.NullableAnnotation is NullableAnnotation.Annotated) - { - type = type.WithNullableAnnotation(NullableAnnotation.None); - } - - if (type is INamedTypeSymbol namedType) - { - if (namedType.IsTupleType) - { - if (namedType.TupleElements.Length < 2) - { - return type; - } - - ImmutableArray erasedElements = namedType.TupleElements - .Select(e => compilation.EraseCompileTimeMetadata(e.Type)) - .ToImmutableArray(); - - type = compilation.CreateTupleTypeSymbol(erasedElements); - } - else if (namedType.IsGenericType) - { - if (namedType.IsUnboundGenericType) - { - return namedType; - } - - ImmutableArray typeArguments = namedType.TypeArguments; - INamedTypeSymbol? containingType = namedType.ContainingType; - - if (containingType?.IsGenericType == true) - { - containingType = (INamedTypeSymbol)compilation.EraseCompileTimeMetadata(containingType); - type = namedType = containingType.GetTypeMembers().First(t => t.Name == namedType.Name && t.Arity == namedType.Arity); - } - - if (typeArguments.Length > 0) - { - ITypeSymbol[] erasedTypeArgs = typeArguments - .Select(compilation.EraseCompileTimeMetadata) - .ToArray(); - - type = namedType.ConstructedFrom.Construct(erasedTypeArgs); - } - } - } - - return type; - } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/Microsoft.Extensions.Configuration.Binder.sln b/src/libraries/Microsoft.Extensions.Configuration.Binder/Microsoft.Extensions.Configuration.Binder.sln index d4eb7fe34621a..ac38e3aec99f6 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/Microsoft.Extensions.Configuration.Binder.sln +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/Microsoft.Extensions.Configuration.Binder.sln @@ -1,8 +1,4 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34004.107 -MinimumVisualStudioVersion = 10.0.40219.1 +Microsoft Visual Studio Solution File, Format Version 12.00 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{5FB89358-3575-45AA-ACFE-EF3598B9AB7E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.AsyncInterfaces", "..\Microsoft.Bcl.AsyncInterfaces\ref\Microsoft.Bcl.AsyncInterfaces.csproj", "{64CB04AC-E5B9-4FB8-A1AF-636A6DF23829}" @@ -115,11 +111,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{94EEF122-C30 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{CC3961B0-C62D-44B9-91DB-11D94A3F91A5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{04FEFFC9-5F1D-47CA-885C-3D7F9643D63D}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "tools\gen", "{04FEFFC9-5F1D-47CA-885C-3D7F9643D63D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{948EC8B5-A259-4D8C-B911-EDE19F5BAE40}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "tools\src", "{948EC8B5-A259-4D8C-B911-EDE19F5BAE40}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{8F2204E7-8124-4F7C-B663-8731C7959001}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{8F2204E7-8124-4F7C-B663-8731C7959001}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{B343C975-AC74-4843-899C-623B6E7A6321}" EndProject @@ -343,66 +339,62 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {5FB89358-3575-45AA-ACFE-EF3598B9AB7E} = {AAE738F3-89AC-4406-B1D9-A61A2C3A1CF0} + {56F4D38E-41A0-45E2-9F04-9E670D002E71} = {AAE738F3-89AC-4406-B1D9-A61A2C3A1CF0} + {75BA0154-A24D-421E-9046-C7949DF12A55} = {AAE738F3-89AC-4406-B1D9-A61A2C3A1CF0} {64CB04AC-E5B9-4FB8-A1AF-636A6DF23829} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {3E443131-1C04-48F8-9635-99ABD9DA0E6A} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {CB09105A-F475-4A91-8836-434FA175F4F9} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {958E5946-FD21-4167-B471-8CEC2CB8D75D} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {D4B3EEA1-7394-49EA-A088-897C0CD26D11} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {F05212B2-FD66-4E8E-AEA0-FAC06A0D6808} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {8C7443D8-864A-4DAE-8835-108580C289F5} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {56F4D38E-41A0-45E2-9F04-9E670D002E71} = {AAE738F3-89AC-4406-B1D9-A61A2C3A1CF0} - {75BA0154-A24D-421E-9046-C7949DF12A55} = {AAE738F3-89AC-4406-B1D9-A61A2C3A1CF0} {CB5C87BF-7E0D-4BAC-86BB-06FFC7D1CEE1} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {C5073E23-D3CD-43D3-9AE3-87E570B1B930} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {A519F26A-3187-40C3-BBDC-FD22EE1FC47B} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {45DB88E7-487F-49F2-A309-64315FB218B8} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {39D99379-E744-4295-9CA8-B5C6DE286EA0} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {5177A566-05AF-4DF0-93CC-D2876F7E6EBB} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {CB1C57BA-64C0-4626-A156-4E89A1995C31} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {56C28B9C-C3FA-4A22-A304-1549F681CE7F} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {A9D0ED56-4B34-4010-A5C3-4E36EE8E9FDF} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {7F71CC3D-D4DA-42C4-8470-72C22806FD89} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {42DAD2C1-0FD4-442E-B218-9CDACE6EEB01} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {091844B8-A79E-4DF4-9A73-B2F01968E982} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {A86A8B8E-CC45-46F4-BB95-29410AA7B6C2} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {7F2A8B79-8E07-4D70-A7A3-144E71647110} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {98760FB8-A548-4BEE-914B-4D5D0D13FA09} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {6C8B4068-73D3-47A0-B9A7-840475175FC6} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {FB70235B-6F82-4495-9011-183E65C78CCF} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {856C015E-982A-4834-88F7-3F8FFD33981C} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} - {F132C649-6660-4583-8AD1-36D38711A994} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {2D5B1B6B-1557-4A14-BC00-C36A21BC97C4} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {78D796C3-227B-4648-9BCB-2A000852B2F7} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {3C288A6A-17C9-40A9-A1AA-E0FC3927DFE3} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {9B21B87F-084B-411B-A513-C22B5B961BF3} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {9EB52984-66B5-40C4-8BE7-D75CA612948F} = {F43534CA-C419-405E-B239-CDE2BDC703BE} {4A94F874-A405-4B2E-912B-81887987103B} = {F43534CA-C419-405E-B239-CDE2BDC703BE} {88A22638-3937-4AD2-965B-0A69B616F41F} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {9E2C43CF-2E01-4570-8C02-F678607DDAFF} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} - {05B7F752-4991-4DC8-9B06-8269211E7817} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} - {6E58AF2F-AB35-4279-9135-67E97BCE1432} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {ACD597F0-0A1A-476A-8AE5-2FB6A0039ACD} = {F43534CA-C419-405E-B239-CDE2BDC703BE} {71F7FC59-D0B0-459C-9AFB-0A41B7B73E4C} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {DA0EC3F6-1D31-41DA-B2A5-CEC8D32E1023} = {F43534CA-C419-405E-B239-CDE2BDC703BE} + {3E443131-1C04-48F8-9635-99ABD9DA0E6A} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {958E5946-FD21-4167-B471-8CEC2CB8D75D} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {8C7443D8-864A-4DAE-8835-108580C289F5} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {C5073E23-D3CD-43D3-9AE3-87E570B1B930} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {45DB88E7-487F-49F2-A309-64315FB218B8} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {5177A566-05AF-4DF0-93CC-D2876F7E6EBB} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {56C28B9C-C3FA-4A22-A304-1549F681CE7F} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {7F71CC3D-D4DA-42C4-8470-72C22806FD89} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {091844B8-A79E-4DF4-9A73-B2F01968E982} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {7F2A8B79-8E07-4D70-A7A3-144E71647110} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {6C8B4068-73D3-47A0-B9A7-840475175FC6} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {856C015E-982A-4834-88F7-3F8FFD33981C} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {78D796C3-227B-4648-9BCB-2A000852B2F7} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {9B21B87F-084B-411B-A513-C22B5B961BF3} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {5DF5B4A7-D78A-400E-A49E-54F33CA51006} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {94C9AA55-C6AD-41CC-B6C8-9186C27A6FFE} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} + {D4B3EEA1-7394-49EA-A088-897C0CD26D11} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} + {F132C649-6660-4583-8AD1-36D38711A994} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} + {9E2C43CF-2E01-4570-8C02-F678607DDAFF} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} + {05B7F752-4991-4DC8-9B06-8269211E7817} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} + {6E58AF2F-AB35-4279-9135-67E97BCE1432} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {D2FE6BEA-70F5-4AE3-8D29-DB6568A9DE49} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {47E22F0C-5F36-450B-8020-B83B36936597} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} {4A2E7449-5480-4872-91B3-B5A479D6A787} = {CC3961B0-C62D-44B9-91DB-11D94A3F91A5} - {DA0EC3F6-1D31-41DA-B2A5-CEC8D32E1023} = {F43534CA-C419-405E-B239-CDE2BDC703BE} - {94C9AA55-C6AD-41CC-B6C8-9186C27A6FFE} = {94EEF122-C307-4BF0-88FE-263B89B59F9F} {AA7E297B-0BD9-4E6B-96FE-BC69FC65DB64} = {04FEFFC9-5F1D-47CA-885C-3D7F9643D63D} {D187AB48-A05A-4C8C-900D-7F2A3D87769A} = {04FEFFC9-5F1D-47CA-885C-3D7F9643D63D} + {04FEFFC9-5F1D-47CA-885C-3D7F9643D63D} = {B343C975-AC74-4843-899C-623B6E7A6321} {44059045-A065-4123-AF8C-107D06782422} = {948EC8B5-A259-4D8C-B911-EDE19F5BAE40} {EDE541FC-0F9D-4D3E-A513-7E74788B8C0B} = {948EC8B5-A259-4D8C-B911-EDE19F5BAE40} - {10106702-31E6-4D1A-BDE0-BB8F9F6C258D} = {8F2204E7-8124-4F7C-B663-8731C7959001} - {04FEFFC9-5F1D-47CA-885C-3D7F9643D63D} = {B343C975-AC74-4843-899C-623B6E7A6321} {948EC8B5-A259-4D8C-B911-EDE19F5BAE40} = {B343C975-AC74-4843-899C-623B6E7A6321} + {10106702-31E6-4D1A-BDE0-BB8F9F6C258D} = {8F2204E7-8124-4F7C-B663-8731C7959001} {8F2204E7-8124-4F7C-B663-8731C7959001} = {B343C975-AC74-4843-899C-623B6E7A6321} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A97DC4BF-32F0-46E8-B91C-84D1E7F2A27E} EndGlobalSection - GlobalSection(SharedMSBuildProjectFiles) = preSolution - ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{d187ab48-a05a-4c8c-900d-7f2a3d87769a}*SharedItemsImports = 5 - ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{ede541fc-0f9d-4d3e-a513-7e74788b8c0b}*SharedItemsImports = 5 - EndGlobalSection EndGlobal 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 1f5f5beca2a2d..c38172cb97a32 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -54,6 +54,21 @@ internal sealed partial class Parser(CompilationData compilationData) }; } + private bool IsValidRootConfigType([NotNullWhen(true)] ITypeSymbol? type) + { + if (type is null || + type.SpecialType is SpecialType.System_Object or SpecialType.System_Void || + !_typeSymbols.Compilation.IsSymbolAccessibleWithin(type, _typeSymbols.Compilation.Assembly) || + type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || + type.IsRefLikeType || + ContainsGenericParameters(type)) + { + return false; + } + + return true; + } + private void ParseInvocations(ImmutableArray invocations) { foreach (BinderInvocation? invocation in invocations) @@ -120,21 +135,15 @@ private void RegisterInterceptors() } } - private void EnqueueTargetTypeForRootInvocation(ITypeSymbol? typeSymbol, MethodsToGen bindingOverload, BinderInvocation binderInvocation) + private void EnqueueTargetTypeForRootInvocation(ITypeSymbol? typeSymbol, MethodsToGen overload, BinderInvocation invocation) { if (!IsValidRootConfigType(typeSymbol)) { - RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, binderInvocation.Location); + RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location); } else { - TypeParseInfo typeParseInfo = new TypeParseInfo(typeSymbol, _typeSymbols.Compilation) - { - BindingOverload = bindingOverload, - BinderInvocation = binderInvocation, - ContainingTypeDiagnosticInfo = null, - }; - + TypeParseInfo typeParseInfo = TypeParseInfo.Create(typeSymbol, overload, invocation, containingTypeDiagInfo: null); _typesToParse.Enqueue(typeParseInfo); _invocationTypeParseInfo.Add(typeParseInfo); } @@ -142,7 +151,7 @@ private void EnqueueTargetTypeForRootInvocation(ITypeSymbol? typeSymbol, Methods private TypeRef EnqueueTransitiveType(TypeParseInfo containingTypeParseInfo, ITypeSymbol memberTypeSymbol, DiagnosticDescriptor diagDescriptor, string? memberName = null) { - TypeParseInfo memberTypeParseInfo = containingTypeParseInfo.ToTransitiveTypeParseInfo(memberTypeSymbol, _typeSymbols.Compilation, diagDescriptor, memberName); + TypeParseInfo memberTypeParseInfo = containingTypeParseInfo.ToTransitiveTypeParseInfo(memberTypeSymbol, diagDescriptor, memberName); if (_createdTypeSpecs.TryGetValue(memberTypeSymbol, out TypeSpec? memberTypeSpec)) { 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 7988fb31cdb6a..b66741549b458 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 @@ -47,7 +47,6 @@ - diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs index 54750fee3903b..5e6c501e952d8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs @@ -8,6 +8,52 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { + public sealed partial class ConfigurationBindingGenerator + { + internal sealed partial class Parser + { + private readonly struct TypeParseInfo + { + public ITypeSymbol TypeSymbol { get; private init; } + public string TypeName { get; private init; } + public MethodsToGen BindingOverload { get; private init; } + public BinderInvocation BinderInvocation { get; private init; } + public ContainingTypeDiagnosticInfo? ContainingTypeDiagnosticInfo { get; private init; } + + public static TypeParseInfo Create(ITypeSymbol typeSymbol, MethodsToGen overload, BinderInvocation invocation, ContainingTypeDiagnosticInfo? containingTypeDiagInfo = null) => + new TypeParseInfo + { + TypeSymbol = typeSymbol, + TypeName = typeSymbol.GetName(), + BindingOverload = overload, + BinderInvocation = invocation, + ContainingTypeDiagnosticInfo = containingTypeDiagInfo, + }; + + public TypeParseInfo ToTransitiveTypeParseInfo(ITypeSymbol memberType, DiagnosticDescriptor? diagDescriptor = null, string? memberName = null) + { + ContainingTypeDiagnosticInfo? diagnosticInfo = diagDescriptor is null + ? null + : new() + { + TypeName = TypeName, + Descriptor = diagDescriptor, + MemberName = memberName, + }; + + return Create(memberType, BindingOverload, BinderInvocation, diagnosticInfo); + } + } + + private readonly struct ContainingTypeDiagnosticInfo + { + public required string TypeName { get; init; } + public required string? MemberName { get; init; } + public required DiagnosticDescriptor Descriptor { get; init; } + } + } + } + internal static class ParserExtensions { private static readonly SymbolDisplayFormat s_identifierCompatibleFormat = new SymbolDisplayFormat( @@ -67,8 +113,7 @@ public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type) public static (string? Namespace, string DisplayString, string Name) GetTypeName(this ITypeSymbol type) { - string? @namespace = type.ContainingNamespace?.ToDisplayString(); - @namespace = @namespace is not "" ? @namespace : null; + string? @namespace = type.ContainingNamespace is { IsGlobalNamespace: false } containingNamespace ? containingNamespace.ToDisplayString() : null; string displayString = type.ToDisplayString(s_minimalDisplayFormat); string name = (@namespace is null ? string.Empty : @namespace + ".") + displayString.Replace(".", "+"); return (@namespace, displayString, name); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Helpers.cs deleted file mode 100644 index 3f3eba9363e7f..0000000000000 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Helpers.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis; - -namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration -{ - public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerator - { - internal sealed partial class Parser - { - private readonly struct InvocationDiagnosticInfo - { - public InvocationDiagnosticInfo(DiagnosticDescriptor descriptor, object[]? messageArgs) => - (Descriptor, MessageArgs) = (descriptor, messageArgs); - - public DiagnosticDescriptor Descriptor { get; } - public object[]? MessageArgs { get; } - } - - private readonly struct TypeParseInfo - { - public ITypeSymbol TypeSymbol { get; } - public required MethodsToGen BindingOverload { get; init; } - public required BinderInvocation BinderInvocation { get; init; } - public required ContainingTypeDiagnosticInfo? ContainingTypeDiagnosticInfo { get; init; } - - public TypeParseInfo(ITypeSymbol type, Compilation compilation) - { - Debug.Assert(compilation is not null); - // Trim compile-time erased metadata such as tuple labels and NRT annotations. - //TypeSymbol = compilation.EraseCompileTimeMetadata(type); - TypeSymbol = type; - } - - public TypeParseInfo ToTransitiveTypeParseInfo(ITypeSymbol memberType, Compilation compilation, DiagnosticDescriptor? diagDescriptor = null, string? memberName = null) - { - ContainingTypeDiagnosticInfo? diagnosticInfo = diagDescriptor is null - ? null - : new ContainingTypeDiagnosticInfo - { - TypeName = TypeSymbol.GetTypeName().Name, - Descriptor = diagDescriptor, - MemberName = memberName, - }; - - return new TypeParseInfo(memberType, compilation) - { - BindingOverload = BindingOverload, - BinderInvocation = BinderInvocation, - ContainingTypeDiagnosticInfo = diagnosticInfo, - }; - } - } - - private readonly struct ContainingTypeDiagnosticInfo - { - public required string TypeName { get; init; } - public required string? MemberName { get; init; } - public required DiagnosticDescriptor Descriptor { get; init; } - } - - private bool IsValidRootConfigType([NotNullWhen(true)] ITypeSymbol? type) - { - if (type is null || - type.SpecialType is SpecialType.System_Object or SpecialType.System_Void || - !_typeSymbols.Compilation.IsSymbolAccessibleWithin(type, _typeSymbols.Compilation.Assembly) || - type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || - type.IsRefLikeType || - ContainsGenericParameters(type)) - { - return false; - } - - return true; - } - } - } -} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs index 2854ae3643af0..7d64cffab99a3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs @@ -144,14 +144,8 @@ public void RegisterInterceptor(MethodsToGen overload, ComplexTypeSpec type, IIn .ToImmutableEquatableArray(); } - public sealed record TypedInterceptorInvocationInfo + public sealed record TypedInterceptorInvocationInfo(ComplexTypeSpec TargetType, ImmutableEquatableArray Locations) { - public required ComplexTypeSpec TargetType { get; init; } - public required ImmutableEquatableArray Locations { get; init; } - - public void Deconstruct(out ComplexTypeSpec targetType, out ImmutableEquatableArray locations) => - (targetType, locations) = (TargetType, Locations); - public sealed class Builder(MethodsToGen Overload, ComplexTypeSpec TargetType) { private readonly List _infoList = new(); @@ -159,11 +153,9 @@ public sealed class Builder(MethodsToGen Overload, ComplexTypeSpec TargetType) public void RegisterInvocation(IInvocationOperation invocation) => _infoList.Add(new InvocationLocationInfo(Overload, invocation)); - public TypedInterceptorInvocationInfo ToIncrementalValue() => new() - { - TargetType = TargetType, - Locations = _infoList.OrderBy(i => i.FilePath).ToImmutableEquatableArray() - }; + public TypedInterceptorInvocationInfo ToIncrementalValue() => new( + TargetType, + Locations: _infoList.OrderBy(i => i.FilePath).ToImmutableEquatableArray()); } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs index 4dec96ac51546..ef2765c28a33e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs @@ -57,13 +57,12 @@ public async Task RunGeneratorAndUpdateCompilation(st _generatorDriver = _generatorDriver.RunGeneratorsAndUpdateCompilation(_compilation, out Compilation outputCompilation, out _, CancellationToken.None); GeneratorDriverRunResult runResult = _generatorDriver.GetRunResult(); - //_compilation = outputCompilation; return new ConfigBindingGenRunResult { OutputCompilation = outputCompilation, Diagnostics = runResult.Diagnostics, - GeneratedSources = runResult.Results[0].GeneratedSources, + GeneratedSource = runResult.Results[0].GeneratedSources is { Length: not 0 } sources ? sources[0] : null, TrackedSteps = runResult.Results[0].TrackedSteps[ConfigurationBindingGenerator.GenSpecTrackingName], GenerationSpec = _genSpec }; @@ -96,7 +95,7 @@ internal struct ConfigBindingGenRunResult { public required Compilation OutputCompilation { get; init; } - public required ImmutableArray GeneratedSources { get; init; } + public required GeneratedSourceResult? GeneratedSource { get; init; } /// /// Diagnostics produced by the generator alone. Doesn't include any from other build participants. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs index 5b4492da74eb8..01acddd49c8f3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs @@ -141,7 +141,7 @@ public static void Main() public class MyClass { - public string MyString { get; set; } + public string? MyString { get; set; } public int MyInt { get; set; } public List MyList { get; set; } public Dictionary MyDictionary { get; set; } @@ -655,7 +655,7 @@ public class MyClass2 ; ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source); - Assert.Empty(result.GeneratedSources); + Assert.False(result.GeneratedSource.HasValue); Assert.Empty(result.Diagnostics); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs index 1b54b996c4b00..6ad0ccdc6afa6 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs @@ -91,9 +91,11 @@ private enum ExtensionClassType private static async Task VerifyThatSourceIsGenerated(string testSourceCode) { ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(testSourceCode); - Assert.Equal(1, result.GeneratedSources.Length); + + GeneratedSourceResult? source = result.GeneratedSource; + Assert.NotNull(source); Assert.Empty(result.Diagnostics); - Assert.True(result.GeneratedSources[0].SourceText.Lines.Count > 10); + Assert.True(source.Value.SourceText.Lines.Count > 10); } private static async Task VerifyAgainstBaselineUsingFile( @@ -112,7 +114,8 @@ private static async Task VerifyAgainstBaselineUsingFile( ConfigBindingGenTestDriver genDriver = new(); ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(testSourceCode); - GeneratedSourceResult generatedSource = Assert.Single(result.GeneratedSources); + Assert.NotNull(result.GeneratedSource); + GeneratedSourceResult generatedSource = result.GeneratedSource.Value; SourceText resultSourceText = generatedSource.SourceText; bool resultEqualsBaseline = RoslynTestUtils.CompareLines(expectedLines, resultSourceText, out string errorMessage); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs index bfcd19000fa8f..68c39100bb7c7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs @@ -9,9 +9,9 @@ namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests { - [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)] public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase { + [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)] public sealed class IncrementalTests { [Fact] diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs index 4fddfd15e82ef..d93607d376399 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs @@ -28,7 +28,7 @@ public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTes public async Task LangVersionMustBeCharp12OrHigher(LanguageVersion langVersion) { ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(BindCallSampleCode, langVersion: langVersion); - Assert.Empty(result.GeneratedSources); + Assert.False(result.GeneratedSource.HasValue); Diagnostic diagnostic = Assert.Single(result.Diagnostics); Assert.True(diagnostic.Id == "SYSLIB1102"); @@ -76,7 +76,7 @@ public record struct MyRecordStruct { } """; ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source); - Assert.Empty(result.GeneratedSources); + Assert.False(result.GeneratedSource.HasValue); Assert.Equal(7, result.Diagnostics.Count()); foreach (Diagnostic diagnostic in result.Diagnostics) @@ -112,7 +112,7 @@ public record struct MyRecordStruct { } """; ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source); - Assert.Empty(result.GeneratedSources); + Assert.False(result.GeneratedSource.HasValue); Assert.Equal(2, result.Diagnostics.Count()); foreach (Diagnostic diagnostic in result.Diagnostics) @@ -164,7 +164,7 @@ public class MyClass { } """; ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source); - Assert.Empty(result.GeneratedSources); + Assert.False(result.GeneratedSource.HasValue); Assert.Equal(6, result.Diagnostics.Count()); foreach (Diagnostic diagnostic in result.Diagnostics) @@ -220,7 +220,8 @@ async Task Test(bool expectOutput) { ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetFilteredAssemblyRefs(exclusions)); Assert.Empty(result.Diagnostics); - Assert.Equal(expectOutput ? 1 : 0, result.GeneratedSources.Length); + Action ValidateSourceResult = expectOutput ? () => Assert.NotNull(result.GeneratedSource) : () => Assert.False(result.GeneratedSource.HasValue); + ValidateSourceResult(); } } @@ -275,7 +276,7 @@ public class AnotherGraphWithUnsupportedMembers """; ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ImmutableArray<>), typeof(Encoding), typeof(JsonSerializer))); - Assert.Single(result.GeneratedSources); + Assert.NotNull(result.GeneratedSource); Assert.True(result.Diagnostics.Any(diag => diag.Id == Diagnostics.TypeNotSupported.Id)); Assert.True(result.Diagnostics.Any(diag => diag.Id == Diagnostics.PropertyNotSupported.Id)); } diff --git a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs index 41aa8616f346e..3f3ecb506fd83 100644 --- a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs @@ -40,6 +40,61 @@ internal static class RoslynExtensions public static bool ContainsLocation(this Compilation compilation, Location location) => location.SourceTree != null && compilation.ContainsSyntaxTree(location.SourceTree); + /// + /// Removes any type metadata that is erased at compile time, such as NRT annotations and tuple labels. + /// + public static ITypeSymbol EraseCompileTimeMetadata(this Compilation compilation, ITypeSymbol type) + { + if (type.NullableAnnotation is NullableAnnotation.Annotated) + { + type = type.WithNullableAnnotation(NullableAnnotation.None); + } + + if (type is INamedTypeSymbol namedType) + { + if (namedType.IsTupleType) + { + if (namedType.TupleElements.Length < 2) + { + return type; + } + + ImmutableArray erasedElements = namedType.TupleElements + .Select(e => compilation.EraseCompileTimeMetadata(e.Type)) + .ToImmutableArray(); + + type = compilation.CreateTupleTypeSymbol(erasedElements); + } + else if (namedType.IsGenericType) + { + if (namedType.IsUnboundGenericType) + { + return namedType; + } + + ImmutableArray typeArguments = namedType.TypeArguments; + INamedTypeSymbol? containingType = namedType.ContainingType; + + if (containingType?.IsGenericType == true) + { + containingType = (INamedTypeSymbol)compilation.EraseCompileTimeMetadata(containingType); + type = namedType = containingType.GetTypeMembers().First(t => t.Name == namedType.Name && t.Arity == namedType.Arity); + } + + if (typeArguments.Length > 0) + { + ITypeSymbol[] erasedTypeArgs = typeArguments + .Select(compilation.EraseCompileTimeMetadata) + .ToArray(); + + type = namedType.ConstructedFrom.Construct(erasedTypeArgs); + } + } + } + + return type; + } + public static bool CanUseDefaultConstructorForDeserialization(this ITypeSymbol type, out IMethodSymbol? constructorInfo) { if (type.IsAbstract || type.TypeKind is TypeKind.Interface || type is not INamedTypeSymbol namedType) From de2e0e57756f9137c8e42822df4f66fd6a0019a3 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Mon, 25 Sep 2023 21:18:47 -0700 Subject: [PATCH 11/11] Adjust model to minimize baseline diff / misc clean up --- .../ConfigurationBindingGenerator.Parser.cs | 63 ++-- .../gen/Emitter/CoreBindingHelpers.cs | 45 ++- .../gen/Parser/ConfigurationBinder.cs | 6 +- .../gen/Parser/Extensions.cs | 4 +- .../gen/Specs/BindingHelperInfo.cs | 260 +++++++------- .../gen/Specs/InterceptorInfo.cs | 23 +- .../gen/Specs/TypeIndex.cs | 45 ++- .../Baselines/Collections.generated.txt | 50 +-- .../ConfigurationBinder/Bind.generated.txt | 103 +++--- .../Bind_Instance.generated.txt | 103 +++--- .../Bind_Instance_BinderOptions.generated.txt | 103 +++--- .../Bind_Key_Instance.generated.txt | 103 +++--- ...ind_ParseTypeFromMethodParam.generated.txt | 1 + .../ConfigurationBinder/Get.generated.txt | 126 +++---- .../GetValue.generated.txt | 36 +- .../GetValue_T_Key.generated.txt | 4 +- .../GetValue_T_Key_DefaultValue.generated.txt | 4 +- .../GetValue_TypeOf_Key.generated.txt | 4 +- .../Get_PrimitivesOnly.generated.txt | 182 ++++++++++ .../ConfigurationBinder/Get_T.generated.txt | 106 +++--- .../Get_T_BinderOptions.generated.txt | 106 +++--- .../Get_TypeOf.generated.txt | 4 +- .../Get_TypeOf_BinderOptions.generated.txt | 4 +- .../Baselines/EmptyConfigType.generated.txt | 15 +- .../BindConfiguration.generated.txt | 48 +-- .../OptionsBuilder/Bind_T.generated.txt | 48 +-- .../Bind_T_BinderOptions.generated.txt | 48 +-- .../Baselines/Primitives.generated.txt | 316 +++++++++--------- .../Configure_T.generated.txt | 108 +++--- .../Configure_T_BinderOptions.generated.txt | 108 +++--- .../Configure_T_name.generated.txt | 108 +++--- ...nfigure_T_name_BinderOptions.generated.txt | 108 +++--- .../ConfigBindingGenTestDriver.cs | 57 +++- .../GeneratorTests.Baselines.cs | 53 ++- .../GeneratorTests.Helpers.cs | 33 +- .../GeneratorTests.Incremental.cs | 10 +- ...ation.Binder.SourceGeneration.Tests.csproj | 4 +- .../gen/JsonSourceGenerator.Parser.cs | 3 +- 38 files changed, 1385 insertions(+), 1167 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_PrimitivesOnly.generated.txt 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 c38172cb97a32..d01c5dbae13f3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -155,11 +155,7 @@ private TypeRef EnqueueTransitiveType(TypeParseInfo containingTypeParseInfo, ITy if (_createdTypeSpecs.TryGetValue(memberTypeSymbol, out TypeSpec? memberTypeSpec)) { - if (memberTypeSpec is UnsupportedTypeSpec unsupportedTypeSpec) - { - RecordDiagnostic(unsupportedTypeSpec, memberTypeParseInfo); - } - + RecordTypeDiagnosticIfRequired(memberTypeParseInfo, memberTypeSpec); return memberTypeSpec.TypeRef; } @@ -208,6 +204,8 @@ private TypeSpec CreateTypeSpec(TypeParseInfo typeParseInfo) spec = CreateUnsupportedTypeSpec(typeParseInfo, NotSupportedReason.UnknownType); } + RecordTypeDiagnosticIfRequired(typeParseInfo, spec); + return spec; } @@ -566,7 +564,7 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) if (initDiagDescriptor is not null) { Debug.Assert(initExceptionMessage is not null); - RecordDiagnostic(typeParseInfo, initDiagDescriptor); + RecordTypeDiagnostic(typeParseInfo, initDiagDescriptor); } Dictionary? properties = null; @@ -652,21 +650,17 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) return new ObjectSpec( typeSymbol, initializationStrategy, - properties: properties?.Values.OrderBy(p => p.Name).ToImmutableEquatableArray(), + properties: properties?.Values.ToImmutableEquatableArray(), constructorParameters: ctorParams?.ToImmutableEquatableArray(), initExceptionMessage); } - private UnsupportedTypeSpec CreateUnsupportedTypeSpec(TypeParseInfo typeParseInfo, NotSupportedReason reason) - { - UnsupportedTypeSpec type = new(typeParseInfo.TypeSymbol) { NotSupportedReason = reason }; - RecordDiagnostic(type, typeParseInfo); - return type; - } - - private UnsupportedTypeSpec CreateUnsupportedCollectionSpec(TypeParseInfo typeParseInfo) + private static UnsupportedTypeSpec CreateUnsupportedCollectionSpec(TypeParseInfo typeParseInfo) => CreateUnsupportedTypeSpec(typeParseInfo, NotSupportedReason.CollectionNotSupported); + private static UnsupportedTypeSpec CreateUnsupportedTypeSpec(TypeParseInfo typeParseInfo, NotSupportedReason reason) => + new(typeParseInfo.TypeSymbol) { NotSupportedReason = reason }; + private bool TryGetElementType(INamedTypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? elementType) { INamedTypeSymbol? candidate = GetInterface(type, _typeSymbols.GenericIEnumerable_Unbound); @@ -803,26 +797,43 @@ private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol key, ITypeSy private static bool IsEnum(ITypeSymbol type) => type is INamedTypeSymbol { EnumUnderlyingType: INamedTypeSymbol { } }; - private void RecordDiagnostic(UnsupportedTypeSpec type, TypeParseInfo typeParseInfo) + private void RecordTypeDiagnosticIfRequired(TypeParseInfo typeParseInfo, TypeSpec typeSpec) { - DiagnosticDescriptor descriptor = DiagnosticDescriptors.GetNotSupportedDescriptor(type.NotSupportedReason); - RecordDiagnostic(typeParseInfo, descriptor); + ContainingTypeDiagnosticInfo? containingTypeDiagInfo = typeParseInfo.ContainingTypeDiagnosticInfo; + + if (typeSpec is UnsupportedTypeSpec unsupportedTypeSpec) + { + DiagnosticDescriptor descriptor = DiagnosticDescriptors.GetNotSupportedDescriptor(unsupportedTypeSpec.NotSupportedReason); + RecordTypeDiagnostic(typeParseInfo, descriptor); + } + else if (containingTypeDiagInfo?.Descriptor == DiagnosticDescriptors.DictionaryKeyNotSupported && + typeSpec is not ParsableFromStringSpec) + { + ReportContainingTypeDiagnosticIfRequired(typeParseInfo); + } } - private void RecordDiagnostic(TypeParseInfo typeParseInfo, DiagnosticDescriptor descriptor) + private void RecordTypeDiagnostic(TypeParseInfo typeParseInfo, DiagnosticDescriptor descriptor) { - string typeName = typeParseInfo.TypeSymbol.GetName(); - Location invocationLocation = typeParseInfo.BinderInvocation.Location; + RecordDiagnostic(descriptor, typeParseInfo.BinderInvocation.Location, new object?[] { typeParseInfo.TypeName }); + ReportContainingTypeDiagnosticIfRequired(typeParseInfo); + } - RecordDiagnostic(descriptor, invocationLocation, new object?[] { typeName }); + private void ReportContainingTypeDiagnosticIfRequired(TypeParseInfo typeParseInfo) + { + ContainingTypeDiagnosticInfo? containingTypeDiagInfo = typeParseInfo.ContainingTypeDiagnosticInfo; - if (typeParseInfo.ContainingTypeDiagnosticInfo is ContainingTypeDiagnosticInfo containingTypeDiagInfo) + while (containingTypeDiagInfo is not null) { + string containingTypeName = containingTypeDiagInfo.TypeName; + object[] messageArgs = containingTypeDiagInfo.MemberName is string memberName - ? new[] { memberName, typeName } - : new[] { typeName }; + ? new[] { memberName, containingTypeName } + : new[] { containingTypeName }; + + RecordDiagnostic(containingTypeDiagInfo.Descriptor, typeParseInfo.BinderInvocation.Location, messageArgs); - RecordDiagnostic(containingTypeDiagInfo.Descriptor, invocationLocation, messageArgs); + containingTypeDiagInfo = containingTypeDiagInfo.ContainingTypeInfo; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs index 73abbf487882c..1e14094c66440 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -77,7 +77,7 @@ private void EmitConfigurationKeyCaches() Debug.Assert(keys is not null); string configKeysSource = string.Join(", ", keys); - string fieldName = GetConfigKeyCacheFieldName(objectType); + string fieldName = TypeIndex.GetConfigKeyCacheFieldName(objectType); _writer.WriteLine($@"private readonly static Lazy<{TypeDisplayString.HashSetOfString}> {fieldName} = new(() => new {TypeDisplayString.HashSetOfString}(StringComparer.OrdinalIgnoreCase) {{ {configKeysSource} }});"); } } @@ -124,7 +124,7 @@ private void EmitGetCoreMethod() useIncrementalStringValueIdentifier: false); } break; - case ConfigurationSectionSpec configurationSectionSpec: + case ConfigurationSectionSpec: { EmitCastToIConfigurationSection(); _writer.WriteLine($"return {Identifier.section};"); @@ -660,7 +660,7 @@ private void EmitPrimitiveParseMethod(ParsableFromStringSpec type) string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.getPath}()}}", $"{{typeof({typeDisplayString})}}"); - EmitStartBlock($"public static {typeDisplayString} {GetParseMethodName(type)}(string {Identifier.value}, Func {Identifier.getPath})"); + EmitStartBlock($"public static {typeDisplayString} {TypeIndex.GetParseMethodName(type)}(string {Identifier.value}, Func {Identifier.getPath})"); EmitEndBlock($$""" try { @@ -677,7 +677,7 @@ private void EmitBindCoreImplForArray(ArraySpec type) { TypeRef elementTypeRef = type.ElementTypeRef; string elementTypeDisplayString = _typeIndex.GetTypeSpec(elementTypeRef).DisplayString; - string tempIdentifier = Identifier.temp; + string tempIdentifier = GetIncrementalIdentifier(Identifier.temp); // Create temp list. _writer.WriteLine($"var {tempIdentifier} = new List<{elementTypeDisplayString}>();"); @@ -721,7 +721,7 @@ private void EmitBindingLogicForEnumerableWithAdd(TypeRef elementTypeRef, string useIncrementalStringValueIdentifier: false); } break; - case ConfigurationSectionSpec configurationSection: + case ConfigurationSectionSpec: { _writer.WriteLine($"{addExpr}({Identifier.section});"); } @@ -772,15 +772,13 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) useIncrementalStringValueIdentifier: false); } break; - case ConfigurationSectionSpec configurationSection: + case ConfigurationSectionSpec: { _writer.WriteLine($"{instanceIdentifier}[{parsedKeyExpr}] = {Identifier.section};"); } break; case ComplexTypeSpec complexElementType: { - Debug.Assert(_typeIndex.CanBindTo(complexElementType.TypeRef)); - if (keyType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) { // Save value to local to avoid parsing twice - during look-up and during add. @@ -816,13 +814,17 @@ void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr) EmitEndBlock(); } - void EmitBindingLogic() => this.EmitBindingLogic( - complexElementType, - Identifier.element, - Identifier.section, - InitializationKind.None, - ValueDefaulting.None, - writeOnSuccess: parsedValueExpr => _writer.WriteLine($"{instanceIdentifier}[{parsedKeyExpr}] = {parsedValueExpr};")); + void EmitBindingLogic() + { + this.EmitBindingLogic( + complexElementType, + Identifier.element, + Identifier.section, + InitializationKind.None, + ValueDefaulting.None); + + _writer.WriteLine($"{instanceIdentifier}[{parsedKeyExpr}] = {Identifier.element};"); + } } break; } @@ -835,7 +837,7 @@ private void EmitBindCoreImplForObject(ObjectSpec type) { Debug.Assert(_typeIndex.HasBindableMembers(type)); - string keyCacheFieldName = GetConfigKeyCacheFieldName(type); + string keyCacheFieldName = TypeIndex.GetConfigKeyCacheFieldName(type); string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.DisplayString}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});"; _writer.WriteLine(validateMethodCallExpr); @@ -1086,7 +1088,7 @@ private void EmitBindingLogic( { StringParsableTypeKind.AssignFromSectionValue => stringValueToParse_Expr, StringParsableTypeKind.Enum => $"ParseEnum<{type.DisplayString}>({stringValueToParse_Expr}, () => {sectionPathExpr})", - _ => $"{GetParseMethodName(type)}({stringValueToParse_Expr}, () => {sectionPathExpr})", + _ => $"{TypeIndex.GetParseMethodName(type)}({stringValueToParse_Expr}, () => {sectionPathExpr})", }; if (!checkForNullSectionValue) @@ -1260,15 +1262,6 @@ private static string GetConditionKindExpr(ref bool isFirstType) return "else if"; } - - private static string GetConfigKeyCacheFieldName(ObjectSpec type) => - $"s_configKeys_{type.IdentifierCompatibleSubstring}"; - - private static string GetParseMethodName(ParsableFromStringSpec type) - { - Debug.Assert(type.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue); - return $"Parse{type.IdentifierCompatibleSubstring}"; - } } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs index 075aefd3367b2..645786e35c1c5 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/ConfigurationBinder.cs @@ -260,7 +260,7 @@ private void RegisterInterceptor_ConfigurationBinder(TypeParseInfo typeParseInfo if ((MethodsToGen.ConfigBinder_Bind & overload) is not 0) { if (typeSpec is ComplexTypeSpec complexTypeSpec && - _helperInfoBuilder!.TryRegisterComplexTypeForMethodGen(complexTypeSpec)) + _helperInfoBuilder!.TryRegisterTransitiveTypesForMethodGen(complexTypeSpec.TypeRef)) { _interceptorInfoBuilder.RegisterInterceptor_ConfigBinder_Bind(overload, complexTypeSpec, invocationOperation); } @@ -271,8 +271,8 @@ private void RegisterInterceptor_ConfigurationBinder(TypeParseInfo typeParseInfo (MethodsToGen.ConfigBinder_GetValue & overload) is not 0); bool registered = (MethodsToGen.ConfigBinder_Get & overload) is not 0 - ? _helperInfoBuilder!.TryRegisterTypeForGetCoreGen(typeSpec) - : _helperInfoBuilder!.TryRegisterTypeForGetValueCoreGen(typeSpec); + ? _helperInfoBuilder!.TryRegisterTypeForGetGen(typeSpec) + : _helperInfoBuilder!.TryRegisterTypeForGetValueGen(typeSpec); if (registered) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs index 5e6c501e952d8..f685842639966 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/Extensions.cs @@ -39,17 +39,19 @@ public TypeParseInfo ToTransitiveTypeParseInfo(ITypeSymbol memberType, Diagnosti TypeName = TypeName, Descriptor = diagDescriptor, MemberName = memberName, + ContainingTypeInfo = ContainingTypeDiagnosticInfo, }; return Create(memberType, BindingOverload, BinderInvocation, diagnosticInfo); } } - private readonly struct ContainingTypeDiagnosticInfo + private sealed class ContainingTypeDiagnosticInfo { public required string TypeName { get; init; } public required string? MemberName { get; init; } public required DiagnosticDescriptor Descriptor { get; init; } + public required ContainingTypeDiagnosticInfo? ContainingTypeInfo { get; init; } } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs index c0cad319a2b20..096c8410717ae 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs @@ -24,6 +24,8 @@ public sealed record BindingHelperInfo internal sealed class Builder(TypeIndex _typeIndex) { + private readonly Dictionary _seenTransitiveTypes = new(); + private MethodsToGen_CoreBindingHelper _methodsToGen; private bool _emitConfigurationKeyCaches; @@ -38,91 +40,6 @@ internal sealed class Builder(TypeIndex _typeIndex) "Microsoft.Extensions.Configuration", }; - public bool TryRegisterComplexTypeForMethodGen(ComplexTypeSpec type) - { - if (!_typeIndex.CanBindTo(type.TypeRef)) - { - return false; - } - - if (type is ObjectSpec { InstantiationStrategy: ObjectInstantiationStrategy.ParameterizedConstructor } objectType - && _typeIndex.CanInstantiate(objectType)) - { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.Initialize, type); - } - else if (type is DictionarySpec { InstantiationStrategy: CollectionInstantiationStrategy.LinqToDictionary }) - { - _namespaces.Add("System.Linq"); - } - - TryRegisterTypeForBindCoreGen(type); - return true; - } - - public bool TryRegisterTypeForBindCoreMainGen(ComplexTypeSpec type) - { - if (_typeIndex.HasBindableMembers(type)) - { - bool bindCoreRegistered = TryRegisterTypeForBindCoreGen(type); - Debug.Assert(bindCoreRegistered); - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreMain, type); - RegisterForGen_AsConfigWithChildrenHelper(); - return true; - } - - return false; - } - - public bool TryRegisterTypeForBindCoreGen(ComplexTypeSpec type) - { - if (_typeIndex.HasBindableMembers(type)) - { - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, type); - - if (type is ObjectSpec) - { - _emitConfigurationKeyCaches = true; - - // List is used in generated code as a temp holder for formatting - // an error for config properties that don't map to object properties. - _namespaces.Add("System.Collections.Generic"); - } - - return true; - } - - return false; - } - - public bool TryRegisterTypeForGetCoreGen(TypeSpec type) - { - if (!_typeIndex.CanBindTo(type.TypeRef)) - { - return false; - } - - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, type); - RegisterForGen_AsConfigWithChildrenHelper(); - - if (type is ComplexTypeSpec complexType) - { - bool registered = TryRegisterComplexTypeForMethodGen(complexType); - Debug.Assert(registered); - } - - return true; - } - - public bool TryRegisterTypeForGetValueCoreGen(TypeSpec type) - { - ParsableFromStringSpec effectiveType = (ParsableFromStringSpec)_typeIndex.GetEffectiveTypeSpec(type); - RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, type); - RegisterStringParsableType(effectiveType); - return true; - } - - public void RegisterNamespace(string @namespace) => _namespaces.Add(@namespace); - public BindingHelperInfo ToIncrementalValue() { return new BindingHelperInfo @@ -158,81 +75,154 @@ public BindingHelperInfo ToIncrementalValue() static ImmutableEquatableArray GetTypesForGen(IEnumerable types) where TSpec : TypeSpec, IEquatable => - types.OrderBy(t => t.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(); + types.ToImmutableEquatableArray(); } - private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type) + public bool TryRegisterTypeForGetGen(TypeSpec type) { - if (!_typesForGen.TryGetValue(method, out HashSet? types)) + if (TryRegisterTransitiveTypesForMethodGen(type.TypeRef)) { - _typesForGen[method] = types = new HashSet(); + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, type); + RegisterForGen_AsConfigWithChildrenHelper(); + return true; } - if (types.Add(type)) + return false; + } + + public bool TryRegisterTypeForGetValueGen(TypeSpec typeSpec) + { + ParsableFromStringSpec effectiveType = (ParsableFromStringSpec)_typeIndex.GetEffectiveTypeSpec(typeSpec); + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetValueCore, typeSpec); + RegisterStringParsableTypeIfApplicable(effectiveType); + return true; + } + + public bool TryRegisterTypeForBindCoreMainGen(ComplexTypeSpec type) + { + if (TryRegisterTransitiveTypesForMethodGen(type.TypeRef)) { - _methodsToGen |= method; + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreMain, type); + RegisterForGen_AsConfigWithChildrenHelper(); + return true; + } - if (type is { Namespace: string @namespace }) + return false; + } + + public bool TryRegisterTransitiveTypesForMethodGen(TypeRef typeRef) + { + return _seenTransitiveTypes.TryGetValue(typeRef, out bool isValid) + ? isValid + : (_seenTransitiveTypes[typeRef] = TryRegisterCore()); + + bool TryRegisterCore() + { + switch (_typeIndex.GetTypeSpec(typeRef)) { - _namespaces.Add(@namespace); + case NullableSpec nullableSpec: + { + return TryRegisterTransitiveTypesForMethodGen(nullableSpec.EffectiveTypeRef); + } + case ParsableFromStringSpec stringParsableSpec: + { + RegisterStringParsableTypeIfApplicable(stringParsableSpec); + return true; + } + case DictionarySpec dictionarySpec: + { + bool shouldRegister = _typeIndex.CanBindTo(typeRef) && + TryRegisterTransitiveTypesForMethodGen(dictionarySpec.KeyTypeRef) && + TryRegisterTransitiveTypesForMethodGen(dictionarySpec.ElementTypeRef) && + TryRegisterTypeForBindCoreGen(dictionarySpec); + + if (shouldRegister && dictionarySpec.InstantiationStrategy is CollectionInstantiationStrategy.LinqToDictionary) + { + _namespaces.Add("System.Linq"); + } + + return shouldRegister; + } + case CollectionSpec collectionSpec: + { + return TryRegisterTransitiveTypesForMethodGen(collectionSpec.ElementTypeRef) && + TryRegisterTypeForBindCoreGen(collectionSpec); + } + case ObjectSpec objectSpec: + { + // Base case to avoid stack overflow for recursive object graphs. + // Register all object types for gen; we need to throw runtime exceptions in some cases. + bool shouldRegister = true; + _seenTransitiveTypes.Add(typeRef, shouldRegister); + + // List is used in generated code as a temp holder for formatting + // an error for config properties that don't map to object properties. + _namespaces.Add("System.Collections.Generic"); + + if (_typeIndex.HasBindableMembers(objectSpec)) + { + foreach (PropertySpec property in objectSpec.Properties!) + { + TryRegisterTransitiveTypesForMethodGen(property.TypeRef); + + if (_typeIndex.GetTypeSpec(property.TypeRef) is ComplexTypeSpec) + { + RegisterForGen_AsConfigWithChildrenHelper(); + } + } + + bool registeredForBindCore = TryRegisterTypeForBindCoreGen(objectSpec); + Debug.Assert(registeredForBindCore); + + if (objectSpec is { InstantiationStrategy: ObjectInstantiationStrategy.ParameterizedConstructor, InitExceptionMessage: null }) + { + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.Initialize, objectSpec); + } + } + + return true; + } + default: + { + return true; + } } + } + } + + public void RegisterNamespace(string @namespace) => _namespaces.Add(@namespace); - RegisterTransitiveTypesForMethodGen(type); + private bool TryRegisterTypeForBindCoreGen(ComplexTypeSpec type) + { + if (_typeIndex.HasBindableMembers(type)) + { + RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, type); + _emitConfigurationKeyCaches = true; + return true; } + + return false; } - private void RegisterTransitiveTypesForMethodGen(TypeSpec typeSpec) + private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type) { - switch (typeSpec) + if (!_typesForGen.TryGetValue(method, out HashSet? types)) { - case NullableSpec nullableTypeSpec: - { - RegisterTransitiveTypesForMethodGen(_typeIndex.GetEffectiveTypeSpec(nullableTypeSpec)); - } - break; - case ArraySpec: - case EnumerableSpec: - { - RegisterComplexTypeForMethodGen(((CollectionSpec)typeSpec).ElementTypeRef); - } - break; - case DictionarySpec dictionaryTypeSpec: - { - RegisterComplexTypeForMethodGen(dictionaryTypeSpec.KeyTypeRef); - RegisterComplexTypeForMethodGen(dictionaryTypeSpec.ElementTypeRef); - } - break; - case ObjectSpec objectType when _typeIndex.HasBindableMembers(objectType): - { - foreach (PropertySpec property in objectType.Properties!) - { - RegisterComplexTypeForMethodGen(property.TypeRef); - } - } - break; + _typesForGen[method] = types = new HashSet(); } - void RegisterComplexTypeForMethodGen(TypeRef transitiveTypeRef) + if (types.Add(type)) { - TypeSpec effectiveTypeSpec = _typeIndex.GetTypeSpec(transitiveTypeRef); + _methodsToGen |= method; - if (effectiveTypeSpec is ParsableFromStringSpec parsableFromStringSpec) - { - RegisterStringParsableType(parsableFromStringSpec); - } - else if (effectiveTypeSpec is ComplexTypeSpec complexEffectiveTypeSpec) + if (type is { Namespace: string @namespace }) { - if (_typeIndex.HasBindableMembers(complexEffectiveTypeSpec)) - { - RegisterForGen_AsConfigWithChildrenHelper(); - } - - TryRegisterComplexTypeForMethodGen(complexEffectiveTypeSpec); + _namespaces.Add(@namespace); } } } - private void RegisterStringParsableType(ParsableFromStringSpec type) + private void RegisterStringParsableTypeIfApplicable(ParsableFromStringSpec type) { if (type.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs index 7d64cffab99a3..999ed6514f99d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs @@ -79,8 +79,11 @@ public void RegisterInterceptor_ConfigBinder_Bind(MethodsToGen overload, Complex MethodsToGen |= overload; - void RegisterInterceptor(ref TypedInterceptorInfoBuildler? infoBuilder) => - (infoBuilder ??= new()).RegisterInterceptor(overload, type, invocation); + void RegisterInterceptor(ref TypedInterceptorInfoBuildler? infoBuilder) + { + infoBuilder ??= new TypedInterceptorInfoBuildler(); + infoBuilder.RegisterInterceptor(overload, type, invocation); + } } public void RegisterInterceptor(MethodsToGen overload, IInvocationOperation operation) @@ -103,8 +106,11 @@ public void RegisterInterceptor(MethodsToGen overload, IInvocationOperation oper MethodsToGen |= overload; - void RegisterInterceptor(ref List? infoList) => - (infoList ??= new()).Add(new InvocationLocationInfo(overload, operation)); + void RegisterInterceptor(ref List? infoList) + { + infoList ??= new List(); + infoList.Add(new InvocationLocationInfo(overload, operation)); + } } public InterceptorInfo ToIncrementalValue() => @@ -112,9 +118,9 @@ public InterceptorInfo ToIncrementalValue() => { MethodsToGen = MethodsToGen, - ConfigBinder = _interceptors_configBinder?.OrderBy(i => i.FilePath).ToImmutableEquatableArray(), - OptionsBuilderExt = _interceptors_OptionsBuilderExt?.OrderBy(i => i.FilePath).ToImmutableEquatableArray(), - ServiceCollectionExt = _interceptors_serviceCollectionExt?.OrderBy(i => i.FilePath).ToImmutableEquatableArray(), + ConfigBinder = _interceptors_configBinder?.ToImmutableEquatableArray(), + OptionsBuilderExt = _interceptors_OptionsBuilderExt?.ToImmutableEquatableArray(), + ServiceCollectionExt = _interceptors_serviceCollectionExt?.ToImmutableEquatableArray(), ConfigBinder_Bind_instance = _configBinder_InfoBuilder_Bind_instance?.ToIncrementalValue(), ConfigBinder_Bind_instance_BinderOptions = _configBinder_InfoBuilder_Bind_instance_BinderOptions?.ToIncrementalValue(), @@ -140,7 +146,6 @@ public void RegisterInterceptor(MethodsToGen overload, ComplexTypeSpec type, IIn public ImmutableEquatableArray? ToIncrementalValue() => _invocationInfoBuilderCache.Values .Select(b => b.ToIncrementalValue()) - .OrderBy(i => i.TargetType.TypeRef.FullyQualifiedName) .ToImmutableEquatableArray(); } @@ -155,7 +160,7 @@ public void RegisterInvocation(IInvocationOperation invocation) => public TypedInterceptorInvocationInfo ToIncrementalValue() => new( TargetType, - Locations: _infoList.OrderBy(i => i.FilePath).ToImmutableEquatableArray()); + Locations: _infoList.ToImmutableEquatableArray()); } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs index 9564008084ab7..5b59577b39292 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs @@ -22,11 +22,9 @@ internal sealed class TypeIndex(IEnumerable typeSpecs) public bool CanInstantiate(ComplexTypeSpec typeSpec) => typeSpec switch { - ObjectSpec objectType => objectType is { InstantiationStrategy: not ObjectInstantiationStrategy.None, InitExceptionMessage: null }, - DictionarySpec dictionaryType => - // Nullable not allowed; that would cause us to emit code that violates dictionary key notnull generic parameter constraint. - GetTypeSpec(dictionaryType.KeyTypeRef) is ParsableFromStringSpec, - CollectionSpec => true, + ObjectSpec objectSpec => objectSpec is { InstantiationStrategy: not ObjectInstantiationStrategy.None, InitExceptionMessage: null }, + DictionarySpec dictionarySpec => KeyIsSupported(dictionarySpec), + CollectionSpec collectionSpec => CanBindTo(collectionSpec.ElementTypeRef), _ => throw new InvalidOperationException(), }; @@ -34,22 +32,25 @@ public bool HasBindableMembers(ComplexTypeSpec typeSpec) => typeSpec switch { ObjectSpec objectSpec => objectSpec.Properties?.Any(ShouldBindTo) is true, - DictionarySpec dictSpec => GetTypeSpec(dictSpec.KeyTypeRef) is ParsableFromStringSpec && CanBindTo(dictSpec.ElementTypeRef), + DictionarySpec dictSpec => KeyIsSupported(dictSpec) && CanBindTo(dictSpec.ElementTypeRef), CollectionSpec collectionSpec => CanBindTo(collectionSpec.ElementTypeRef), _ => throw new InvalidOperationException(), }; public bool ShouldBindTo(PropertySpec property) { - bool isAccessible = property.CanGet || property.CanSet; + TypeSpec propTypeSpec = GetEffectiveTypeSpec(property.TypeRef); + return IsAccessible() && !IsCollectionAndCannotOverride() && !IsDictWithUnsupportedKey(); - bool isCollectionAndCannotOverride = !property.CanSet && - GetEffectiveTypeSpec(property.TypeRef) is CollectionWithCtorInitSpec + bool IsAccessible() => property.CanGet || property.CanSet; + + bool IsDictWithUnsupportedKey() => propTypeSpec is DictionarySpec dictionarySpec && !KeyIsSupported(dictionarySpec); + + bool IsCollectionAndCannotOverride() => !property.CanSet && + propTypeSpec is CollectionWithCtorInitSpec { InstantiationStrategy: CollectionInstantiationStrategy.CopyConstructor or CollectionInstantiationStrategy.LinqToDictionary }; - - return isAccessible && !isCollectionAndCannotOverride; } public TypeSpec GetEffectiveTypeSpec(TypeRef typeRef) @@ -95,5 +96,27 @@ public string GetGenericTypeDisplayString(CollectionWithCtorInitSpec type, Enum string keyTypeDisplayString = GetTypeSpec(((DictionarySpec)type).KeyTypeRef).DisplayString; return $"{proxyTypeNameStr}<{keyTypeDisplayString}, {elementTypeDisplayString}>"; } + + public bool KeyIsSupported(DictionarySpec typeSpec) => + // Only types that are parsable from string are supported. + // Nullable keys not allowed; that would cause us to emit + // code that violates dictionary key notnull constraint. + GetTypeSpec(typeSpec.KeyTypeRef) is ParsableFromStringSpec; + + public static string GetConfigKeyCacheFieldName(ObjectSpec type) => $"s_configKeys_{type.IdentifierCompatibleSubstring}"; + + public static string GetParseMethodName(ParsableFromStringSpec type) + { + Debug.Assert(type.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue); + + string displayString = type.DisplayString; + + string parseMethod = type.StringParsableTypeKind is StringParsableTypeKind.ByteArray + ? "ParseByteArray" + // MinimalDisplayString.Length is certainly > 2. + : $"Parse{(char.ToUpper(displayString[0]) + displayString.Substring(1)).Replace(".", "")}"; + + return parseMethod; + } } } 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 ddd52c68b9989..ea4fba79cbc46 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 @@ -37,7 +37,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClassWithCustomCollections = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "CustomDictionary", "CustomList", "IReadOnlyList", "IReadOnlyDictionary" }); + private readonly static Lazy> s_configKeys_ProgramMyClassWithCustomCollections = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "CustomDictionary", "CustomList", "ICustomDictionary", "ICustomCollection", "IReadOnlyList", "UnsupportedIReadOnlyDictionaryUnsupported", "IReadOnlyDictionary" }); public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) { @@ -85,28 +85,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - - public static void BindCore(IConfiguration configuration, ref ICollection instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance.Add(ParseInt(value, () => section.Path)); - } - } - } - public static void BindCore(IConfiguration configuration, ref IReadOnlyList instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { if (instance is not ICollection temp) @@ -123,28 +101,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = ParseInt(value, () => section.Path); - } - } - } - - public static void BindCore(IConfiguration configuration, ref IDictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) - { - if (section.Value is string value) - { - instance[section.Key] = ParseInt(value, () => section.Path); - } - } - } - public static void BindCore(IConfiguration configuration, ref IReadOnlyDictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { if (instance is not IDictionary temp) @@ -184,7 +140,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (AsConfigWithChildren(configuration.GetSection("IReadOnlyList")) is IConfigurationSection section7) { IReadOnlyList? temp9 = instance.IReadOnlyList; - temp9 = temp9 is null ? new List() : new List(temp9); + temp9 = temp9 is null ? (IReadOnlyList)new List() : (IReadOnlyList)new List(temp9); BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); instance.IReadOnlyList = temp9; } @@ -192,7 +148,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (AsConfigWithChildren(configuration.GetSection("IReadOnlyDictionary")) is IConfigurationSection section10) { IReadOnlyDictionary? temp12 = instance.IReadOnlyDictionary; - temp12 = temp12 is null ? new Dictionary() : temp12.ToDictionary(pair => pair.Key, pair => pair.Value); + temp12 = temp12 is null ? (IReadOnlyDictionary)new Dictionary() : (IReadOnlyDictionary)temp12.ToDictionary(pair => pair.Key, pair => pair.Value); BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); instance.IReadOnlyDictionary = temp12; } 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 97ea6db5885bb..fc0dda4b5b3ae 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 @@ -86,58 +86,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section0) - { - Dictionary? temp2 = instance.MyComplexDictionary; - temp2 ??= new Dictionary(); - BindCore(section0, ref temp2, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp2; - } - - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section3) - { - Dictionary? temp5 = instance.MyDictionary; - temp5 ??= new Dictionary(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp5; - } - - if (configuration["MyInt"] is string value6) - { - instance.MyInt = ParseInt32(value6, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section7) - { - List? temp9 = instance.MyList; - temp9 ??= new List(); - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp9; - } - - if (configuration["MyString"] is string value10) - { - instance.MyString = value10; - } - } - - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + if (section.Value is string value) { - element = new Program.MyClass2(); + instance.Add(ParseInt(value, () => section.Path)); } } } @@ -153,14 +110,58 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { - instance.Add(ParseInt32(value, () => section.Path)); + element = new Program.MyClass2(); } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp10; } } @@ -214,7 +215,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 acf8152bd4ff6..9cbc0a22c5c84 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 @@ -50,58 +50,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section0) - { - Dictionary? temp2 = instance.MyComplexDictionary; - temp2 ??= new Dictionary(); - BindCore(section0, ref temp2, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp2; - } - - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section3) - { - Dictionary? temp5 = instance.MyDictionary; - temp5 ??= new Dictionary(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp5; - } - - if (configuration["MyInt"] is string value6) - { - instance.MyInt = ParseInt32(value6, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section7) - { - List? temp9 = instance.MyList; - temp9 ??= new List(); - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp9; - } - - if (configuration["MyString"] is string value10) - { - instance.MyString = value10; - } - } - - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + if (section.Value is string value) { - element = new Program.MyClass2(); + instance.Add(ParseInt(value, () => section.Path)); } } } @@ -117,14 +74,58 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { - instance.Add(ParseInt32(value, () => section.Path)); + element = new Program.MyClass2(); } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp10; } } @@ -160,7 +161,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 b65e79c6bc99b..f3efa07fc0c5c 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 @@ -50,58 +50,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section0) - { - Dictionary? temp2 = instance.MyComplexDictionary; - temp2 ??= new Dictionary(); - BindCore(section0, ref temp2, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp2; - } - - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section3) - { - Dictionary? temp5 = instance.MyDictionary; - temp5 ??= new Dictionary(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp5; - } - - if (configuration["MyInt"] is string value6) - { - instance.MyInt = ParseInt32(value6, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section7) - { - List? temp9 = instance.MyList; - temp9 ??= new List(); - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp9; - } - - if (configuration["MyString"] is string value10) - { - instance.MyString = value10; - } - } - - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + if (section.Value is string value) { - element = new Program.MyClass2(); + instance.Add(ParseInt(value, () => section.Path)); } } } @@ -117,14 +74,58 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { - instance.Add(ParseInt32(value, () => section.Path)); + element = new Program.MyClass2(); } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp10; } } @@ -178,7 +179,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 2e91c870d1dc7..89b82d31bc19a 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 @@ -50,58 +50,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyComplexDictionary", "MyDictionary", "MyInt", "MyList", "MyString" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" }); - public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section0) - { - Dictionary? temp2 = instance.MyComplexDictionary; - temp2 ??= new Dictionary(); - BindCore(section0, ref temp2, defaultValueIfNotFound: false, binderOptions); - instance.MyComplexDictionary = temp2; - } - - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section3) - { - Dictionary? temp5 = instance.MyDictionary; - temp5 ??= new Dictionary(); - BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp5; - } - - if (configuration["MyInt"] is string value6) - { - instance.MyInt = ParseInt32(value6, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } - - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section7) - { - List? temp9 = instance.MyList; - temp9 ??= new List(); - BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp9; - } - - if (configuration["MyString"] is string value10) - { - instance.MyString = value10; - } - } - - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) + if (section.Value is string value) { - element = new Program.MyClass2(); + instance.Add(ParseInt(value, () => section.Path)); } } } @@ -117,14 +74,58 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { foreach (IConfigurationSection section in configuration.GetChildren()) { - if (section.Value is string value) + if (!(instance.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null)) { - instance.Add(ParseInt32(value, () => section.Path)); + element = new Program.MyClass2(); } + instance[section.Key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value0) + { + instance.MyString = value0; + } + + if (configuration["MyInt"] is string value1) + { + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } + + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + { + List? temp4 = instance.MyList; + temp4 ??= new List(); + BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp4; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) + { + Dictionary? temp7 = instance.MyDictionary; + temp7 ??= new Dictionary(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp7; + } + + if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8) + { + Dictionary? temp10 = instance.MyComplexDictionary; + temp10 ??= new Dictionary(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyComplexDictionary = temp10; } } @@ -160,7 +161,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt index 55998b03bdef0..14753a4a1f8e4 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_ParseTypeFromMethodParam.generated.txt @@ -22,6 +22,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration using Microsoft.Extensions.Configuration; using System; using System.CodeDom.Compiler; + using System.Collections.Generic; using System.Globalization; using System.Runtime.CompilerServices; 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 d41c4adf5479d..b6fb659d544d4 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 @@ -48,7 +48,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyArray", "MyDictionary", "MyInt", "MyList", "MyString" }); + 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" }); public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) @@ -81,100 +81,100 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section2) + foreach (IConfigurationSection section in configuration.GetChildren()) { - int[]? temp4 = instance.MyArray; - temp4 ??= new int[0]; - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp4; + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } } + } - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5) - { - Dictionary? temp7 = instance.MyDictionary; - temp7 ??= new Dictionary(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp7; - } + public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + var temp2 = new List(); - if (configuration["MyInt"] is string value8) - { - instance.MyInt = ParseInt32(value8, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) + foreach (IConfigurationSection section in configuration.GetChildren()) { - instance.MyInt = default; + if (section.Value is string value) + { + temp2.Add(ParseInt(value, () => section.Path)); + } } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section9) - { - List? temp11 = instance.MyList; - temp11 ??= new List(); - BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp11; - } + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp2.Count); + temp2.CopyTo(instance, originalCount); + } - if (configuration["MyString"] is string value12) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) { - instance.MyString = value12; + if (section.Value is string value) + { + instance[section.Key] = value; + } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyInt"] is string value13) + if (configuration["MyString"] is string value3) { - instance.MyInt = ParseInt32(value13, () => configuration.GetSection("MyInt").Path); + instance.MyString = value3; + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) { - if (section.Value is string value) - { - instance[section.Key] = value; - } + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section8) { - if (section.Value is string value) - { - instance.Add(ParseInt32(value, () => section.Path)); - } + int[]? temp10 = instance.MyArray; + temp10 ??= new int[0]; + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp10; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) + { + Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp13; } } - public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - var temp = new List(); + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); - foreach (IConfigurationSection section in configuration.GetChildren()) + if (configuration["MyInt"] is string value14) { - if (section.Value is string value) - { - temp.Add(ParseInt32(value, () => section.Path)); - } + instance.MyInt = ParseInt(value14, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; } - - int originalCount = instance.Length; - Array.Resize(ref instance, originalCount + temp.Count); - temp.CopyTo(instance, originalCount); } @@ -236,7 +236,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 17660edfae642..bf7e64bd31f90 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 @@ -61,9 +61,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - if (type == typeof(bool?)) + if (type == typeof(int)) { - return ParseBoolean(value, () => section.Path); + return ParseInt(value, () => section.Path); + } + else if (type == typeof(bool?)) + { + return ParseBool(value, () => section.Path); } else if (type == typeof(byte[])) { @@ -73,59 +77,55 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { return ParseCultureInfo(value, () => section.Path); } - else if (type == typeof(int)) - { - return ParseInt32(value, () => section.Path); - } return null; } - public static bool ParseBoolean(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return bool.Parse(value); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } - public static byte[] ParseByteArray(string value, Func getPath) + public static bool ParseBool(string value, Func getPath) { try { - return Convert.FromBase64String(value); + return bool.Parse(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); } } - public static CultureInfo ParseCultureInfo(string value, Func getPath) + public static byte[] ParseByteArray(string value, Func getPath) { try { - return CultureInfo.GetCultureInfo(value); + return Convert.FromBase64String(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); } } - public static int ParseInt32(string value, Func getPath) + public static CultureInfo ParseCultureInfo(string value, Func getPath) { try { - return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return CultureInfo.GetCultureInfo(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + 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 4d3bde81dcab3..b86915b78303b 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 @@ -51,13 +51,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (type == typeof(int)) { - return ParseInt32(value, () => section.Path); + return ParseInt(value, () => section.Path); } return null; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 32c192c057089..697f710dff302 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 @@ -51,13 +51,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (type == typeof(int)) { - return ParseInt32(value, () => section.Path); + return ParseInt(value, () => section.Path); } return null; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 cb86c3581112a..b5a22e71ccefb 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 @@ -51,13 +51,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (type == typeof(bool?)) { - return ParseBoolean(value, () => section.Path); + return ParseBool(value, () => section.Path); } return null; } - public static bool ParseBoolean(string value, Func getPath) + public static bool ParseBool(string value, Func getPath) { try { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_PrimitivesOnly.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_PrimitivesOnly.generated.txt new file mode 100644 index 0000000000000..b703fb5f1c864 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_PrimitivesOnly.generated.txt @@ -0,0 +1,182 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + 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 +{ + using Microsoft.Extensions.Configuration; + using System; + using System.CodeDom.Compiler; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the configuration instance to a new instance of type T. + [InterceptsLocation(@"src-0.cs", 10, 16)] + 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. + [InterceptsLocation(@"src-0.cs", 12, 16)] + 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. + [InterceptsLocation(@"src-0.cs", 11, 16)] + 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. + [InterceptsLocation(@"src-0.cs", 13, 16)] + public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + #endregion IConfiguration extensions. + + #region Core binding extensions. + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(int)) + { + if (configuration is not IConfigurationSection section) + { + throw new InvalidOperationException(); + } + if (section.Value is string value) + { + return ParseInt(value, () => section.Path); + } + } + else if (type == typeof(string)) + { + if (configuration is not IConfigurationSection section) + { + throw new InvalidOperationException(); + } + return section.Value; + } + else if (type == typeof(float)) + { + if (configuration is not IConfigurationSection section) + { + throw new InvalidOperationException(); + } + if (section.Value is string value) + { + return ParseFloat(value, () => section.Path); + } + } + else if (type == typeof(double)) + { + if (configuration is not IConfigurationSection section) + { + throw new InvalidOperationException(); + } + if (section.Value is string value) + { + return ParseDouble(value, () => section.Path); + } + } + + throw new NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return AsConfigWithChildren(configuration) is not null; + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + + public static BinderOptions? GetBinderOptions(Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + + if (binderOptions.BindNonPublicProperties) + { + throw new NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + + return binderOptions; + } + + public static int ParseInt(string value, Func getPath) + { + try + { + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + + public static float ParseFloat(string value, Func getPath) + { + try + { + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(float)}'.", exception); + } + } + + public static double ParseDouble(string value, Func getPath) + { + try + { + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(double)}'.", 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 142ff6f4ebe7a..c2e8f167bb475 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 @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyArray", "MyDictionary", "MyInt", "MyList", "MyString" }); + 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) { @@ -62,47 +62,32 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section1) - { - int[]? temp3 = instance.MyArray; - temp3 ??= new int[0]; - BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp3; - } - - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section4) + foreach (IConfigurationSection section in configuration.GetChildren()) { - Dictionary? temp6 = instance.MyDictionary; - temp6 ??= new Dictionary(); - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp6; + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } } + } - if (configuration["MyInt"] is string value7) - { - instance.MyInt = ParseInt32(value7, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } + public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + var temp1 = new List(); - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section8) + foreach (IConfigurationSection section in configuration.GetChildren()) { - List? temp10 = instance.MyList; - temp10 ??= new List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp10; + if (section.Value is string value) + { + temp1.Add(ParseInt(value, () => section.Path)); + } } - if (configuration["MyString"] is string value11) - { - instance.MyString = value11; - } + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp1.Count); + temp1.CopyTo(instance, originalCount); } public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) @@ -116,32 +101,47 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value2) { - if (section.Value is string value) - { - instance.Add(ParseInt32(value, () => section.Path)); - } + instance.MyString = value2; } - } - public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - var temp = new List(); + if (configuration["MyInt"] is string value3) + { + instance.MyInt = ParseInt(value3, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) { - if (section.Value is string value) - { - temp.Add(ParseInt32(value, () => section.Path)); - } + List? temp6 = instance.MyList; + temp6 ??= new List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; } - int originalCount = instance.Length; - Array.Resize(ref instance, originalCount + temp.Count); - temp.CopyTo(instance, originalCount); + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section7) + { + int[]? temp9 = instance.MyArray; + temp9 ??= new int[0]; + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp9; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section10) + { + Dictionary? temp12 = instance.MyDictionary; + temp12 ??= new Dictionary(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp12; + } } @@ -203,7 +203,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 7840028ca3061..cd3f237917d4e 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 @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyArray", "MyDictionary", "MyInt", "MyList", "MyString" }); + 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) { @@ -62,47 +62,32 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section1) - { - int[]? temp3 = instance.MyArray; - temp3 ??= new int[0]; - BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); - instance.MyArray = temp3; - } - - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section4) + foreach (IConfigurationSection section in configuration.GetChildren()) { - Dictionary? temp6 = instance.MyDictionary; - temp6 ??= new Dictionary(); - BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp6; + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } } + } - if (configuration["MyInt"] is string value7) - { - instance.MyInt = ParseInt32(value7, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) - { - instance.MyInt = default; - } + public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + var temp1 = new List(); - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section8) + foreach (IConfigurationSection section in configuration.GetChildren()) { - List? temp10 = instance.MyList; - temp10 ??= new List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp10; + if (section.Value is string value) + { + temp1.Add(ParseInt(value, () => section.Path)); + } } - if (configuration["MyString"] is string value11) - { - instance.MyString = value11; - } + int originalCount = instance.Length; + Array.Resize(ref instance, originalCount + temp1.Count); + temp1.CopyTo(instance, originalCount); } public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) @@ -116,32 +101,47 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - foreach (IConfigurationSection section in configuration.GetChildren()) + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); + + if (configuration["MyString"] is string value2) { - if (section.Value is string value) - { - instance.Add(ParseInt32(value, () => section.Path)); - } + instance.MyString = value2; } - } - public static void BindCore(IConfiguration configuration, ref int[] instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - var temp = new List(); + if (configuration["MyInt"] is string value3) + { + instance.MyInt = ParseInt(value3, () => configuration.GetSection("MyInt").Path); + } + else if (defaultValueIfNotFound) + { + instance.MyInt = default; + } - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section4) { - if (section.Value is string value) - { - temp.Add(ParseInt32(value, () => section.Path)); - } + List? temp6 = instance.MyList; + temp6 ??= new List(); + BindCore(section4, ref temp6, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp6; } - int originalCount = instance.Length; - Array.Resize(ref instance, originalCount + temp.Count); - temp.CopyTo(instance, originalCount); + if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section7) + { + int[]? temp9 = instance.MyArray; + temp9 ??= new int[0]; + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.MyArray = temp9; + } + + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section10) + { + Dictionary? temp12 = instance.MyDictionary; + temp12 ??= new Dictionary(); + BindCore(section10, ref temp12, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp12; + } } @@ -203,7 +203,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 c838ae5aa61de..eca2001123ff0 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 @@ -68,7 +68,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt32(value1, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -135,7 +135,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 f4c9cb643836f..7883f4a50da0f 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 @@ -68,7 +68,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt32(value1, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { @@ -135,7 +135,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt index 73121ed621a24..404ce7561cc47 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt @@ -30,12 +30,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration file static class BindingExtensions { #region IConfiguration extensions. - /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. - [InterceptsLocation(@"src-0.cs", 18, 23)] - public static void Bind_ListAbstractType_CannotInit(this IConfiguration configuration, object? instance) - { - } - /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. [InterceptsLocation(@"src-0.cs", 12, 23)] public static void Bind_TypeWithNoMembers(this IConfiguration configuration, object? instance) @@ -96,6 +90,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } #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 1d84c37f84f39..44f1df2e78232 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 @@ -63,7 +63,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion OptionsBuilder extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt", "MyList", "MyString" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -89,41 +89,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyInt"] is string value1) - { - instance.MyInt = ParseInt32(value1, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) + if (configuration["MyString"] is string value1) { - instance.MyInt = default; + instance.MyString = value1; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + if (configuration["MyInt"] is string value2) { - List? temp4 = instance.MyList; - temp4 ??= new List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + instance.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); } - - if (configuration["MyString"] is string value5) + else if (defaultValueIfNotFound) { - instance.MyString = value5; + instance.MyInt = default; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) { - if (section.Value is string value) - { - instance.Add(ParseInt32(value, () => section.Path)); - } + List? temp5 = instance.MyList; + temp5 ??= new List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } } @@ -186,7 +186,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 6c0903bf75b55..8d7c70a27b3f2 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 @@ -73,7 +73,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt", "MyList", "MyString" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -99,41 +99,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyInt"] is string value1) - { - instance.MyInt = ParseInt32(value1, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) + if (configuration["MyString"] is string value1) { - instance.MyInt = default; + instance.MyString = value1; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + if (configuration["MyInt"] is string value2) { - List? temp4 = instance.MyList; - temp4 ??= new List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + instance.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); } - - if (configuration["MyString"] is string value5) + else if (defaultValueIfNotFound) { - instance.MyString = value5; + instance.MyInt = default; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) { - if (section.Value is string value) - { - instance.Add(ParseInt32(value, () => section.Path)); - } + List? temp5 = instance.MyList; + temp5 ??= new List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } } @@ -196,7 +196,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 dc8932679e3a0..385079af1709a 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 @@ -67,7 +67,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt", "MyList", "MyString" }); + private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -93,41 +93,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } + } + } + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyInt"] is string value1) - { - instance.MyInt = ParseInt32(value1, () => configuration.GetSection("MyInt").Path); - } - else if (defaultValueIfNotFound) + if (configuration["MyString"] is string value1) { - instance.MyInt = default; + instance.MyString = value1; } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2) + if (configuration["MyInt"] is string value2) { - List? temp4 = instance.MyList; - temp4 ??= new List(); - BindCore(section2, ref temp4, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp4; + instance.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path); } - - if (configuration["MyString"] is string value5) + else if (defaultValueIfNotFound) { - instance.MyString = value5; + instance.MyInt = default; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3) { - if (section.Value is string value) - { - instance.Add(ParseInt32(value, () => section.Path)); - } + List? temp5 = instance.MyList; + temp5 ??= new List(); + BindCore(section3, ref temp5, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp5; } } @@ -190,7 +190,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 949d2a46ae273..a8373ca527095 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 @@ -50,7 +50,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IConfiguration extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Prop0", "Prop1", "Prop10", "Prop11", "Prop12", "Prop13", "Prop14", "Prop15", "Prop16", "Prop17", "Prop18", "Prop19", "Prop2", "Prop20", "Prop21", "Prop22", "Prop23", "Prop24", "Prop25", "Prop26", "Prop27", "Prop28", "Prop29", "Prop3", "Prop30", "Prop4", "Prop5", "Prop6", "Prop7", "Prop8", "Prop9" }); + 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", "Prop28", "Prop29", "Prop30" }); public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { @@ -58,7 +58,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration if (configuration["Prop0"] is string value0) { - instance.Prop0 = ParseBoolean(value0, () => configuration.GetSection("Prop0").Path); + instance.Prop0 = ParseBool(value0, () => configuration.GetSection("Prop0").Path); } else if (defaultValueIfNotFound) { @@ -74,241 +74,241 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration instance.Prop1 = default; } - if (configuration["Prop10"] is string value2) + if (configuration["Prop2"] is string value2) { - instance.Prop10 = ParseSingle(value2, () => configuration.GetSection("Prop10").Path); + instance.Prop2 = ParseSbyte(value2, () => configuration.GetSection("Prop2").Path); } else if (defaultValueIfNotFound) { - instance.Prop10 = default; + instance.Prop2 = default; } - if (configuration["Prop11"] is string value3) + if (configuration["Prop3"] is string value3) { - instance.Prop11 = ParseHalf(value3, () => configuration.GetSection("Prop11").Path); + instance.Prop3 = ParseChar(value3, () => configuration.GetSection("Prop3").Path); } else if (defaultValueIfNotFound) { - instance.Prop11 = default; + instance.Prop3 = default; } - if (configuration["Prop12"] is string value4) + if (configuration["Prop4"] is string value4) { - instance.Prop12 = ParseUInt128(value4, () => configuration.GetSection("Prop12").Path); + instance.Prop4 = ParseDouble(value4, () => configuration.GetSection("Prop4").Path); } else if (defaultValueIfNotFound) { - instance.Prop12 = default; + instance.Prop4 = default; } - if (configuration["Prop13"] is string value5) + if (configuration["Prop5"] is string value5) { - instance.Prop13 = ParseUInt16(value5, () => configuration.GetSection("Prop13").Path); + instance.Prop5 = value5; + } + + if (configuration["Prop6"] is string value6) + { + instance.Prop6 = ParseInt(value6, () => configuration.GetSection("Prop6").Path); } else if (defaultValueIfNotFound) { - instance.Prop13 = default; + instance.Prop6 = default; } - if (configuration["Prop14"] is string value6) + if (configuration["Prop8"] is string value7) { - instance.Prop14 = ParseUInt32(value6, () => configuration.GetSection("Prop14").Path); + instance.Prop8 = ParseShort(value7, () => configuration.GetSection("Prop8").Path); } else if (defaultValueIfNotFound) { - instance.Prop14 = default; + instance.Prop8 = default; } - if (configuration["Prop15"] is string value7) + if (configuration["Prop9"] is string value8) { - instance.Prop15 = ParseUInt64(value7, () => configuration.GetSection("Prop15").Path); + instance.Prop9 = ParseLong(value8, () => configuration.GetSection("Prop9").Path); } else if (defaultValueIfNotFound) { - instance.Prop15 = default; + instance.Prop9 = default; } - if (configuration["Prop16"] is string value8) + if (configuration["Prop10"] is string value9) { - instance.Prop16 = value8; + instance.Prop10 = ParseFloat(value9, () => configuration.GetSection("Prop10").Path); } - - if (configuration["Prop17"] is string value9) + else if (defaultValueIfNotFound) { - instance.Prop17 = ParseCultureInfo(value9, () => configuration.GetSection("Prop17").Path); + instance.Prop10 = default; } - if (configuration["Prop18"] is string value10) + if (configuration["Prop13"] is string value10) { - instance.Prop18 = ParseDateOnly(value10, () => configuration.GetSection("Prop18").Path); + instance.Prop13 = ParseUshort(value10, () => configuration.GetSection("Prop13").Path); } else if (defaultValueIfNotFound) { - instance.Prop18 = default; + instance.Prop13 = default; } - if (configuration["Prop19"] is string value11) + if (configuration["Prop14"] is string value11) { - instance.Prop19 = ParseDateTime(value11, () => configuration.GetSection("Prop19").Path); + instance.Prop14 = ParseUint(value11, () => configuration.GetSection("Prop14").Path); } else if (defaultValueIfNotFound) { - instance.Prop19 = default; + instance.Prop14 = default; } - if (configuration["Prop2"] is string value12) + if (configuration["Prop15"] is string value12) { - instance.Prop2 = ParseSByte(value12, () => configuration.GetSection("Prop2").Path); + instance.Prop15 = ParseUlong(value12, () => configuration.GetSection("Prop15").Path); } else if (defaultValueIfNotFound) { - instance.Prop2 = default; + instance.Prop15 = default; + } + + if (configuration["Prop16"] is string value13) + { + instance.Prop16 = value13; } - if (configuration["Prop20"] is string value13) + if (configuration["Prop17"] is string value14) { - instance.Prop20 = ParseDateTimeOffset(value13, () => configuration.GetSection("Prop20").Path); + instance.Prop17 = ParseCultureInfo(value14, () => configuration.GetSection("Prop17").Path); + } + + if (configuration["Prop19"] is string value15) + { + instance.Prop19 = ParseDateTime(value15, () => configuration.GetSection("Prop19").Path); } else if (defaultValueIfNotFound) { - instance.Prop20 = default; + instance.Prop19 = default; } - if (configuration["Prop21"] is string value14) + if (configuration["Prop20"] is string value16) { - instance.Prop21 = ParseDecimal(value14, () => configuration.GetSection("Prop21").Path); + instance.Prop20 = ParseDateTimeOffset(value16, () => configuration.GetSection("Prop20").Path); } else if (defaultValueIfNotFound) { - instance.Prop21 = default; + instance.Prop20 = default; } - if (configuration["Prop22"] is string value15) + if (configuration["Prop21"] is string value17) { - instance.Prop22 = ParseTimeOnly(value15, () => configuration.GetSection("Prop22").Path); + instance.Prop21 = ParseDecimal(value17, () => configuration.GetSection("Prop21").Path); } else if (defaultValueIfNotFound) { - instance.Prop22 = default; + instance.Prop21 = default; } - if (configuration["Prop23"] is string value16) + if (configuration["Prop23"] is string value18) { - instance.Prop23 = ParseTimeSpan(value16, () => configuration.GetSection("Prop23").Path); + instance.Prop23 = ParseTimeSpan(value18, () => configuration.GetSection("Prop23").Path); } else if (defaultValueIfNotFound) { instance.Prop23 = default; } - if (configuration["Prop24"] is string value17) + if (configuration["Prop24"] is string value19) { - instance.Prop24 = ParseGuid(value17, () => configuration.GetSection("Prop24").Path); + instance.Prop24 = ParseGuid(value19, () => configuration.GetSection("Prop24").Path); } else if (defaultValueIfNotFound) { instance.Prop24 = default; } - if (configuration["Prop25"] is string value18) + if (configuration["Prop25"] is string value20) { - instance.Prop25 = ParseUri(value18, () => configuration.GetSection("Prop25").Path); + instance.Prop25 = ParseUri(value20, () => configuration.GetSection("Prop25").Path); } - if (configuration["Prop26"] is string value19) + if (configuration["Prop26"] is string value21) { - instance.Prop26 = ParseVersion(value19, () => configuration.GetSection("Prop26").Path); + instance.Prop26 = ParseVersion(value21, () => configuration.GetSection("Prop26").Path); } - if (configuration["Prop27"] is string value20) + if (configuration["Prop27"] is string value22) { - instance.Prop27 = ParseEnum(value20, () => configuration.GetSection("Prop27").Path); + instance.Prop27 = ParseEnum(value22, () => configuration.GetSection("Prop27").Path); } else if (defaultValueIfNotFound) { instance.Prop27 = default; } - if (configuration["Prop28"] is string value21) - { - instance.Prop28 = ParseByteArray(value21, () => configuration.GetSection("Prop28").Path); - } - - if (configuration["Prop29"] is string value22) + if (configuration["Prop7"] is string value23) { - instance.Prop29 = ParseInt32(value22, () => configuration.GetSection("Prop29").Path); + instance.Prop7 = ParseInt128(value23, () => configuration.GetSection("Prop7").Path); } else if (defaultValueIfNotFound) { - instance.Prop29 = default; + instance.Prop7 = default; } - if (configuration["Prop3"] is string value23) + if (configuration["Prop11"] is string value24) { - instance.Prop3 = ParseChar(value23, () => configuration.GetSection("Prop3").Path); + instance.Prop11 = ParseHalf(value24, () => configuration.GetSection("Prop11").Path); } else if (defaultValueIfNotFound) { - instance.Prop3 = default; + instance.Prop11 = default; } - if (configuration["Prop30"] is string value24) + if (configuration["Prop12"] is string value25) { - instance.Prop30 = ParseDateTime(value24, () => configuration.GetSection("Prop30").Path); + instance.Prop12 = ParseUInt128(value25, () => configuration.GetSection("Prop12").Path); } else if (defaultValueIfNotFound) { - instance.Prop30 = default; + instance.Prop12 = default; } - if (configuration["Prop4"] is string value25) + if (configuration["Prop18"] is string value26) { - instance.Prop4 = ParseDouble(value25, () => configuration.GetSection("Prop4").Path); + instance.Prop18 = ParseDateOnly(value26, () => configuration.GetSection("Prop18").Path); } else if (defaultValueIfNotFound) { - instance.Prop4 = default; - } - - if (configuration["Prop5"] is string value26) - { - instance.Prop5 = value26; + instance.Prop18 = default; } - if (configuration["Prop6"] is string value27) + if (configuration["Prop22"] is string value27) { - instance.Prop6 = ParseInt32(value27, () => configuration.GetSection("Prop6").Path); + instance.Prop22 = ParseTimeOnly(value27, () => configuration.GetSection("Prop22").Path); } else if (defaultValueIfNotFound) { - instance.Prop6 = default; + instance.Prop22 = default; } - if (configuration["Prop7"] is string value28) + if (configuration["Prop28"] is string value28) { - instance.Prop7 = ParseInt128(value28, () => configuration.GetSection("Prop7").Path); - } - else if (defaultValueIfNotFound) - { - instance.Prop7 = default; + instance.Prop28 = ParseByteArray(value28, () => configuration.GetSection("Prop28").Path); } - if (configuration["Prop8"] is string value29) + if (configuration["Prop29"] is string value29) { - instance.Prop8 = ParseInt16(value29, () => configuration.GetSection("Prop8").Path); + instance.Prop29 = ParseInt(value29, () => configuration.GetSection("Prop29").Path); } else if (defaultValueIfNotFound) { - instance.Prop8 = default; + instance.Prop29 = default; } - if (configuration["Prop9"] is string value30) + if (configuration["Prop30"] is string value30) { - instance.Prop9 = ParseInt64(value30, () => configuration.GetSection("Prop9").Path); + instance.Prop30 = ParseDateTime(value30, () => configuration.GetSection("Prop30").Path); } else if (defaultValueIfNotFound) { - instance.Prop9 = default; + instance.Prop30 = default; } } @@ -335,7 +335,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static bool ParseBoolean(string value, Func getPath) + public static bool ParseBool(string value, Func getPath) { try { @@ -359,15 +359,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static byte[] ParseByteArray(string value, Func getPath) + public static sbyte ParseSbyte(string value, Func getPath) { try { - return Convert.FromBase64String(value); + return sbyte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(byte[])}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(sbyte)}'.", exception); } } @@ -383,151 +383,147 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static decimal ParseDecimal(string value, Func getPath) + public static double ParseDouble(string value, Func getPath) { try { - return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(decimal)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(double)}'.", exception); } } - public static double ParseDouble(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { - return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(double)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); } } - public static float ParseSingle(string value, Func getPath) + public static short ParseShort(string value, Func getPath) { try { - return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + return short.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(float)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(short)}'.", exception); } } - public static DateOnly ParseDateOnly(string value, Func getPath) + public static long ParseLong(string value, Func getPath) { try { - return DateOnly.Parse(value, CultureInfo.InvariantCulture); + return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateOnly)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(long)}'.", exception); } } - public static DateTime ParseDateTime(string value, Func getPath) + public static float ParseFloat(string value, Func getPath) { try { - return DateTime.Parse(value, CultureInfo.InvariantCulture); + return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateTime)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(float)}'.", exception); } } - public static DateTimeOffset ParseDateTimeOffset(string value, Func getPath) + public static ushort ParseUshort(string value, Func getPath) { try { - return DateTimeOffset.Parse(value, CultureInfo.InvariantCulture); + return ushort.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateTimeOffset)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ushort)}'.", exception); } } - public static T ParseEnum(string value, Func getPath) where T : struct + public static uint ParseUint(string value, Func getPath) { try { - #if NETFRAMEWORK || NETSTANDARD2_0 - return (T)Enum.Parse(typeof(T), value, ignoreCase: true); - #else - return Enum.Parse(value, ignoreCase: true); - #endif + return uint.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(T)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(uint)}'.", exception); } } - public static CultureInfo ParseCultureInfo(string value, Func getPath) + public static ulong ParseUlong(string value, Func getPath) { try { - return CultureInfo.GetCultureInfo(value); + return ulong.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ulong)}'.", exception); } } - public static Guid ParseGuid(string value, Func getPath) + public static CultureInfo ParseCultureInfo(string value, Func getPath) { try { - return Guid.Parse(value); + return CultureInfo.GetCultureInfo(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Guid)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); } } - public static Half ParseHalf(string value, Func getPath) + public static DateTime ParseDateTime(string value, Func getPath) { try { - return Half.Parse(value, CultureInfo.InvariantCulture); + return DateTime.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Half)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateTime)}'.", exception); } } - public static Int128 ParseInt128(string value, Func getPath) + public static DateTimeOffset ParseDateTimeOffset(string value, Func getPath) { try { - return Int128.Parse(value, CultureInfo.InvariantCulture); + return DateTimeOffset.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Int128)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateTimeOffset)}'.", exception); } } - public static TimeOnly ParseTimeOnly(string value, Func getPath) + public static decimal ParseDecimal(string value, Func getPath) { try { - return TimeOnly.Parse(value, CultureInfo.InvariantCulture); + return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(TimeOnly)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(decimal)}'.", exception); } } @@ -543,15 +539,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static UInt128 ParseUInt128(string value, Func getPath) + public static Guid ParseGuid(string value, Func getPath) { try { - return UInt128.Parse(value, CultureInfo.InvariantCulture); + return Guid.Parse(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(UInt128)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Guid)}'.", exception); } } @@ -579,87 +575,91 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - public static int ParseInt32(string value, Func getPath) + public static T ParseEnum(string value, Func getPath) where T : struct { try { - return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + #if NETFRAMEWORK || NETSTANDARD2_0 + return (T)Enum.Parse(typeof(T), value, ignoreCase: true); + #else + return Enum.Parse(value, ignoreCase: true); + #endif } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(T)}'.", exception); } } - public static long ParseInt64(string value, Func getPath) + public static Int128 ParseInt128(string value, Func getPath) { try { - return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return Int128.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(long)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Int128)}'.", exception); } } - public static sbyte ParseSByte(string value, Func getPath) + public static Half ParseHalf(string value, Func getPath) { try { - return sbyte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return Half.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(sbyte)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(Half)}'.", exception); } } - public static short ParseInt16(string value, Func getPath) + public static UInt128 ParseUInt128(string value, Func getPath) { try { - return short.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return UInt128.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(short)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(UInt128)}'.", exception); } } - public static uint ParseUInt32(string value, Func getPath) + public static DateOnly ParseDateOnly(string value, Func getPath) { try { - return uint.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return DateOnly.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(uint)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(DateOnly)}'.", exception); } } - public static ulong ParseUInt64(string value, Func getPath) + public static TimeOnly ParseTimeOnly(string value, Func getPath) { try { - return ulong.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return TimeOnly.Parse(value, CultureInfo.InvariantCulture); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ulong)}'.", exception); + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(TimeOnly)}'.", exception); } } - public static ushort ParseUInt16(string value, Func getPath) + public static byte[] ParseByteArray(string value, Func getPath) { try { - return ushort.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + return Convert.FromBase64String(value); } catch (Exception exception) { - throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ushort)}'.", exception); + 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 145093ea55aac..3e38a484c8255 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 @@ -59,8 +59,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyDictionary", "MyInt", "MyList", "MyList2", "MyString" }); 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -86,92 +86,92 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section1) + foreach (IConfigurationSection section in configuration.GetChildren()) { - Dictionary? temp3 = instance.MyDictionary; - temp3 ??= new Dictionary(); - BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp3; + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } } + } - if (configuration["MyInt"] is string value4) + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt32(value4, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } + } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) - { - List? temp7 = instance.MyList; - temp7 ??= new List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; - } - - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) { - List? temp10 = instance.MyList2; - temp10 ??= new List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); } + } - if (configuration["MyString"] is string value11) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) { - instance.MyString = value11; + if (section.Value is string value) + { + instance[section.Key] = value; + } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyInt"] is string value12) + if (configuration["MyString"] is string value3) { - instance.MyInt = ParseInt32(value12, () => configuration.GetSection("MyInt").Path); + instance.MyString = value3; + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) { - if (section.Value is string value) - { - instance[section.Key] = value; - } + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - var value = new Program.MyClass2(); - BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); - instance.Add(value); + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) { - if (section.Value is string value) - { - instance.Add(ParseInt32(value, () => section.Path)); - } + Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp13; } } @@ -234,7 +234,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 4aa428bba74f2..186e93a49207b 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 @@ -59,8 +59,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyDictionary", "MyInt", "MyList", "MyList2", "MyString" }); 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -86,92 +86,92 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section1) + foreach (IConfigurationSection section in configuration.GetChildren()) { - Dictionary? temp3 = instance.MyDictionary; - temp3 ??= new Dictionary(); - BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp3; + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } } + } - if (configuration["MyInt"] is string value4) + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt32(value4, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } + } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) - { - List? temp7 = instance.MyList; - temp7 ??= new List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; - } - - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) { - List? temp10 = instance.MyList2; - temp10 ??= new List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); } + } - if (configuration["MyString"] is string value11) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) { - instance.MyString = value11; + if (section.Value is string value) + { + instance[section.Key] = value; + } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyInt"] is string value12) + if (configuration["MyString"] is string value3) { - instance.MyInt = ParseInt32(value12, () => configuration.GetSection("MyInt").Path); + instance.MyString = value3; + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) { - if (section.Value is string value) - { - instance[section.Key] = value; - } + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - var value = new Program.MyClass2(); - BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); - instance.Add(value); + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) { - if (section.Value is string value) - { - instance.Add(ParseInt32(value, () => section.Path)); - } + Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp13; } } @@ -234,7 +234,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 d456f83046649..7958adb112533 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 @@ -59,8 +59,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyDictionary", "MyInt", "MyList", "MyList2", "MyString" }); 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -86,92 +86,92 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section1) + foreach (IConfigurationSection section in configuration.GetChildren()) { - Dictionary? temp3 = instance.MyDictionary; - temp3 ??= new Dictionary(); - BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp3; + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } } + } - if (configuration["MyInt"] is string value4) + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt32(value4, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } + } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) - { - List? temp7 = instance.MyList; - temp7 ??= new List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; - } - - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) { - List? temp10 = instance.MyList2; - temp10 ??= new List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); } + } - if (configuration["MyString"] is string value11) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) { - instance.MyString = value11; + if (section.Value is string value) + { + instance[section.Key] = value; + } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyInt"] is string value12) + if (configuration["MyString"] is string value3) { - instance.MyInt = ParseInt32(value12, () => configuration.GetSection("MyInt").Path); + instance.MyString = value3; + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) { - if (section.Value is string value) - { - instance[section.Key] = value; - } + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - var value = new Program.MyClass2(); - BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); - instance.Add(value); + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) { - if (section.Value is string value) - { - instance.Add(ParseInt32(value, () => section.Path)); - } + Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp13; } } @@ -234,7 +234,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { 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 6c483b07dc5b3..b87d0c0a259cc 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 @@ -53,8 +53,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #endregion IServiceCollection extensions. #region Core binding extensions. - private readonly static Lazy> s_configKeys_ProgramMyClass = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "MyDictionary", "MyInt", "MyList", "MyList2", "MyString" }); 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 BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) { @@ -80,92 +80,92 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration 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.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - - if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section1) + foreach (IConfigurationSection section in configuration.GetChildren()) { - Dictionary? temp3 = instance.MyDictionary; - temp3 ??= new Dictionary(); - BindCore(section1, ref temp3, defaultValueIfNotFound: false, binderOptions); - instance.MyDictionary = temp3; + if (section.Value is string value) + { + instance.Add(ParseInt(value, () => section.Path)); + } } + } - if (configuration["MyInt"] is string value4) + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + + if (configuration["MyInt"] is string value1) { - instance.MyInt = ParseInt32(value4, () => configuration.GetSection("MyInt").Path); + instance.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } + } - if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) - { - List? temp7 = instance.MyList; - temp7 ??= new List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.MyList = temp7; - } - - if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) + public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) { - List? temp10 = instance.MyList2; - temp10 ??= new List(); - BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); - instance.MyList2 = temp10; + var value = new Program.MyClass2(); + BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); + instance.Add(value); } + } - if (configuration["MyString"] is string value11) + public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + { + foreach (IConfigurationSection section in configuration.GetChildren()) { - instance.MyString = value11; + if (section.Value is string value) + { + instance[section.Key] = value; + } } } - public static void BindCore(IConfiguration configuration, ref Program.MyClass2 instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref Program.MyClass instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { - ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions); + ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions); - if (configuration["MyInt"] is string value12) + if (configuration["MyString"] is string value3) { - instance.MyInt = ParseInt32(value12, () => configuration.GetSection("MyInt").Path); + instance.MyString = value3; + } + + if (configuration["MyInt"] is string value4) + { + instance.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path); } else if (defaultValueIfNotFound) { instance.MyInt = default; } - } - public static void BindCore(IConfiguration configuration, ref Dictionary instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5) { - if (section.Value is string value) - { - instance[section.Key] = value; - } + List? temp7 = instance.MyList; + temp7 ??= new List(); + BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); + instance.MyList = temp7; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8) { - var value = new Program.MyClass2(); - BindCore(section, ref value, defaultValueIfNotFound: false, binderOptions); - instance.Add(value); + List? temp10 = instance.MyList2; + temp10 ??= new List(); + BindCore(section8, ref temp10, defaultValueIfNotFound: false, binderOptions); + instance.MyList2 = temp10; } - } - public static void BindCore(IConfiguration configuration, ref List instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) - { - foreach (IConfigurationSection section in configuration.GetChildren()) + if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11) { - if (section.Value is string value) - { - instance.Add(ParseInt32(value, () => section.Path)); - } + Dictionary? temp13 = instance.MyDictionary; + temp13 ??= new Dictionary(); + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.MyDictionary = temp13; } } @@ -228,7 +228,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return binderOptions; } - public static int ParseInt32(string value, Func getPath) + public static int ParseInt(string value, Func getPath) { try { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs index ef2765c28a33e..4373b404fc67f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigBindingGenTestDriver.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Reflection; using System.Threading; @@ -90,28 +92,33 @@ private async Task UpdateCompilationWithSource(string? source = null) } } } + } - internal struct ConfigBindingGenRunResult - { - public required Compilation OutputCompilation { get; init; } + internal struct ConfigBindingGenRunResult + { + public required Compilation OutputCompilation { get; init; } - public required GeneratedSourceResult? GeneratedSource { get; init; } + public required GeneratedSourceResult? GeneratedSource { get; init; } - /// - /// Diagnostics produced by the generator alone. Doesn't include any from other build participants. - /// - public required ImmutableArray Diagnostics { get; init; } + /// + /// Diagnostics produced by the generator alone. Doesn't include any from other build participants. + /// + public required ImmutableArray Diagnostics { get; init; } - public required ImmutableArray TrackedSteps { get; init; } + public required ImmutableArray TrackedSteps { get; init; } - public required SourceGenerationSpec? GenerationSpec { get; init; } - } + public required SourceGenerationSpec? GenerationSpec { get; init; } + } + + internal enum ExpectedDiagnostics + { + None, + FromGeneratorOnly, } internal static class ConfigBindingGenTestDriverExtensions { - public static void ValidateIncrementalResult( - this ConfigurationBindingGeneratorTests.ConfigBindingGenRunResult result, + public static void ValidateIncrementalResult(this ConfigBindingGenRunResult result, IncrementalStepRunReason inputReason, IncrementalStepRunReason outputReason) { @@ -121,5 +128,29 @@ public static void ValidateIncrementalResult( Assert.Collection(step.Outputs, output => Assert.Equal(outputReason, output.Reason)); }); } + + public static void ValidateDiagnostics(this ConfigBindingGenRunResult result, ExpectedDiagnostics expectedDiags) + { + ImmutableArray outputDiagnostics = result.OutputCompilation.GetDiagnostics(); + + if (expectedDiags is ExpectedDiagnostics.None) + { + foreach (Diagnostic diagnostic in outputDiagnostics) + { + Assert.True( + IsPermitted(diagnostic), + $"Generator caused dagnostic in output compilation: {diagnostic.GetMessage(CultureInfo.InvariantCulture)}."); + } + } + else + { + Debug.Assert(expectedDiags is ExpectedDiagnostics.FromGeneratorOnly); + + Assert.NotEmpty(result.Diagnostics); + Assert.False(outputDiagnostics.Any(diag => !IsPermitted(diag))); + } + + static bool IsPermitted(Diagnostic diagnostic) => diagnostic.Severity <= DiagnosticSeverity.Info; + } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs index 01acddd49c8f3..e05a773713712 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.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.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -314,6 +315,30 @@ public class MyClass4 await VerifyAgainstBaselineUsingFile("Get.generated.txt", source, extType: ExtensionClassType.ConfigurationBinder); } + [Fact] + public async Task Get_PrimitivesOnly() + { + string source = """ + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + config.Get(); + config.Get(typeof(string)); + config.Get(binderOptions => { }); + config.Get(typeof(double), binderOptions => { }); + } + } + """; + + await VerifyAgainstBaselineUsingFile("Get_PrimitivesOnly.generated.txt", source, extType: ExtensionClassType.ConfigurationBinder); + } + [Fact] public async Task Get_T() { @@ -719,7 +744,6 @@ public class MyClass } [Fact] - [ActiveIssue("Work out why we aren't getting all the expected diagnostics.")] public async Task Collections() { string source = """ @@ -737,6 +761,7 @@ public static void Main() section.Get(); } + // Diagnostic warning because we don't know how to instantiate two properties on this type. public class MyClassWithCustomCollections { public CustomDictionary CustomDictionary { get; set; } @@ -744,6 +769,7 @@ public class MyClassWithCustomCollections public ICustomDictionary ICustomDictionary { get; set; } public ICustomSet ICustomCollection { get; set; } public IReadOnlyList IReadOnlyList { get; set; } + // Diagnostic warning because we don't know how to instantiate the property type. public IReadOnlyDictionary UnsupportedIReadOnlyDictionaryUnsupported { get; set; } public IReadOnlyDictionary IReadOnlyDictionary { get; set; } } @@ -756,21 +782,26 @@ public class CustomList : List { } + // Diagnostic warning because we don't know how to instantiate this type. public interface ICustomDictionary : IDictionary { } + // Diagnostic warning because we don't know how to instantiate this type. public interface ICustomSet : ISet { } } """; - await VerifyAgainstBaselineUsingFile("Collections.generated.txt", source, validateOutputDiags: false, assessDiagnostics: (d) => - { - Assert.Equal(3, d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); - Assert.Equal(6, d.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count()); - }); + ConfigBindingGenRunResult result = await VerifyAgainstBaselineUsingFile( + "Collections.generated.txt", + source, + expectedDiags: ExpectedDiagnostics.FromGeneratorOnly); + + ImmutableArray diagnostics = result.Diagnostics; + Assert.Equal(3, diagnostics.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); + Assert.Equal(3, diagnostics.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count()); } [Fact] @@ -812,14 +843,12 @@ public abstract class AbstractType_CannotInit } """; - await VerifyAgainstBaselineUsingFile( + ConfigBindingGenRunResult result = await VerifyAgainstBaselineUsingFile( "EmptyConfigType.generated.txt", source, - assessDiagnostics: (d) => - { - Assert.Equal(2, d.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); - }, - validateOutputDiags: false); + expectedDiags: ExpectedDiagnostics.FromGeneratorOnly); + + Assert.Equal(2, result.Diagnostics.Where(diag => diag.Id == Diagnostics.TypeNotSupported.Id).Count()); } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs index 6ad0ccdc6afa6..cbbd34e7fc41d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs @@ -66,6 +66,7 @@ private static class Diagnostics } private static readonly Assembly[] s_compilationAssemblyRefs = new[] { + typeof(BitArray).Assembly, typeof(ConfigurationBinder).Assembly, typeof(ConfigurationBuilder).Assembly, typeof(CultureInfo).Assembly, @@ -91,19 +92,18 @@ private enum ExtensionClassType private static async Task VerifyThatSourceIsGenerated(string testSourceCode) { ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(testSourceCode); - GeneratedSourceResult? source = result.GeneratedSource; + Assert.NotNull(source); Assert.Empty(result.Diagnostics); Assert.True(source.Value.SourceText.Lines.Count > 10); } - private static async Task VerifyAgainstBaselineUsingFile( + private static async Task VerifyAgainstBaselineUsingFile( string filename, string testSourceCode, - Action>? assessDiagnostics = null, ExtensionClassType extType = ExtensionClassType.None, - bool validateOutputDiags = true) + ExpectedDiagnostics expectedDiags = ExpectedDiagnostics.None) { string path = extType is ExtensionClassType.None ? Path.Combine("Baselines", filename) @@ -112,15 +112,13 @@ private static async Task VerifyAgainstBaselineUsingFile( string[] expectedLines = baseline.Replace("%VERSION%", typeof(ConfigurationBindingGenerator).Assembly.GetName().Version?.ToString()) .Split(Environment.NewLine); - ConfigBindingGenTestDriver genDriver = new(); ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(testSourceCode); - Assert.NotNull(result.GeneratedSource); - GeneratedSourceResult generatedSource = result.GeneratedSource.Value; + result.ValidateDiagnostics(expectedDiags); - SourceText resultSourceText = generatedSource.SourceText; + SourceText resultSourceText = result.GeneratedSource.Value.SourceText; bool resultEqualsBaseline = RoslynTestUtils.CompareLines(expectedLines, resultSourceText, out string errorMessage); -#if !UPDATE_BASELINES +#if UPDATE_BASELINES if (!resultEqualsBaseline) { const string envVarName = "RepoRootDir"; @@ -138,27 +136,18 @@ private static async Task VerifyAgainstBaselineUsingFile( } #endif - assessDiagnostics ??= static (diagnostics) => Assert.Empty(diagnostics); - assessDiagnostics(result.Diagnostics); Assert.True(resultEqualsBaseline, errorMessage); + + return result; } private static async Task RunGeneratorAndUpdateCompilation( string source, LanguageVersion langVersion = LanguageVersion.CSharp12, - IEnumerable? assemblyReferences = null, - bool validateCompilationDiagnostics = false) + IEnumerable? assemblyReferences = null) { ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(langVersion, assemblyReferences); - ConfigBindingGenRunResult result = await driver.RunGeneratorAndUpdateCompilation(source); - - if (validateCompilationDiagnostics) - { - ImmutableArray compilationDiags = result.OutputCompilation.GetDiagnostics(); - Assert.False(compilationDiags.Any(d => d.Severity > DiagnosticSeverity.Info)); - } - - return result; + return await driver.RunGeneratorAndUpdateCompilation(source); } private static List GetAssemblyRefsWithAdditional(params Type[] additional) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs index 68c39100bb7c7..aff9a0c20364c 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Incremental.cs @@ -51,9 +51,9 @@ public async Task RunWithNoDiags_Then_ChangeInputOrder() result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_ReorderedInvocations); result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified); - // No diff from previous step because type model is the same. + // We expect different spec because members are reordered. result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_ReorderedConfigTypeMembers); - result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Unchanged); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified); } [Fact] @@ -93,7 +93,7 @@ public async Task RunWithDiags_Then_NoEdit() } [Fact] - public async Task RunWithDiags_Then_NoOpEdit() + public async Task RunWithDiags_Then_ChangeInputOrder() { ConfigBindingGenTestDriver driver = new ConfigBindingGenTestDriver(); @@ -104,9 +104,9 @@ public async Task RunWithDiags_Then_NoOpEdit() result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_ReorderedInvocations); result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified); - // No diff from previous step because type model is the same. + // We expect different spec because members are reordered. result = await driver.RunGeneratorAndUpdateCompilation(BindCallSampleCodeVariant_WithUnsupportedMember_ReorderedConfigTypeMembers); - result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Unchanged); + result.ValidateIncrementalResult(IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified); } [Fact] 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 f4b493becf711..848d93b32a475 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 @@ -33,6 +33,7 @@ + @@ -53,7 +54,6 @@ - @@ -66,4 +66,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 4396bd1f349d5..e0dac6a9ad82c 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; using SourceGenerators; namespace System.Text.Json.SourceGeneration @@ -61,7 +60,7 @@ public void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location location = _contextClassLocation; } - Diagnostics.Add(DiagnosticInfo.Create(descriptor, location, messageArgs ?? Array.Empty())); + Diagnostics.Add(DiagnosticInfo.Create(descriptor, location, messageArgs)); } public Parser(KnownTypeSymbols knownSymbols)