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 a5de49c0316366..cd2cba95a298bc 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs @@ -17,6 +17,7 @@ private sealed partial class Emitter private readonly bool _emitGenericParseEnum; private readonly bool _emitNotNullIfNotNull; private readonly bool _emitThrowIfNullMethod; + private readonly bool _useUpdatedMemorySafetyRules; private readonly SourceWriter _writer = new(); @@ -29,6 +30,7 @@ public Emitter(SourceGenerationSpec sourceGenSpec) _emitGenericParseEnum = sourceGenSpec.EmitGenericParseEnum; _emitNotNullIfNotNull = sourceGenSpec.EmitNotNullIfNotNull; _emitThrowIfNullMethod = sourceGenSpec.EmitThrowIfNullMethod; + _useUpdatedMemorySafetyRules = sourceGenSpec.UseUpdatedMemorySafetyRules; } 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 2e18263bac1001..5c330342f3cc30 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -58,10 +58,34 @@ internal sealed partial class Parser(CompilationData compilationData) EmitEnumParseMethod = _emitEnumParseMethod, EmitGenericParseEnum = _emitGenericParseEnum, EmitNotNullIfNotNull = _typeSymbols.NotNullIfNotNullAttribute is not null, - EmitThrowIfNullMethod = IsThrowIfNullMethodToBeEmitted() + EmitThrowIfNullMethod = IsThrowIfNullMethodToBeEmitted(), + UseUpdatedMemorySafetyRules = UsesUpdatedMemorySafetyRules(_typeSymbols.Compilation), }; } + // OverloadResolutionPriorityAttribute-era compilers expose the memory-safety rules version on the module; the + // enum-valued API is unavailable in older Roslyn hosts, so it is bound through its underlying int type. + private static readonly Func? s_memorySafetyRulesVersionAccessor = CreateMemorySafetyRulesVersionAccessor(); + + private static Func? CreateMemorySafetyRulesVersionAccessor() + { + System.Reflection.MethodInfo? getter = typeof(IModuleSymbol).GetProperty("MemorySafetyRulesVersion")?.GetMethod; + return getter is null + ? null + : (Func)getter.CreateDelegate(typeof(Func)); + } + + private static bool UsesUpdatedMemorySafetyRules(Compilation compilation) + { + const int UpdatedMemorySafetyRulesVersion = 2; + + // The module API includes both the compilation option and the legacy feature flag. + // Older compiler hosts expose only the temporary feature-flag opt-in. + return s_memorySafetyRulesVersionAccessor is { } getVersion + ? getVersion(compilation.SourceModule) >= UpdatedMemorySafetyRulesVersion + : compilation.SyntaxTrees.FirstOrDefault()?.Options.Features.ContainsKey("updated-memory-safety-rules") is true; + } + private bool IsValidRootConfigType([NotNullWhen(true)] ITypeSymbol? type) { if (type is null || @@ -222,11 +246,9 @@ private TypeSpec CreateTypeSpec(TypeParseInfo typeParseInfo) private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType) { - if (type is INamedTypeSymbol { IsGenericType: true } genericType && - genericType.ConstructUnboundGenericType() is INamedTypeSymbol { } unboundGeneric && - unboundGeneric.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { - underlyingType = genericType.TypeArguments[0]; + underlyingType = ((INamedTypeSymbol)type).TypeArguments[0]; return true; } @@ -564,9 +586,9 @@ private bool IsUnsupportedType(ITypeSymbol type, HashSet? visitedTy return true; } - if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + if (IsNullable(type, out ITypeSymbol? underlyingType)) { - type = ((INamedTypeSymbol)type).TypeArguments[0]; // extract the T from a Nullable + type = underlyingType; } if (SymbolEqualityComparer.Default.Equals(_typeSymbols.IntPtr, type) || @@ -659,6 +681,7 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) string? initExceptionMessage = null; IMethodSymbol? ctor = null; + bool hasExplicitParameterlessCtor = false; if (!(typeSymbol.IsAbstract || typeSymbol.TypeKind is TypeKind.Interface)) { @@ -691,6 +714,13 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) } bool hasPublicParameterlessCtor = typeSymbol.IsValueType || parameterlessCtor is not null; + + // A struct's synthesized parameterless constructor is implicitly declared; an author-written one is + // not. This matters for value-type construction that must bypass the required-member check: default(T) + // is only correct for the synthesized constructor (which does nothing), while an explicit constructor + // must actually run (like Activator.CreateInstance does for the reflection binder). + hasExplicitParameterlessCtor = parameterlessCtor is { IsImplicitlyDeclared: false }; + if (!hasPublicParameterlessCtor && hasMultipleParameterizedCtors) { initDiagDescriptor = DiagnosticDescriptors.MultipleParameterizedConstructors; @@ -713,6 +743,11 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) initializationStrategy = ctor.Parameters.Length is 0 ? ObjectInstantiationStrategy.ParameterlessConstructor : ObjectInstantiationStrategy.ParameterizedConstructor; } + // A constructor marked [SetsRequiredMembers] satisfies all required members, so the compiler does not + // require them to be set in an object initializer even when the generator omits them. + bool ctorSetsRequiredMembers = ctor is not null && _typeSymbols.SetsRequiredMembersAttribute is not null && + ctor.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.SetsRequiredMembersAttribute)); + if (initDiagDescriptor is not null) { Debug.Assert(initExceptionMessage is not null); @@ -721,10 +756,13 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) Dictionary? properties = null; HashSet? reportedUnsupportedProperties = null; + bool hasRequiredMember = false; INamedTypeSymbol? current = typeSymbol; + int declaringTypeIndex = -1; while (current is not null) { + declaringTypeIndex++; ImmutableArray members = current.GetMembers(); foreach (ISymbol member in members) { @@ -760,10 +798,22 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) string configKeyName = attributeData?.ConstructorArguments.FirstOrDefault().Value as string ?? propertyName; bool isIgnored = attributes.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.ConfigurationIgnoreAttribute)); + hasRequiredMember |= property.IsRequired; + + (TypeRef? accessorDeclaringTypeRef, bool setterCanUseUnsafeAccessor, GenericAccessorInfo genericInfo) = + GetSetterAccessorInfo(property, typeSymbol); + PropertySpec spec = new(property, new TypeRef(property.Type)) { ConfigurationKeyName = configKeyName, IsIgnored = isIgnored, + AccessorDeclaringTypeRef = accessorDeclaringTypeRef, + SetterCanUseUnsafeAccessor = setterCanUseUnsafeAccessor, + DeclaringTypeParameterNames = genericInfo.TypeParameterNames, + OpenDeclaringTypeFQN = genericInfo.OpenDeclaringTypeFQN, + OpenPropertyTypeFQN = genericInfo.OpenMemberTypeFQN, + DeclaringTypeParameterConstraintClauses = genericInfo.ConstraintClauses, + DeclaringTypeIndex = declaringTypeIndex, }; if (!spec.IsIgnored && (spec.CanGet || spec.CanSet || BacksConstructorParameter(ctor, propertyName))) @@ -799,9 +849,16 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) } else { + // For a generic type using a constructor-accessor wrapper, the parameter type inside the + // wrapper is expressed with open type parameters; capture it when it differs from the closed form. + string? openParameterTypeFQN = typeSymbol.IsGenericType && + !SymbolEqualityComparer.Default.Equals(parameter.Type, parameter.OriginalDefinition.Type) + ? parameter.OriginalDefinition.Type.GetFullyQualifiedName() : null; + ParameterSpec paramSpec = new ParameterSpec(parameter, propertySpec.TypeRef) { ConfigurationKeyName = propertySpec.ConfigurationKeyName, + OpenTypeFQN = openParameterTypeFQN, }; propertySpec.MatchingCtorParam = paramSpec; @@ -828,12 +885,136 @@ private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo) static string FormatParams(List names) => string.Join(",", names); } + // A type with required members not satisfied by a [SetsRequiredMembers] constructor cannot be created + // with a plain new T(...). It is instead created in a way that bypasses the required-member check, then + // the required members are set post-construction (only when their config key is present), matching the + // reflection binder and preserving their defaults for absent keys. Reference types, value types with a + // parameterized constructor, and value types with an explicit parameterless constructor go through a + // constructor accessor (so constructor arguments are passed and any author-written constructor runs). A + // value type whose only parameterless constructor is the synthesized one uses default(T), which also + // bypasses the check and is equivalent to running that do-nothing constructor. + bool needsRequiredMemberBypass = hasRequiredMember && !ctorSetsRequiredMembers; + bool constructValueTypeWithDefault = needsRequiredMemberBypass && typeSymbol.IsValueType && + initializationStrategy is ObjectInstantiationStrategy.ParameterlessConstructor && !hasExplicitParameterlessCtor; + bool constructionRequiresAccessor = needsRequiredMemberBypass && !constructValueTypeWithDefault; + + // [UnsafeAccessor] is available on .NET 8+. It is used for init-only setters and the constructor accessor. + // A generic type additionally requires generic [UnsafeAccessor] support (.NET 9+) and must not be nested + // in a generic type; when used, the constructor extern is emitted inside a generic wrapper class. + // Downlevel frameworks (and unsupported generic shapes) fall back to reflection. + bool typeIsGeneric = typeSymbol.IsGenericType; + bool constructorCanUseUnsafeAccessor = _typeSymbols.UnsafeAccessorAttribute is not null && + (!typeIsGeneric || (_typeSymbols.SupportsGenericUnsafeAccessors && typeSymbol.ContainingType is not { IsGenericType: true })); + + ImmutableEquatableArray? ctorTypeParameterNames = null; + string? ctorOpenTypeFQN = null; + string? ctorConstraintClauses = null; + if (constructorCanUseUnsafeAccessor && typeIsGeneric) + { + INamedTypeSymbol definition = typeSymbol.OriginalDefinition; + ctorTypeParameterNames = definition.TypeParameters.Select(static tp => tp.Name).ToImmutableEquatableArray(); + ctorOpenTypeFQN = definition.GetFullyQualifiedName(); + ctorConstraintClauses = GetTypeParameterConstraintClauses(definition); + } + return new ObjectSpec( typeSymbol, initializationStrategy, properties: properties?.Values.ToImmutableEquatableArray(), constructorParameters: ctorParams?.ToImmutableEquatableArray(), - initExceptionMessage); + initExceptionMessage) + { + ConstructorCanUseUnsafeAccessor = constructorCanUseUnsafeAccessor, + ConstructionRequiresAccessor = constructionRequiresAccessor, + ConstructValueTypeWithDefault = constructValueTypeWithDefault, + DeclaringTypeParameterNames = ctorTypeParameterNames, + OpenTypeFQN = ctorOpenTypeFQN, + DeclaringTypeParameterConstraintClauses = ctorConstraintClauses, + }; + } + + private static readonly SymbolDisplayFormat s_fullyQualifiedWithConstraints = + SymbolDisplayFormat.FullyQualifiedFormat.AddGenericsOptions(SymbolDisplayGenericsOptions.IncludeTypeConstraints); + + private readonly record struct GenericAccessorInfo( + ImmutableEquatableArray? TypeParameterNames, + string? OpenDeclaringTypeFQN, + string? OpenMemberTypeFQN, + string? ConstraintClauses); + + /// + /// Computes how an init-only setter accessor for targets its declaring type: the + /// base declaring type for an inherited property (when it can be named), whether [UnsafeAccessor] can be + /// used, and the open-generic wrapper info for a generic declaring type. Returns defaults for properties that + /// are not set through an accessor (non-public or non-init-only setters). + /// + private (TypeRef? AccessorDeclaringTypeRef, bool SetterCanUseUnsafeAccessor, GenericAccessorInfo GenericInfo) GetSetterAccessorInfo( + IPropertySymbol property, INamedTypeSymbol boundType) + { + if (property.SetMethod is not { DeclaredAccessibility: Accessibility.Public, IsInitOnly: true }) + { + return (null, false, default); + } + + INamedTypeSymbol declaringType = property.ContainingType; + bool isInherited = !SymbolEqualityComparer.Default.Equals(declaringType, boundType); + + // [UnsafeAccessor] resolves a setter against the exact type named. An inherited setter is declared on the + // base type, so the extern must name that type (and the derived instance is passed via an implicit + // upcast). When the base type cannot be named (inaccessible), the reflection fallback, which searches base + // types, is used instead. + TypeRef? accessorDeclaringTypeRef = null; + bool declaringTypeCanBeNamed = true; + if (isInherited) + { + if (_typeSymbols.Compilation.IsSymbolAccessibleWithin(declaringType, _typeSymbols.Compilation.Assembly)) + { + accessorDeclaringTypeRef = new TypeRef(declaringType); + } + else + { + declaringTypeCanBeNamed = false; + } + } + + bool declaringTypeIsGeneric = declaringType.IsGenericType; + + // A generic declaring type uses a generic wrapper class for the accessor, but only when it is not itself + // nested in a generic type: the wrapper is keyed on the declaring type's own type parameters, which cannot + // express the enclosing type's parameters. A type nested in a generic type (whether or not it adds its own + // parameters) therefore falls back to reflection, which needs no open-generic form. + bool canUseGenericWrapper = declaringTypeIsGeneric && + _typeSymbols.SupportsGenericUnsafeAccessors && + declaringType.ContainingType is not { IsGenericType: true }; + bool setterCanUseUnsafeAccessor = + _typeSymbols.UnsafeAccessorAttribute is not null && + declaringTypeCanBeNamed && + (!declaringTypeIsGeneric || canUseGenericWrapper); + + GenericAccessorInfo genericInfo = default; + if (canUseGenericWrapper) + { + INamedTypeSymbol definition = declaringType.OriginalDefinition; + genericInfo = new GenericAccessorInfo( + definition.TypeParameters.Select(static tp => tp.Name).ToImmutableEquatableArray(), + definition.GetFullyQualifiedName(), + property.OriginalDefinition.Type.GetFullyQualifiedName(), + GetTypeParameterConstraintClauses(definition)); + } + + return (accessorDeclaringTypeRef, setterCanUseUnsafeAccessor, genericInfo); + } + + private static string? GetTypeParameterConstraintClauses(INamedTypeSymbol type) + { + Debug.Assert(type.IsGenericType); + + // The display string has the form "global::NS.Type where T : C1 where U : C2". + // Return the constraint clauses (everything from the first " where "). + string display = type.ToDisplayString(s_fullyQualifiedWithConstraints); + const string whereMarker = " where "; + int whereIndex = display.IndexOf(whereMarker, StringComparison.Ordinal); + return whereIndex < 0 ? null : display.Substring(whereIndex + 1); } private static UnsupportedTypeSpec CreateUnsupportedCollectionSpec(TypeParseInfo typeParseInfo) 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 30d589d07cad26..712e04dff4e2b9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs @@ -32,6 +32,7 @@ private void EmitCoreBindingHelpers() EmitBindCoreMainMethod(); EmitBindCoreMethods(); EmitInitializeMethods(); + EmitAccessorMethods(); EmitHelperMethods(); EmitBindingExtEndRegion(); } @@ -323,60 +324,41 @@ private void EmitInitializeMethods() private void EmitInitializeMethod(ObjectSpec type) { - Debug.Assert(type.InstantiationStrategy is ObjectInstantiationStrategy.ParameterizedConstructor); + Debug.Assert(TypeIndex.HasInitializeMethod(type)); 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."); + type.InstantiationStrategy is ObjectInstantiationStrategy.ParameterlessConstructor || type.ConstructorParameters is not null, + $"Expecting parameterized type for init method, {type.DisplayString}, to have ctor params."); - IEnumerable initOnlyProps = type.Properties - .Where(prop => prop.SetOnInit && _typeIndex.ShouldBindTo(prop)); List ctorArgList = new(); EmitStartBlock($"public static {type.TypeRef.FullyQualifiedName} {GetInitializeMethodDisplayString(type)}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})"); _emitBlankLineBeforeNextStatement = false; - foreach (ParameterSpec parameter in type.ConstructorParameters) + if (type.ConstructorParameters is not null) { - string name = EscapeIdentifier(parameter.Name); - string argExpr = parameter.RefKind switch + foreach (ParameterSpec parameter in type.ConstructorParameters) { - RefKind.None => name, - RefKind.Ref => $"ref {name}", - RefKind.Out => "out _", - RefKind.In => $"in {name}", - _ => throw new InvalidOperationException() - }; - - ctorArgList.Add(argExpr); - EmitBindImplForMember(parameter); - } - - foreach (PropertySpec property in initOnlyProps) - { - if (property.MatchingCtorParam is null) - { - EmitBindImplForMember(property); + string name = EscapeIdentifier(parameter.Name); + string argExpr = parameter.RefKind switch + { + RefKind.None => name, + RefKind.Ref => $"ref {name}", + RefKind.Out => "out _", + RefKind.In => $"in {name}", + _ => throw new InvalidOperationException() + }; + + ctorArgList.Add(argExpr); + EmitBindImplForMember(parameter); } } - string returnExpression = $"return new {type.TypeRef.FullyQualifiedName}({string.Join(", ", ctorArgList)})"; - if (!initOnlyProps.Any()) - { - _writer.WriteLine($"{returnExpression};"); - } - else - { - EmitStartBlock(returnExpression); - foreach (PropertySpec property in initOnlyProps) - { - // Properties bound through a matching constructor parameter don't have a local of their - // own; their bound value lives in the local named after the parameter. - string valueExpr = EscapeIdentifier(property.MatchingCtorParam?.Name ?? property.Name); - _writer.WriteLine($@"{EscapeIdentifier(property.Name)} = {valueExpr},"); - } - EmitEndBlock(endBraceTrailingSource: ";"); - } + // Construct the instance and return it. Init-only and required members are not assigned here; they are + // set post-construction in BindCore (only when their config key is present) so their defaults survive. + // A type with required members is constructed through an accessor that bypasses the required-member + // check (see GetConstructionExpression); everything else uses a plain constructor call. + _writer.WriteLine($"return {GetConstructionExpression(type, ctorArgList)};"); // End method. EmitEndBlock(); @@ -441,6 +423,188 @@ void EmitBindImplForMember(MemberSpec member) } } + /// + /// The expression that constructs . A type with required members not satisfied by a + /// [SetsRequiredMembers] constructor is constructed through an accessor that bypasses the + /// required-member check (so those members can be set post-construction); everything else uses new. + /// + private static string GetConstructionExpression(ObjectSpec type, List ctorArgList) + { + // A ConstructValueTypeWithDefault type has no Initialize method (HasInitializeMethod is false for it), + // so it is constructed inline by EmitObjectInit, never here. This method only runs for types that do + // have an Initialize method, so default(T) never applies. + Debug.Assert(!type.ConstructValueTypeWithDefault); + + string args = string.Join(", ", ctorArgList); + return type.ConstructionRequiresAccessor + ? $"{UnsafeAccessorEmitter.GetQualifiedConstructorAccessorName(type.ConstructorCanUseUnsafeAccessor, type.DeclaringTypeParameterNames, type.TypeRef.FullyQualifiedName, type.IdentifierCompatibleSubstring)}({args})" + : $"new {type.TypeRef.FullyQualifiedName}({args})"; + } + + /// + /// The call-site name of the init-only setter accessor for , matching the accessor + /// emitted by (including the generic wrapper prefix for generic types). + /// + private static string GetInitOnlySetterAccessorName(ObjectSpec type, PropertySpec property) => + UnsafeAccessorEmitter.GetQualifiedAccessorName( + property.DeclaringTypeParameterNames, + property.DeclaringTypeIndex, + (property.AccessorDeclaringTypeRef ?? type.TypeRef).FullyQualifiedName, + type.IdentifierCompatibleSubstring, + accessorKind: "set", + property.Name, + propertyIndex: 0, + // Property names are unique within a type (the parser dedupes by name), so no index disambiguation. + needsDisambiguation: false); + + /// + /// Emits the constructor and init-only setter accessors used to construct types and set their init-only or + /// required members post-construction, delegating to the shared (which + /// uses [UnsafeAccessor] where available and reflection otherwise). + /// + private void EmitAccessorMethods() + { + List? constructorAccessorTypes = null; + if (_bindingHelperInfo.TypesForGen_Initialize is ImmutableEquatableArray initTypes) + { + foreach (ObjectSpec type in initTypes) + { + if (type.ConstructionRequiresAccessor) + { + (constructorAccessorTypes ??= new()).Add(type); + } + } + } + + List<(ObjectSpec Type, List Members)>? setterAccessorTypes = null; + if (_bindingHelperInfo.TypesForGen_BindCore is ImmutableEquatableArray bindTypes) + { + foreach (ComplexTypeSpec spec in bindTypes) + { + if (spec is not ObjectSpec { Properties: not null } type) + { + continue; + } + + List? members = null; + foreach (PropertySpec property in type.Properties) + { + if (property.CanSetViaAccessor && _typeIndex.ShouldBindTo(property)) + { + (members ??= new()).Add(property); + } + } + + if (members is not null) + { + (setterAccessorTypes ??= new()).Add((type, members)); + } + } + } + + if (constructorAccessorTypes is null && setterAccessorTypes is null) + { + return; + } + + bool needsBindingFlags = false; + bool needsValueTypeSetterDelegate = false; + + if (constructorAccessorTypes is not null) + { + foreach (ObjectSpec type in constructorAccessorTypes) + { + EmitConstructorAccessor(type); + needsBindingFlags |= !type.ConstructorCanUseUnsafeAccessor; + } + } + + if (setterAccessorTypes is not null) + { + foreach ((ObjectSpec type, List members) in setterAccessorTypes) + { + needsValueTypeSetterDelegate |= EmitInitOnlySetterAccessors(type, members); + foreach (PropertySpec property in members) + { + needsBindingFlags |= !property.SetterCanUseUnsafeAccessor; + } + } + } + + // The reflection fallback references an InstanceMemberBindingFlags const and, for value-type setters, a + // ValueTypeSetter<,> delegate; the shared helper owns their declarations so the emitted names and + // signatures stay in sync with the fallback code that consumes them. + if (needsBindingFlags) + { + _writer.WriteLine(); + _writer.WriteLine(UnsafeAccessorEmitter.InstanceMemberBindingFlagsDeclaration); + } + + if (needsValueTypeSetterDelegate) + { + _writer.WriteLine(); + _writer.WriteLine(UnsafeAccessorEmitter.ValueTypeSetterDelegateDeclaration); + } + + _emitBlankLineBeforeNextStatement = true; + } + + private void EmitConstructorAccessor(ObjectSpec type) + { + List parameters = new(); + if (type.ConstructorParameters is not null) + { + int index = 0; + foreach (ParameterSpec parameter in type.ConstructorParameters) + { + parameters.Add(new UnsafeAccessorEmitter.UnsafeAccessorParameterSpec + { + TypeFQN = parameter.TypeRef.FullyQualifiedName, + Index = index++, + RefKind = parameter.RefKind, + OpenTypeFQN = parameter.OpenTypeFQN, + }); + } + } + + _writer.WriteLine(); + UnsafeAccessorEmitter.EmitConstructorAccessor(_writer, _useUpdatedMemorySafetyRules, new UnsafeAccessorEmitter.UnsafeAccessorConstructorSpec + { + TypeFriendlyName = type.IdentifierCompatibleSubstring, + TypeFQN = type.TypeRef.FullyQualifiedName, + CanUseUnsafeAccessor = type.ConstructorCanUseUnsafeAccessor, + Parameters = parameters.ToImmutableEquatableArray(), + DeclaringTypeParameterNames = type.DeclaringTypeParameterNames, + OpenDeclaringTypeFQN = type.OpenTypeFQN, + DeclaringTypeParameterConstraintClauses = type.DeclaringTypeParameterConstraintClauses, + }); + } + + private bool EmitInitOnlySetterAccessors(ObjectSpec type, List members) + { + var memberSpecs = new List(members.Count); + foreach (PropertySpec property in members) + { + memberSpecs.Add(new UnsafeAccessorEmitter.UnsafeAccessorMemberSpec + { + Kind = UnsafeAccessorEmitter.AccessorMemberKind.Property, + MemberName = property.Name, + NeedsGetter = false, + NeedsSetter = true, + CanUseUnsafeAccessors = property.SetterCanUseUnsafeAccessor, + DeclaringTypeFQN = (property.AccessorDeclaringTypeRef ?? type.TypeRef).FullyQualifiedName, + MemberTypeFQN = property.TypeRef.FullyQualifiedName, + DeclaringTypeIndex = property.DeclaringTypeIndex, + DeclaringTypeParameterNames = property.DeclaringTypeParameterNames, + OpenDeclaringTypeFQN = property.OpenDeclaringTypeFQN, + OpenMemberTypeFQN = property.OpenPropertyTypeFQN, + DeclaringTypeParameterConstraintClauses = property.DeclaringTypeParameterConstraintClauses, + }); + } + + return UnsafeAccessorEmitter.EmitMemberAccessors(_writer, type.IdentifierCompatibleSubstring, type.IsValueType, _useUpdatedMemorySafetyRules, memberSpecs); + } + private void EmitHelperMethods() { // This is used all the time Get, Bind, and GetValue methods. @@ -455,7 +619,7 @@ private void EmitHelperMethods() EmitValidateConfigurationKeysMethod(); } - if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreMain | MethodsToGen_CoreBindingHelper.GetCore)) + if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreMain | MethodsToGen_CoreBindingHelper.GetCore | MethodsToGen_CoreBindingHelper.HasValueOrChildren)) { // HasValueOrChildren references this method. Debug.Assert(emitAsConfigWithChildren); @@ -917,10 +1081,49 @@ private void EmitBindCoreImplForObject(ObjectSpec type) EmitBindImplForProperty(property); } EmitEndBlock(); + + // For a constructed instance, a property that matches a constructor parameter is not bound from + // config again (that would duplicate collection items). Instead it is reset - assigned back to its + // own current value through its setter - so setters with side effects still run. This matches the + // reflection binder's ResetPropertyValue, which only applies to properties with a public getter and + // a setter (a public set or an init-only setter reachable through an accessor). + List? resetProperties = null; + foreach (PropertySpec property in initializeBoundProperties) + { + if (property.CanGet && (property.CanSet || property.CanSetViaAccessor)) + { + (resetProperties ??= new()).Add(property); + } + } + + if (resetProperties is not null) + { + EmitStartBlock("else"); + foreach (PropertySpec property in resetProperties) + { + string memberAccessExpr = $"{Identifier.instance}.{EscapeIdentifier(property.Name)}"; + if (property.CanSet) + { + _writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr};"); + } + else + { + string instanceArg = type.IsValueType ? $"ref {Identifier.instance}" : Identifier.instance; + _writer.WriteLine($"{GetInitOnlySetterAccessorName(type, property)}({instanceArg}, {memberAccessExpr});"); + } + } + EmitEndBlock(); + } } void EmitBindImplForProperty(PropertySpec property) { + if (property.CanSetViaAccessor) + { + EmitBindImplForInitOnlyProperty(property); + return; + } + string containingTypeRef = property.IsStatic ? type.TypeRef.FullyQualifiedName : Identifier.instance; EmitBindImplForMember( property, @@ -930,6 +1133,45 @@ void EmitBindImplForProperty(PropertySpec property) canGet: property.CanGet, InitializationKind.Declaration); } + + // Binds an init-only property, which cannot be assigned directly post-construction. The value is bound + // into a local seeded with the property's current value (its default), then written back through an + // accessor. Seeding with the current value preserves the default when the config key is absent. + void EmitBindImplForInitOnlyProperty(PropertySpec property) + { + // Only bindable (ShouldBindTo) properties reach here, and an init-only property is accessible only + // through its getter (its setter is not counted by IsAccessible), so it always has a public getter. + Debug.Assert(property.CanGet); + + EmitBlankLineIfRequired(); + + TypeSpec memberType = _typeIndex.GetTypeSpec(property.TypeRef); + string local = GetIncrementalIdentifier(Identifier.temp); + string propTypeFQN = memberType.TypeRef.FullyQualifiedName; + + _writer.WriteLine($"{propTypeFQN} {local} = {Identifier.instance}.{EscapeIdentifier(property.Name)};"); + + EmitBindImplForMember( + property, + memberAccessExpr: local, + GetSectionPathFromConfigurationExpression(property.ConfigurationKeyName), + canSet: true, + canGet: property.CanGet, + InitializationKind.None, + bindingToLocal: true); + + string instanceArg = type.IsValueType ? $"ref {Identifier.instance}" : Identifier.instance; + + // Invoke the init-only setter only when configuration is present for this member. This mirrors a + // settable property, whose assignment runs only inside its own "key present" check, and the + // reflection binder, which sets a property only when it bound a value (HasNewValue). An absent key + // leaves the member at its default without calling the setter - so a null default is preserved and a + // validating or side-effecting setter is not invoked with it - while a present (even empty) value is + // set, matching how a nullable member is reset to null. + EmitStartBlock($"if ({Identifier.HasValueOrChildren}({GetSectionFromConfigurationExpression(property.ConfigurationKeyName)}))"); + _writer.WriteLine($"{GetInitOnlySetterAccessorName(type, property)}({instanceArg}, {local});"); + EmitEndBlock(); + } } /// @@ -946,14 +1188,14 @@ private bool ShouldEmitBoundThroughConstructorParameter(ComplexTypeSpec type) => IsPropertyReboundInBindCore(property)); /// - /// Whether is populated while an instance of is created - /// through its Initialize method: either it flows through a matching constructor parameter, or it is a - /// required/init-only property assigned in the object initializer. Only parameterized-constructor types have - /// an Initialize method; parameterless-constructor types bind all their properties in BindCore. + /// Whether is bound while an instance of is created + /// through its Initialize method. Only properties flowing through a matching constructor parameter are bound + /// during construction; init-only and required properties are set post-construction in BindCore. + /// Types without an Initialize method bind all their properties in BindCore. /// private static bool IsBoundInInitialize(ObjectSpec type, PropertySpec property) => - type.InstantiationStrategy is ObjectInstantiationStrategy.ParameterizedConstructor && - (property.MatchingCtorParam is not null || property.SetOnInit); + TypeIndex.HasInitializeMethod(type) && + property.MatchingCtorParam is not null; /// /// Whether binding in a BindCore method emits code that reads from or @@ -965,9 +1207,9 @@ private bool IsPropertyReboundInBindCore(PropertySpec property) switch (_typeIndex.GetEffectiveTypeSpec(property.TypeRef)) { case ParsableFromStringSpec: - return property.CanGet && property.CanSet; + return property.CanGet && (property.CanSet || property.CanSetViaAccessor); case ConfigurationSectionSpec: - return property.CanSet; + return property.CanSet || property.CanSetViaAccessor; case ComplexTypeSpec complexType: return IsBindableAsMember(complexType, property.CanSet); default: @@ -1440,15 +1682,29 @@ private bool EmitObjectInit(ComplexTypeSpec type, string memberAccessExpr, Initi if (strategy is ObjectInstantiationStrategy.ParameterlessConstructor) { - // value tuple types will be declared with syntax like: - // (int, int) value = default; - // This is to avoid using invalid syntax calling the parameterless constructor - initExpr = type.IsValueTuple ? "default" : $"new {typeFQN}()"; + if (TypeIndex.HasInitializeMethod(objectType)) + { + // A required or init-only property can only be assigned in an object initializer at + // construction time; that is done in the Initialize method. + string initMethodIdentifier = GetInitializeMethodDisplayString(objectType); + initExpr = $"{initMethodIdentifier}({configArgExpr}, {Identifier.binderOptions})"; + } + else + { + // value tuple types will be declared with syntax like: + // (int, int) value = default; + // This is to avoid using invalid syntax calling the parameterless constructor. + // A value type with required members likewise cannot use new T() (CS9035); default(T) + // bypasses the check and its required members are set post-construction. + initExpr = type.IsValueTuple ? "default" + : objectType.ConstructValueTypeWithDefault ? $"default({typeFQN})" + : $"new {typeFQN}()"; + } } else { Debug.Assert(strategy is ObjectInstantiationStrategy.ParameterizedConstructor); - string initMethodIdentifier = GetInitializeMethodDisplayString(((ObjectSpec)type)); + string initMethodIdentifier = GetInitializeMethodDisplayString(objectType); initExpr = $"{initMethodIdentifier}({configArgExpr}, {Identifier.binderOptions})"; } } 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 2debb378bcc85b..dc7acf3773c35a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/Helpers.cs @@ -53,10 +53,10 @@ private static class Expression private static class TypeDisplayString { - public const string NullableActionOfBinderOptions = "Action?"; + public const string NullableActionOfBinderOptions = "global::System.Action?"; public const string OptionsBuilderOfTOptions = $"global::Microsoft.Extensions.Options.OptionsBuilder<{Identifier.TOptions}>"; public const string HashSetOfString = "global::System.Collections.Generic.HashSet"; - public const string LazyHashSetOfString = "Lazy>"; + public const string LazyHashSetOfString = "global::System.Lazy>"; public const string ListOfString = "global::System.Collections.Generic.List"; } 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 a0fac6dbbfb626..9a9e529c181d6a 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 @@ -34,6 +34,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs index 5b60ff7f148093..75a3e10d39725b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Parser/KnownTypeSymbols.cs @@ -32,6 +32,7 @@ internal sealed class KnownTypeSymbols public INamedTypeSymbol? ConfigurationBinder { get; } public INamedTypeSymbol? ConfigurationIgnoreAttribute { get; } public INamedTypeSymbol? ConfigurationKeyNameAttribute { get; } + public INamedTypeSymbol? SetsRequiredMembersAttribute { get; } public INamedTypeSymbol? OptionsBuilderConfigurationExtensions { get; } public INamedTypeSymbol? OptionsBuilderOfT { get; } public INamedTypeSymbol? OptionsBuilderOfT_Unbound { get; } @@ -66,6 +67,15 @@ internal sealed class KnownTypeSymbols public INamedTypeSymbol? ParameterInfo { get; } public INamedTypeSymbol? Delegate { get; } public INamedTypeSymbol? NotNullIfNotNullAttribute { get; } + public INamedTypeSymbol? UnsafeAccessorAttribute { get; } + public INamedTypeSymbol? OverloadResolutionPriorityAttribute { get; } + + /// + /// Whether [UnsafeAccessor] can target generic declaring types. Pre-.NET 9 [UnsafeAccessor] does + /// not support generics; the .NET 9 OverloadResolutionPriorityAttribute is used as a proxy for that + /// runtime support (it shipped in the same release), alongside UnsafeAccessorAttribute (.NET 8). + /// + public bool SupportsGenericUnsafeAccessors => UnsafeAccessorAttribute is not null && OverloadResolutionPriorityAttribute is not null; public KnownTypeSymbols(CSharpCompilation compilation) { @@ -91,6 +101,7 @@ public KnownTypeSymbols(CSharpCompilation compilation) ConfigurationBinder = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.ConfigurationBinder"); ConfigurationIgnoreAttribute = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.ConfigurationIgnoreAttribute"); ConfigurationKeyNameAttribute = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.ConfigurationKeyNameAttribute"); + SetsRequiredMembersAttribute = compilation.GetBestTypeByMetadataName("System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute"); IConfiguration = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.IConfiguration"); IConfigurationSection = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.IConfigurationSection"); IServiceCollection = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.DependencyInjection.IServiceCollection"); @@ -138,6 +149,12 @@ public KnownTypeSymbols(CSharpCompilation compilation) // Only generate nullable attributes if available NotNullIfNotNullAttribute = compilation.GetBestTypeByMetadataName("System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute"); + + // Used to decide whether generated code can set init-only/required members and bypass the required-member + // check via [UnsafeAccessor] (.NET 8+) instead of falling back to reflection. OverloadResolutionPriorityAttribute + // (.NET 9) is a proxy for [UnsafeAccessor] supporting generic declaring types. + UnsafeAccessorAttribute = compilation.GetBestTypeByMetadataName("System.Runtime.CompilerServices.UnsafeAccessorAttribute"); + OverloadResolutionPriorityAttribute = compilation.GetBestTypeByMetadataName("System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute"); } } } 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 e4b140a4e88f1d..665e2624dbbb37 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/BindingHelperInfo.cs @@ -183,11 +183,13 @@ bool TryRegisterCore() // A type with a parameterized constructor gets its constructor parameters bound // in the Initialize method regardless of whether it also has other bindable - // members; that binding capability must be registered even when - // HasBindableMembers is false (e.g. the only member is a ctor parameter backed - // by a non-bindable read-only collection type), otherwise the emitter can end up - // calling an Initialize method that was never generated. - bool needsInitializeMethod = objectSpec is { InstantiationStrategy: ObjectInstantiationStrategy.ParameterizedConstructor, InitExceptionMessage: null }; + // members; a parameterless-constructor type with a required or init-only property + // also needs an Initialize method to assign those members in an object initializer. + // That binding capability must be registered even when HasBindableMembers is false + // (e.g. the only member is a ctor parameter backed by a non-bindable read-only + // collection type), otherwise the emitter can end up calling an Initialize method + // that was never generated. + bool needsInitializeMethod = TypeIndex.HasInitializeMethod(objectSpec); if (hasBindableMembers || needsInitializeMethod) { @@ -207,6 +209,14 @@ bool TryRegisterCore() { RegisterForGen_AsConfigWithChildrenHelper(); } + + // An init-only member is set post-construction only when its configuration is + // present, which the generated BindCore checks with HasValueOrChildren, so ensure + // that helper is emitted. + if (property.CanSetViaAccessor && _typeIndex.ShouldBindTo(property)) + { + RegisterForGen_HasValueOrChildrenHelper(); + } } if (hasBindableMembers) @@ -268,6 +278,10 @@ private void RegisterStringParsableTypeIfApplicable(ParsableFromStringSpec type) } private void RegisterForGen_AsConfigWithChildrenHelper() => _methodsToGen |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren; + + // HasValueOrChildren is backed by AsConfigWithChildren, so registering it also registers that helper. + private void RegisterForGen_HasValueOrChildrenHelper() => + _methodsToGen |= MethodsToGen_CoreBindingHelper.HasValueOrChildren | MethodsToGen_CoreBindingHelper.AsConfigWithChildren; } } } 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 53ca14ae7eedec..1d7fce9f0d9154 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 @@ -29,6 +29,9 @@ public ParameterSpec(IParameterSymbol parameter, TypeRef typeRef) : base(paramet public RefKind RefKind { get; } + /// The open (type-parameter-referencing) form of the parameter type, used inside a generic constructor-accessor wrapper class; when the declaring type is non-generic or the parameter type contains no type parameters. + public string? OpenTypeFQN { get; init; } + public override bool CanGet => false; public override bool CanSet => true; 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 79a2d8f5f904c1..e7167f357ac473 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 @@ -15,9 +15,18 @@ public PropertySpec(IPropertySymbol property, TypeRef typeRef) : base(property, bool isInitOnly = setMethod?.IsInitOnly is true; IsStatic = property.IsStatic; + // Only public setters are considered here, consistent with CanSet. A required or init-only property with a + // non-public (e.g. internal) setter is therefore not treated as SetOnInit: the generator does not set it + // (matching the reflection binder, which does not bind non-public members by default), and the member keeps + // its default value. SetOnInit = setterIsPublic && (property.IsRequired || isInitOnly); CanSet = setterIsPublic && !isInitOnly; + // An init-only property can only be assigned at construction time through normal C#. Post-construction the + // generator sets it through an [UnsafeAccessor] setter (or reflection downlevel), which lets absent config + // keys preserve the property's default value instead of overwriting it. + CanSetViaAccessor = setterIsPublic && isInitOnly; CanGet = property.GetMethod?.DeclaredAccessibility is Accessibility.Public; + IsRequired = property.IsRequired; } public ParameterSpec? MatchingCtorParam { get; set; } @@ -28,8 +37,42 @@ public PropertySpec(IPropertySymbol property, TypeRef typeRef) : base(property, public bool SetOnInit { get; } + public bool IsRequired { get; } + public override bool CanGet { get; } public override bool CanSet { get; } + + /// + /// Whether the property has a public init-only setter, so it is assignable post-construction only through an + /// [UnsafeAccessor] setter (or a reflection fallback downlevel) rather than a direct assignment. + /// + public bool CanSetViaAccessor { get; } + + /// + /// The declaring type an accessor for this property must target, when it differs from the type being bound (an + /// inherited property's setter is declared on a base type, and [UnsafeAccessor] resolves against the exact + /// type named). when the property is declared on the bound type itself. + /// + public TypeRef? AccessorDeclaringTypeRef { get; init; } + + /// + /// Whether the init-only setter accessor for this property can use [UnsafeAccessor] (the framework + /// supports it and, for a generic declaring type, supports generics). falls back to reflection. + /// + public bool SetterCanUseUnsafeAccessor { get; init; } + + /// Type-parameter names of the (generic) declaring type when a generic wrapper class is used for the setter (.NET 9+), otherwise . + public ImmutableEquatableArray? DeclaringTypeParameterNames { get; init; } + public string? OpenDeclaringTypeFQN { get; init; } + public string? OpenPropertyTypeFQN { get; init; } + public string? DeclaringTypeParameterConstraintClauses { get; init; } + + /// + /// The zero-based position of the property's declaring type in the bound type's inheritance hierarchy (the bound + /// type itself is 0, its base 1, and so on). Disambiguates the generic setter-accessor wrapper class between + /// members inherited from different generic base types. + /// + public int DeclaringTypeIndex { get; init; } } } 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 e435ffa0cd0b23..8bf9fed2bff33a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/SourceGenerationSpec.cs @@ -14,5 +14,11 @@ public sealed record SourceGenerationSpec public required bool EmitGenericParseEnum { get; set; } public required bool EmitNotNullIfNotNull { get; set; } public required bool EmitThrowIfNullMethod { get; set; } + + /// + /// Whether the compilation uses the updated memory-safety rules, under which [UnsafeAccessor] externs must + /// be marked safe. Threaded to the shared accessor emitter so it emits the correct modifier. + /// + public required bool UseUpdatedMemorySafetyRules { 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 index ff1e4f74bb32b0..5874038924d48a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/TypeIndex.cs @@ -61,6 +61,28 @@ private bool CanConstructElementsOf(CollectionSpec typeSpec) }; } + /// + /// Whether an Initialize method is generated for to construct it. This is the + /// case for a parameterized-constructor type (whose parameters are bound and passed to the constructor) and for + /// a parameterless-constructor type whose required members force construction through an accessor that bypasses + /// the required-member check. Init-only and required members are then set post-construction in BindCore + /// (only when their config key is present), not in an object initializer, so their defaults are preserved. + /// + public static bool HasInitializeMethod(ObjectSpec type) + { + if (type.InitExceptionMessage is not null) + { + return false; + } + + return type.InstantiationStrategy switch + { + ObjectInstantiationStrategy.ParameterizedConstructor => true, + ObjectInstantiationStrategy.ParameterlessConstructor => type.ConstructionRequiresAccessor, + _ => false, + }; + } + public bool ShouldBindTo(PropertySpec property) { if (property.IsIgnored || !IsAccessible()) 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 abc01258d4190c..4f966ce13fd79b 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 @@ -28,6 +28,36 @@ public ObjectSpec( public ImmutableEquatableArray? ConstructorParameters { get; } public string? InitExceptionMessage { get; } + + /// + /// Whether the constructor accessor for this type can use [UnsafeAccessor(Constructor)]. Requires the + /// framework to support [UnsafeAccessor] (.NET 8+); a generic type additionally requires generic + /// [UnsafeAccessor] support (.NET 9+) and that it is not nested in a generic type, in which case the extern + /// is emitted inside a generic wrapper class. When the constructor accessor uses a cached + /// . + /// + public bool ConstructorCanUseUnsafeAccessor { get; init; } + + /// Type-parameter names of the type when a generic constructor-accessor wrapper class is used (.NET 9+), otherwise . + public ImmutableEquatableArray? DeclaringTypeParameterNames { get; init; } + public string? OpenTypeFQN { get; init; } + public string? DeclaringTypeParameterConstraintClauses { get; init; } + + /// + /// Whether the type has required members that are not satisfied by a [SetsRequiredMembers] constructor, so + /// it cannot be created with a plain new T(...) (which would require an object initializer, CS9035). + /// Construction goes through an accessor ([UnsafeAccessor(Constructor)] or reflection) that bypasses the + /// check, then the required members are set post-construction, preserving their defaults for absent config keys. + /// + public bool ConstructionRequiresAccessor { get; init; } + + /// + /// Whether the type is a value type with required members not satisfied by a [SetsRequiredMembers] + /// constructor. Such a struct cannot be created with new T() (CS9035) and has no constructor an accessor + /// could target, so it is constructed with default(T) (which bypasses the required-member check) and its + /// required members are set post-construction. + /// + public bool ConstructValueTypeWithDefault { get; init; } } public enum ObjectInstantiationStrategy diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs index 7274a8d55a47a4..157234813a40a7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs @@ -339,6 +339,71 @@ public sealed class NestedForInitOnly public string Value { get; set; } } + public sealed class ClassWithInitOnlyCollectionParameterlessCtor + { + public List Items { get; init; } = new(); + public string Name { get; init; } + } + + public sealed class InitOnlyPropertiesWithNonNullDefaults + { + public string Name { get; init; } = "defaultName"; + public List Items { get; init; } = new() { "preset" }; + } + + public sealed class InitOnlyAssignFromSectionValueProperties + { + public string Text { get; init; } = "textDefault"; + public object Obj { get; init; } = "objDefault"; + } + + public class BaseWithInitOnlyProperty + { + public string BaseName { get; init; } = "baseDefault"; + } + + public sealed class DerivedWithInitOnlyProperty : BaseWithInitOnlyProperty + { + public string DerivedName { get; init; } = "derivedDefault"; + } + +#if NET + public sealed class RequiredPropertiesParameterlessCtor + { + public required string Name { get; set; } + public required NestedForInitOnly Child { get; set; } + } + + public sealed class RequiredInitPropertiesParameterlessCtor + { + public required string Name { get; init; } + public required NestedForInitOnly Child { get; init; } + } + + public sealed class RequiredPropertiesWithNonNullDefaults + { + public required string Name { get; set; } = "defaultName"; + public required List Items { get; init; } = new() { "preset" }; + } + + public record struct StructWithCtorParamAndRequiredMember(int A) + { + public required int B { get; set; } + } + + public struct StructWithExplicitParameterlessCtorAndRequiredMember + { + public int FromCtor; + public StructWithExplicitParameterlessCtorAndRequiredMember() { FromCtor = 42; } + public required int Req { get; set; } + } + + public sealed class GenericRequiredInitProperty + { + public required T Value { get; init; } + } +#endif + public readonly record struct ReadonlyRecordStructTypeOptions(string Color, int Length); public class ContainerWithNestedImmutableObject 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 40eed1215954cf..0c942bd386dfb0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -2052,6 +2052,241 @@ public void CanBindOnParametersAndProperties_InitOnlyComplexPropertyWithoutConst Assert.Equal("hello", result.Child.Value); } + /// + /// A parameterless-constructor type with an init-only collection property. The collection is set + /// post-construction through an accessor (an [UnsafeAccessor] setter, or reflection downlevel), bound + /// exactly once so its items are not duplicated, matching the reflection binder. + /// + [Fact] + public void CanBind_InitOnlyCollectionOnParameterlessConstructorType() + { + string json = """ + { + "Name": "n", + "Items": [ "a", "b" ] + } + """; + + IConfiguration config = TestHelpers.GetConfigurationFromJsonString(json); + + ClassWithInitOnlyCollectionParameterlessCtor result = config.Get(); + + Assert.Equal("n", result.Name); + Assert.Equal(new[] { "a", "b" }, result.Items); + } + + /// + /// When binding onto an already-existing instance of a parameterless-constructor type with an init-only + /// collection, the collection is set through an accessor post-construction. Its items must be appended into the + /// existing collection exactly once, not duplicated, matching the reflection binder. (Before the accessor-based + /// approach the source generator could not set an init-only member on an existing instance at all.) + /// + [Fact] + public void CanBindExistingInstance_InitOnlyCollectionOnParameterlessConstructorType() + { + string json = """ + { + "Items": [ "a", "b" ] + } + """; + + IConfiguration config = TestHelpers.GetConfigurationFromJsonString(json); + var instance = new ClassWithInitOnlyCollectionParameterlessCtor { Items = new List { "existing" } }; + + config.Bind(instance); + + Assert.Equal(new[] { "existing", "a", "b" }, instance.Items); + } + +#if NET + /// + /// A required nested complex property on a parameterless-constructor type is set after construction (which goes + /// through an accessor that bypasses the required-member check) and must be bound to the configured values, + /// whether the required property has a settable or an init-only setter. + /// + [Fact] + public void CanBind_RequiredNestedComplexOnParameterlessConstructorType_SettableSetter() + { + string json = """ + { + "Name": "n", + "Child": { "Value": "hello" } + } + """; + + IConfiguration config = TestHelpers.GetConfigurationFromJsonString(json); + + RequiredPropertiesParameterlessCtor result = config.Get(); + + Assert.Equal("n", result.Name); + Assert.Equal("hello", result.Child.Value); + } + + [Fact] + public void CanBind_RequiredNestedComplexOnParameterlessConstructorType_InitOnlySetter() + { + string json = """ + { + "Name": "n", + "Child": { "Value": "hello" } + } + """; + + IConfiguration config = TestHelpers.GetConfigurationFromJsonString(json); + + RequiredInitPropertiesParameterlessCtor result = config.Get(); + + Assert.Equal("n", result.Name); + Assert.Equal("hello", result.Child.Value); + } + + /// + /// Required members with a non-null field-initializer default are preserved when their config key is absent and + /// bound when present, identically to the reflection binder. The source generator constructs the instance + /// through an accessor that bypasses the required-member check (running the field initializers) and then sets + /// each required member only when its key is present, so the defaults survive. This covers both a required + /// settable property and a required init-only property. + /// + [Fact] + public void RequiredPropertiesWithNonNullDefaults_PreserveDefaults() + { + string present = """{ "Name": "n", "Items": [ "a", "b" ] }"""; + string missing = """{ "Unrelated": "x" }"""; + + RequiredPropertiesWithNonNullDefaults whenPresent = + TestHelpers.GetConfigurationFromJsonString(present).Get(); + RequiredPropertiesWithNonNullDefaults whenMissing = + TestHelpers.GetConfigurationFromJsonString(missing).Get(); + + Assert.Equal("n", whenPresent.Name); + Assert.Equal(new[] { "preset", "a", "b" }, whenPresent.Items); + Assert.Equal("defaultName", whenMissing.Name); + Assert.Equal(new[] { "preset" }, whenMissing.Items); + } + + /// + /// A value type with both a constructor parameter and a required member cannot be created with new S(a) (CS9035) + /// because the required member is not set. It is constructed through an accessor that passes the constructor + /// argument and bypasses the required-member check; the required member is then set post-construction. Both the + /// constructor-bound value and the required member must bind, matching the reflection binder. + /// + [Fact] + public void ValueTypeWithConstructorParameterAndRequiredMember_BindsBoth() + { + IConfiguration config = TestHelpers.GetConfigurationFromJsonString("""{ "A": 1, "B": 2 }"""); + + StructWithCtorParamAndRequiredMember result = config.Get(); + + Assert.Equal(1, result.A); + Assert.Equal(2, result.B); + } + + /// + /// A value type with an explicit (author-written) parameterless constructor and a required member must have its + /// constructor run during binding, matching the reflection binder (which uses Activator.CreateInstance). The + /// source generator constructs it through an accessor rather than default(T) (which would skip the constructor). + /// + [Fact] + public void ValueTypeWithExplicitParameterlessCtorAndRequiredMember_RunsConstructor() + { + IConfiguration config = TestHelpers.GetConfigurationFromJsonString("""{ "Req": 7 }"""); + + StructWithExplicitParameterlessCtorAndRequiredMember result = + config.Get(); + + Assert.Equal(42, result.FromCtor); + Assert.Equal(7, result.Req); + } + + /// + /// A generic type with a required init-only member is constructed through an accessor that bypasses the + /// required-member check, then the member is set post-construction. On frameworks with generic + /// [UnsafeAccessor] support the constructor and setter accessors are emitted inside a generic wrapper + /// class; otherwise the reflection fallback is used. Both must bind the member, matching the reflection binder. + /// + [Fact] + public void CanBind_GenericTypeWithRequiredInitMember() + { + IConfiguration config = TestHelpers.GetConfigurationFromJsonString("""{ "Value": "hello" }"""); + + GenericRequiredInitProperty result = config.Get>(); + + Assert.Equal("hello", result.Value); + } +#endif + + /// + /// An init-only property with a non-null field-initializer default is now bound identically by the reflection + /// binder and the source generator: the instance is constructed (running the field initializer) and the + /// property is set post-construction only when its config key is present. A configured value is layered over the + /// existing default (appended, for a collection), and an absent key preserves the default. The source generator + /// achieves this by setting init-only members through an [UnsafeAccessor] setter (or reflection downlevel) + /// rather than an object initializer, so the default it cannot observe is never overwritten. + /// + [Fact] + public void InitOnlyPropertyWithNonNullDefault_PreservesDefault() + { + string present = """{ "Name": "n", "Items": [ "a", "b" ] }"""; + string missing = """{ "Unrelated": "x" }"""; + + InitOnlyPropertiesWithNonNullDefaults whenPresent = + TestHelpers.GetConfigurationFromJsonString(present).Get(); + InitOnlyPropertiesWithNonNullDefaults whenMissing = + TestHelpers.GetConfigurationFromJsonString(missing).Get(); + + // The field-initializer default is preserved: layered over when present, kept when absent. + Assert.Equal("n", whenPresent.Name); + Assert.Equal(new[] { "preset", "a", "b" }, whenPresent.Items); + Assert.Equal("defaultName", whenMissing.Name); + Assert.Equal(new[] { "preset" }, whenMissing.Items); + } + + /// + /// An init-only property inherited from a base type is set post-construction like any other init-only property. + /// The source generator's setter must target the base type that declares the setter (through an + /// extern against the base type, or the + /// reflection fallback downlevel), so both the inherited and the derived init-only members bind, and each + /// preserves its default when its config key is absent - matching the reflection binder. + /// + [Fact] + public void CanBind_InheritedInitOnlyProperty() + { + string present = """{ "BaseName": "b", "DerivedName": "d" }"""; + string missing = """{ "Unrelated": "x" }"""; + + DerivedWithInitOnlyProperty whenPresent = + TestHelpers.GetConfigurationFromJsonString(present).Get(); + DerivedWithInitOnlyProperty whenMissing = + TestHelpers.GetConfigurationFromJsonString(missing).Get(); + + Assert.Equal("b", whenPresent.BaseName); + Assert.Equal("d", whenPresent.DerivedName); + Assert.Equal("baseDefault", whenMissing.BaseName); + Assert.Equal("derivedDefault", whenMissing.DerivedName); + } + + /// + /// An init-only property of an AssignFromSectionValue type ( or ) + /// takes the section value as-is. An empty string is a real value, so it is set (overwriting a non-null default) + /// rather than treated as absent, while a missing key preserves the default - matching the reflection binder. + /// + [Fact] + public void CanBind_InitOnlyAssignFromSectionValueProperty() + { + string present = """{ "Text": "", "Obj": "hi" }"""; + string missing = """{ "Unrelated": "x" }"""; + + InitOnlyAssignFromSectionValueProperties whenPresent = + TestHelpers.GetConfigurationFromJsonString(present).Get(); + InitOnlyAssignFromSectionValueProperties whenMissing = + TestHelpers.GetConfigurationFromJsonString(missing).Get(); + + Assert.Equal("", whenPresent.Text); + Assert.Equal("hi", whenPresent.Obj); + Assert.Equal("textDefault", whenMissing.Text); + Assert.Equal("objDefault", whenMissing.Obj); + } + public static IEnumerable Configuration_TestData() { yield return new object[] diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt index c86526984b3d40..3c4707ac4f08e9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind.generated.txt @@ -54,7 +54,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. [InterceptsLocation(1, "/TzDbopkyui/vWzNJfmpq2YBAABzcmMtMC5jcw==")] // src-0.cs(13,20) - public static void Bind_Program__MyClass(this IConfiguration configuration, object? instance, Action? configureOptions) + public static void Bind_Program__MyClass(this IConfiguration configuration, object? instance, global::System.Action? configureOptions) { if (configuration is null) { @@ -212,7 +212,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -242,7 +242,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt index 5bc1ad637bd7b5..d79c86f013c988 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance.generated.txt @@ -176,7 +176,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt index c6c7feafc3cd66..d77c79d5691ccb 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IConfiguration extensions. /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. [InterceptsLocation(1, "7CkSkJNSgE0UIXT00R2Pg2ABAABzcmMtMC5jcw==")] // src-0.cs(12,20) - public static void Bind_Program__MyClass(this IConfiguration configuration, object? instance, Action? configureOptions) + public static void Bind_Program__MyClass(this IConfiguration configuration, object? instance, global::System.Action? configureOptions) { if (configuration is null) { @@ -176,7 +176,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -206,7 +206,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt index 27ada27eeb2f74..5ec6cbb16f6dcd 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt @@ -176,7 +176,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt index 39ebfc9934c8de..2dc60d59cc863d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt @@ -46,7 +46,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. [InterceptsLocation(1, "T1w3acs59wB13yghJH3pNpACAABzcmMtMC5jcw==")] // src-0.cs(23,16) - public static void Bind_Program__MyClass1(this IConfiguration configuration, object? instance, Action? configureOptions) + public static void Bind_Program__MyClass1(this IConfiguration configuration, object? instance, global::System.Action? configureOptions) { if (configuration is null) { @@ -86,7 +86,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return value != null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get.generated.txt index 07b5468ceea345..430aa7f14713d9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get.generated.txt @@ -40,7 +40,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "ybRgWwDRfhqdJiXObv1EptsBAABzcmMtMC5jcw==")] // src-0.cs(14,36) - public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + public static T? Get(this IConfiguration configuration, global::System.Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "ybRgWwDRfhqdJiXObv1EpqEBAABzcmMtMC5jcw==")] // src-0.cs(13,56) @@ -48,7 +48,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "ybRgWwDRfhqdJiXObv1Epi0CAABzcmMtMC5jcw==")] // src-0.cs(15,47) - public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + public static object? Get(this IConfiguration configuration, Type type, global::System.Action? configureOptions) => GetCore(configuration, type, configureOptions); #endregion IConfiguration extensions. #region Core binding extensions. @@ -234,7 +234,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -273,7 +273,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt index 2d4f4da184a350..3fb6bf07bcacd0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt @@ -39,7 +39,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "t6tvUrc1mCV/SdIkmk1VLDIBAABzcmMtMC5jcw==")] // src-0.cs(12,16) - public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + public static T? Get(this IConfiguration configuration, global::System.Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "t6tvUrc1mCV/SdIkmk1VLA4BAABzcmMtMC5jcw==")] // src-0.cs(11,16) @@ -47,7 +47,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "t6tvUrc1mCV/SdIkmk1VLGMBAABzcmMtMC5jcw==")] // src-0.cs(13,16) - public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + public static object? Get(this IConfiguration configuration, Type type, global::System.Action? configureOptions) => GetCore(configuration, type, configureOptions); #endregion IConfiguration extensions. #region Core binding extensions. @@ -152,7 +152,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt index 2067a714833529..6f86faff9c543d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T.generated.txt @@ -198,7 +198,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -237,7 +237,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt index 7f443cb8ec7c26..28e466e3fad073 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IConfiguration extensions. /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "G/3QeDCtvtw8eMmVQBFB100BAABzcmMtMC5jcw==")] // src-0.cs(11,40) - public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + public static T? Get(this IConfiguration configuration, global::System.Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); #endregion IConfiguration extensions. #region Core binding extensions. @@ -198,7 +198,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -237,7 +237,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt index aa8ad68a97fb6f..cc5f2cd238f4fc 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf.generated.txt @@ -97,7 +97,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -136,7 +136,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt index b95a885ec261a7..ef8801a8081ed7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IConfiguration extensions. /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "E7RMXqTP0g6z7RR21Qk0nTkBAABzcmMtMC5jcw==")] // src-0.cs(11,20) - public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + public static object? Get(this IConfiguration configuration, Type type, global::System.Action? configureOptions) => GetCore(configuration, type, configureOptions); #endregion IConfiguration extensions. #region Core binding extensions. @@ -97,7 +97,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -136,7 +136,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt index 0a2c4f8f3f95a8..a9a6caeec0cce0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfiguration.generated.txt @@ -38,7 +38,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region global::Microsoft.Extensions.Options.OptionsBuilder extensions. /// Registers the dependency injection container to bind against the obtained from the DI service provider. [InterceptsLocation(1, "dQtLTBW+V+KExKSKfSGYWHgBAABzcmMtMC5jcw==")] // src-0.cs(12,24) - public static global::Microsoft.Extensions.Options.OptionsBuilder BindConfiguration(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, string configSectionPath, Action? configureBinder = null) where TOptions : class + public static global::Microsoft.Extensions.Options.OptionsBuilder BindConfiguration(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, string configSectionPath, global::System.Action? configureBinder = null) where TOptions : class { if (optionsBuilder is null) { @@ -73,7 +73,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -168,7 +168,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -207,7 +207,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt index b357b97fc261a0..cec2b460b6cdc7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt @@ -38,7 +38,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region global::Microsoft.Extensions.Options.OptionsBuilder extensions. /// Registers the dependency injection container to bind against the obtained from the DI service provider. [InterceptsLocation(1, "+juUY8RZzi0MOViHimSQBXgBAABzcmMtMC5jcw==")] // src-0.cs(12,24) - public static global::Microsoft.Extensions.Options.OptionsBuilder BindConfiguration(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, string configSectionPath, Action? configureBinder = null) where TOptions : class + public static global::Microsoft.Extensions.Options.OptionsBuilder BindConfiguration(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, string configSectionPath, global::System.Action? configureBinder = null) where TOptions : class { if (optionsBuilder is null) { @@ -73,7 +73,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -168,7 +168,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -207,7 +207,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt index 013ef9e450173f..5169c6624cd48d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T.generated.txt @@ -44,7 +44,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } /// Registers a configuration instance which will bind against. - public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, IConfiguration config, Action? configureBinder) where TOptions : class + public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, IConfiguration config, global::System.Action? configureBinder) where TOptions : class { if (optionsBuilder is null) { @@ -58,7 +58,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IServiceCollection extensions. /// Registers a configuration instance which TOptions will bind against. - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { if (services is null) { @@ -79,7 +79,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -174,7 +174,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -213,7 +213,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt index eccfa299c31c60..fe8a2778fe50d0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt @@ -38,7 +38,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region global::Microsoft.Extensions.Options.OptionsBuilder extensions. /// Registers a configuration instance which will bind against. [InterceptsLocation(1, "eAEQHTx/qUZMzyaWapEG3uEBAABzcmMtMC5jcw==")] // src-0.cs(15,24) - public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, IConfiguration config, Action? configureBinder) where TOptions : class + public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, IConfiguration config, global::System.Action? configureBinder) where TOptions : class { if (optionsBuilder is null) { @@ -52,7 +52,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IServiceCollection extensions. /// Registers a configuration instance which TOptions will bind against. - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { if (services is null) { @@ -73,7 +73,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -168,7 +168,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -207,7 +207,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt index c7848d855b7ebd..22f062ce56a503 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T.generated.txt @@ -45,7 +45,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } /// Registers a configuration instance which TOptions will bind against. - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { if (services is null) { @@ -67,7 +67,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration private readonly static Lazy> s_configKeys_Program__MyClass2 = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -230,7 +230,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -269,7 +269,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt index 074cead2a39a8b..08dd65c30818bf 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt @@ -39,13 +39,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IServiceCollection extensions. /// Registers a configuration instance which TOptions will bind against. [InterceptsLocation(1, "rlE/o+amAiG6/WEDLRfo0rcBAABzcmMtMC5jcw==")] // src-0.cs(14,18) - public static IServiceCollection Configure(this IServiceCollection services, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { return Configure(services, string.Empty, config, configureOptions); } /// Registers a configuration instance which TOptions will bind against. - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { if (services is null) { @@ -67,7 +67,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration private readonly static Lazy> s_configKeys_Program__MyClass2 = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -230,7 +230,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -269,7 +269,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt index 8500b226e943a5..3fa69029becb11 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name.generated.txt @@ -45,7 +45,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } /// Registers a configuration instance which TOptions will bind against. - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { if (services is null) { @@ -67,7 +67,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration private readonly static Lazy> s_configKeys_Program__MyClass2 = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -230,7 +230,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -269,7 +269,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt index 8a6cbcdd238908..7c04ba63f99c43 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt @@ -39,7 +39,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IServiceCollection extensions. /// Registers a configuration instance which TOptions will bind against. [InterceptsLocation(1, "GVTnXyPUwpMq46hK7kk0ULcBAABzcmMtMC5jcw==")] // src-0.cs(14,18) - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { if (services is null) { @@ -61,7 +61,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration private readonly static Lazy> s_configKeys_Program__MyClass2 = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -224,7 +224,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -263,7 +263,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt index 668cbc6f836bd0..2ae3129fdd7736 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Collections.generated.txt @@ -228,7 +228,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -267,7 +267,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt index 7fa4bc64ab7d19..41e82f3d50ef03 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/DefaultConstructorParameters.generated.txt @@ -220,7 +220,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt index f4e7d87294530a..256e881685f1e5 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/EmptyConfigType.generated.txt @@ -101,7 +101,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/GetterOnlyCollectionProperties.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/GetterOnlyCollectionProperties.generated.txt index 6206a7f9e947e6..dcb78596c22f4f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/GetterOnlyCollectionProperties.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/GetterOnlyCollectionProperties.generated.txt @@ -106,7 +106,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Primitives.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Primitives.generated.txt index c04c278e695ee9..d45bf55f5e77ba 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Primitives.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/Primitives.generated.txt @@ -437,7 +437,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt index 57ebb4a3cdc268..6ac1d7019d2348 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/net462/Version1/UnsupportedTypes.generated.txt @@ -38,7 +38,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXPoBAABzcmMtMC5jcw==")] // src-0.cs(18,23) [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXFICAABzcmMtMC5jcw==")] // src-0.cs(21,23) [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXH0CAABzcmMtMC5jcw==")] // src-0.cs(22,15) - public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + public static T? Get(this IConfiguration configuration, global::System.Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXNsBAABzcmMtMC5jcw==")] // src-0.cs(17,23) @@ -121,18 +121,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration temp1.CopyTo(instance, originalCount); } - public static void BindCore(IConfiguration configuration, ref global::Record instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref global::Record instance, bool defaultValueIfNotFound, BinderOptions? binderOptions, bool boundThroughConstructor = false) { ValidateConfigurationKeys(typeof(global::Record), s_configKeys_Record___System__Action___, configuration, binderOptions); + if (!boundThroughConstructor) + { + + int temp2 = instance.x; + + if (TryGetConfigurationValue(configuration, key: "x", out string? value3)) + { + if (!string.IsNullOrEmpty(value3)) + { + temp2 = ParseInt(value3, configuration.GetSection("x").Path); + } + } + if (HasValueOrChildren(configuration.GetSection("x"))) + { + __set_Record___System__Action____x(instance, temp2); + } + } + else + { + __set_Record___System__Action____x(instance, instance.x); + } } public static void BindCore(IConfiguration configuration, ref global::Options instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(global::Options), s_configKeys_Options, configuration, binderOptions); - if (TryGetConfigurationValue(configuration, key: "Name", out string? value2)) + if (TryGetConfigurationValue(configuration, key: "Name", out string? value4)) { - instance.Name = value2; + instance.Name = value4; } else if (defaultValueIfNotFound) { @@ -143,11 +164,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (TryGetConfigurationValue(configuration, key: "Age", out string? value3)) + if (TryGetConfigurationValue(configuration, key: "Age", out string? value5)) { - if (!string.IsNullOrEmpty(value3)) + if (!string.IsNullOrEmpty(value5)) { - instance.Age = ParseInt(value3, configuration.GetSection("Age").Path); + instance.Age = ParseInt(value5, configuration.GetSection("Age").Path); } } else if (defaultValueIfNotFound) @@ -155,43 +176,44 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration instance.Age = instance.Age; } - var value4 = configuration.GetSection("List"); - if (AsConfigWithChildren(value4) is IConfigurationSection section5) + var value6 = configuration.GetSection("List"); + if (AsConfigWithChildren(value6) is IConfigurationSection section7) { - global::System.Collections.Generic.List? temp7 = instance.List; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.List = temp7; + global::System.Collections.Generic.List? temp9 = instance.List; + temp9 ??= new global::System.Collections.Generic.List(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.List = temp9; } else { instance.List = instance.List; } - var value8 = configuration.GetSection("Array"); - if (AsConfigWithChildren(value8) is IConfigurationSection section9) + var value10 = configuration.GetSection("Array"); + if (AsConfigWithChildren(value10) is IConfigurationSection section11) { - string[]? temp11 = instance.Array; - temp11 ??= new string[0]; - BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); - instance.Array = temp11; + string[]? temp13 = instance.Array; + temp13 ??= new string[0]; + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.Array = temp13; } else { instance.Array = instance.Array; } - if (instance.Array is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + if (instance.Array is null && TryGetConfigurationValue(value10, key: null, out string? value14) && value14 == string.Empty) { instance.Array = global::System.Array.Empty(); } - var value13 = configuration.GetSection("Record"); - if (AsConfigWithChildren(value13) is IConfigurationSection section14) + var value15 = configuration.GetSection("Record"); + if (AsConfigWithChildren(value15) is IConfigurationSection section16) { - global::Record? temp16 = instance.Record; - temp16 ??= InitializeRecord___System__Action___(section14, binderOptions); - BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); - instance.Record = temp16; + global::Record? temp18 = instance.Record; + bool wasNull19 = temp18 is null; + temp18 ??= InitializeRecord___System__Action___(section16, binderOptions); + BindCore(section16, ref temp18, defaultValueIfNotFound: false, binderOptions, boundThroughConstructor: wasNull19); + instance.Record = temp18; } else { @@ -202,17 +224,22 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static global::Record InitializeRecord___System__Action___(IConfiguration configuration, BinderOptions? binderOptions) { int x = (int)(10); - if (TryGetConfigurationValue(configuration, key: "x", out string? value17) && value17 is not null) + if (TryGetConfigurationValue(configuration, key: "x", out string? value20) && value20 is not null) { - x = ParseInt(value17, configuration.GetSection("x").Path); + x = ParseInt(value20, configuration.GetSection("x").Path); } - return new global::Record(x) - { - x = x, - }; + return new global::Record(x); } + private static global::System.Action, int>? s_set_Record___System__Action____x; + private static void __set_Record___System__Action____x(global::Record obj, int value) => (s_set_Record___System__Action____x ??= (global::System.Action, int>)global::System.Delegate.CreateDelegate(typeof(global::System.Action, int>), typeof(global::Record).GetProperty("x", InstanceMemberBindingFlags, null, typeof(int), global::System.Array.Empty(), null)!.GetSetMethod(true)!))(obj, value); + + private const global::System.Reflection.BindingFlags InstanceMemberBindingFlags = + global::System.Reflection.BindingFlags.Instance | + global::System.Reflection.BindingFlags.Public | + global::System.Reflection.BindingFlags.NonPublic; + /// Tries to get the configuration value for the specified key. public static bool TryGetConfigurationValue(IConfiguration configuration, string key, out string? value) { @@ -227,7 +254,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -266,7 +293,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt index 7f4fd63b274009..04aa12ccbf9502 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind.generated.txt @@ -51,7 +51,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. [InterceptsLocation(1, "/TzDbopkyui/vWzNJfmpq2YBAABzcmMtMC5jcw==")] // src-0.cs(13,20) - public static void Bind_Program__MyClass(this IConfiguration configuration, object? instance, Action? configureOptions) + public static void Bind_Program__MyClass(this IConfiguration configuration, object? instance, global::System.Action? configureOptions) { ArgumentNullException.ThrowIfNull(configuration); @@ -203,7 +203,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -233,7 +233,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt index 60a5eb1103e120..9354369a32fc0b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance.generated.txt @@ -173,7 +173,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt index fcd3c5465d9021..46b694779299a0 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Instance_BinderOptions.generated.txt @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IConfiguration extensions. /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. [InterceptsLocation(1, "7CkSkJNSgE0UIXT00R2Pg2ABAABzcmMtMC5jcw==")] // src-0.cs(12,20) - public static void Bind_Program__MyClass(this IConfiguration configuration, object? instance, Action? configureOptions) + public static void Bind_Program__MyClass(this IConfiguration configuration, object? instance, global::System.Action? configureOptions) { ArgumentNullException.ThrowIfNull(configuration); @@ -173,7 +173,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -203,7 +203,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt index 88c3f160a93ba6..fdaed1bbe3d583 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_Key_Instance.generated.txt @@ -173,7 +173,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt index f54ae1c6adc12f..059cd97ef9d0b3 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Bind_ParseTypeFromMethodParam.generated.txt @@ -43,7 +43,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. [InterceptsLocation(1, "T1w3acs59wB13yghJH3pNpACAABzcmMtMC5jcw==")] // src-0.cs(23,16) - public static void Bind_Program__MyClass1(this IConfiguration configuration, object? instance, Action? configureOptions) + public static void Bind_Program__MyClass1(this IConfiguration configuration, object? instance, global::System.Action? configureOptions) { ArgumentNullException.ThrowIfNull(configuration); @@ -77,7 +77,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return value != null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get.generated.txt index 99e17ad2ff8418..526aaaa2d04b99 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get.generated.txt @@ -40,7 +40,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "ybRgWwDRfhqdJiXObv1EptsBAABzcmMtMC5jcw==")] // src-0.cs(14,36) - public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + public static T? Get(this IConfiguration configuration, global::System.Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "ybRgWwDRfhqdJiXObv1EpqEBAABzcmMtMC5jcw==")] // src-0.cs(13,56) @@ -48,7 +48,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "ybRgWwDRfhqdJiXObv1Epi0CAABzcmMtMC5jcw==")] // src-0.cs(15,47) - public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + public static object? Get(this IConfiguration configuration, Type type, global::System.Action? configureOptions) => GetCore(configuration, type, configureOptions); #endregion IConfiguration extensions. #region Core binding extensions. @@ -231,7 +231,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -270,7 +270,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt index 5dab04cdc700f8..7a9b6a887c57f9 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_PrimitivesOnly.generated.txt @@ -39,7 +39,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "t6tvUrc1mCV/SdIkmk1VLDIBAABzcmMtMC5jcw==")] // src-0.cs(12,16) - public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + public static T? Get(this IConfiguration configuration, global::System.Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "t6tvUrc1mCV/SdIkmk1VLA4BAABzcmMtMC5jcw==")] // src-0.cs(11,16) @@ -47,7 +47,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "t6tvUrc1mCV/SdIkmk1VLGMBAABzcmMtMC5jcw==")] // src-0.cs(13,16) - public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + public static object? Get(this IConfiguration configuration, Type type, global::System.Action? configureOptions) => GetCore(configuration, type, configureOptions); #endregion IConfiguration extensions. #region Core binding extensions. @@ -149,7 +149,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt index 437bce7d135008..d78287111bee27 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T.generated.txt @@ -195,7 +195,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -234,7 +234,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt index 654d5d65e0192b..cc36e13db5d862 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_T_BinderOptions.generated.txt @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IConfiguration extensions. /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "G/3QeDCtvtw8eMmVQBFB100BAABzcmMtMC5jcw==")] // src-0.cs(11,40) - public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + public static T? Get(this IConfiguration configuration, global::System.Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); #endregion IConfiguration extensions. #region Core binding extensions. @@ -195,7 +195,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -234,7 +234,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt index 08b582ec3f93d9..686a3940f34f79 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf.generated.txt @@ -94,7 +94,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -133,7 +133,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt index e7a5e8ac0194eb..a910351e1d92ac 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ConfigurationBinder/Version1/Get_TypeOf_BinderOptions.generated.txt @@ -36,7 +36,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IConfiguration extensions. /// Attempts to bind the configuration instance to a new instance of type T. [InterceptsLocation(1, "E7RMXqTP0g6z7RR21Qk0nTkBAABzcmMtMC5jcw==")] // src-0.cs(11,20) - public static object? Get(this IConfiguration configuration, Type type, Action? configureOptions) => GetCore(configuration, type, configureOptions); + public static object? Get(this IConfiguration configuration, Type type, global::System.Action? configureOptions) => GetCore(configuration, type, configureOptions); #endregion IConfiguration extensions. #region Core binding extensions. @@ -94,7 +94,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -133,7 +133,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt index d66b850ec73a64..7643cff0980634 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfiguration.generated.txt @@ -38,7 +38,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region global::Microsoft.Extensions.Options.OptionsBuilder extensions. /// Registers the dependency injection container to bind against the obtained from the DI service provider. [InterceptsLocation(1, "dQtLTBW+V+KExKSKfSGYWHgBAABzcmMtMC5jcw==")] // src-0.cs(12,24) - public static global::Microsoft.Extensions.Options.OptionsBuilder BindConfiguration(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, string configSectionPath, Action? configureBinder = null) where TOptions : class + public static global::Microsoft.Extensions.Options.OptionsBuilder BindConfiguration(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, string configSectionPath, global::System.Action? configureBinder = null) where TOptions : class { ArgumentNullException.ThrowIfNull(optionsBuilder); @@ -64,7 +64,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -159,7 +159,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -198,7 +198,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt index 32ee775fe8bb0b..e8e1b36f6e3a10 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/BindConfigurationWithConfigureActions.generated.txt @@ -38,7 +38,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region global::Microsoft.Extensions.Options.OptionsBuilder extensions. /// Registers the dependency injection container to bind against the obtained from the DI service provider. [InterceptsLocation(1, "+juUY8RZzi0MOViHimSQBXgBAABzcmMtMC5jcw==")] // src-0.cs(12,24) - public static global::Microsoft.Extensions.Options.OptionsBuilder BindConfiguration(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, string configSectionPath, Action? configureBinder = null) where TOptions : class + public static global::Microsoft.Extensions.Options.OptionsBuilder BindConfiguration(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, string configSectionPath, global::System.Action? configureBinder = null) where TOptions : class { ArgumentNullException.ThrowIfNull(optionsBuilder); @@ -64,7 +64,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -159,7 +159,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -198,7 +198,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt index c152aae35ed352..cce3586b51d909 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T.generated.txt @@ -44,7 +44,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } /// Registers a configuration instance which will bind against. - public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, IConfiguration config, Action? configureBinder) where TOptions : class + public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, IConfiguration config, global::System.Action? configureBinder) where TOptions : class { ArgumentNullException.ThrowIfNull(optionsBuilder); @@ -55,7 +55,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IServiceCollection extensions. /// Registers a configuration instance which TOptions will bind against. - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { ArgumentNullException.ThrowIfNull(services); @@ -70,7 +70,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -165,7 +165,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -204,7 +204,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt index b6d2f75982baaf..cbd744cfd3f9bb 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/OptionsBuilder/Version1/Bind_T_BinderOptions.generated.txt @@ -38,7 +38,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region global::Microsoft.Extensions.Options.OptionsBuilder extensions. /// Registers a configuration instance which will bind against. [InterceptsLocation(1, "eAEQHTx/qUZMzyaWapEG3uEBAABzcmMtMC5jcw==")] // src-0.cs(15,24) - public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, IConfiguration config, Action? configureBinder) where TOptions : class + public static global::Microsoft.Extensions.Options.OptionsBuilder Bind(this global::Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, IConfiguration config, global::System.Action? configureBinder) where TOptions : class { ArgumentNullException.ThrowIfNull(optionsBuilder); @@ -49,7 +49,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IServiceCollection extensions. /// Registers a configuration instance which TOptions will bind against. - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { ArgumentNullException.ThrowIfNull(services); @@ -64,7 +64,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region Core binding extensions. private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -159,7 +159,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -198,7 +198,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt index d9645e2b579072..ee576cf444a774 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T.generated.txt @@ -45,7 +45,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } /// Registers a configuration instance which TOptions will bind against. - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { ArgumentNullException.ThrowIfNull(services); @@ -61,7 +61,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration private readonly static Lazy> s_configKeys_Program__MyClass2 = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -224,7 +224,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -263,7 +263,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt index 065a4d45a779f2..028593adca3812 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_BinderOptions.generated.txt @@ -39,13 +39,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IServiceCollection extensions. /// Registers a configuration instance which TOptions will bind against. [InterceptsLocation(1, "rlE/o+amAiG6/WEDLRfo0rcBAABzcmMtMC5jcw==")] // src-0.cs(14,18) - public static IServiceCollection Configure(this IServiceCollection services, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { return Configure(services, string.Empty, config, configureOptions); } /// Registers a configuration instance which TOptions will bind against. - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { ArgumentNullException.ThrowIfNull(services); @@ -61,7 +61,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration private readonly static Lazy> s_configKeys_Program__MyClass2 = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -224,7 +224,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -263,7 +263,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt index eeafcceb582d8c..5897818d81e60e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name.generated.txt @@ -45,7 +45,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } /// Registers a configuration instance which TOptions will bind against. - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { ArgumentNullException.ThrowIfNull(services); @@ -61,7 +61,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration private readonly static Lazy> s_configKeys_Program__MyClass2 = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -224,7 +224,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -263,7 +263,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt index 6bcaeac55f93a6..425cac15ce3441 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/ServiceCollection/Version1/Configure_T_name_BinderOptions.generated.txt @@ -39,7 +39,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration #region IServiceCollection extensions. /// Registers a configuration instance which TOptions will bind against. [InterceptsLocation(1, "GVTnXyPUwpMq46hK7kk0ULcBAABzcmMtMC5jcw==")] // src-0.cs(14,18) - public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, Action? configureOptions) where TOptions : class + public static IServiceCollection Configure(this IServiceCollection services, string? name, IConfiguration config, global::System.Action? configureOptions) where TOptions : class { ArgumentNullException.ThrowIfNull(services); @@ -55,7 +55,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration private readonly static Lazy> s_configKeys_Program__MyClass2 = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyInt" }); private readonly static Lazy> s_configKeys_Program__MyClass = new(() => new global::System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" }); - public static void BindCoreMain(IConfiguration configuration, object instance, Type type, Action? configureOptions) + public static void BindCoreMain(IConfiguration configuration, object instance, Type type, global::System.Action? configureOptions) { if (instance is null) { @@ -218,7 +218,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -257,7 +257,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt index 5de3a0ef86dd9d..7d0fad6640541f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Collections.generated.txt @@ -225,7 +225,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -264,7 +264,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt index 0584ef27a06710..1f61c3c5baa633 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/DefaultConstructorParameters.generated.txt @@ -217,7 +217,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt index 71153b9171f615..612411589ae496 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/EmptyConfigType.generated.txt @@ -92,7 +92,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/GetterOnlyCollectionProperties.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/GetterOnlyCollectionProperties.generated.txt index ff53c139b147d0..ff8dae0ef46710 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/GetterOnlyCollectionProperties.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/GetterOnlyCollectionProperties.generated.txt @@ -103,7 +103,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Primitives.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Primitives.generated.txt index 376e88d4635d13..be9b2ddbdb89de 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Primitives.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/Primitives.generated.txt @@ -494,7 +494,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt index 3f3222a95fbffc..5a184dbf0a29bb 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/netcoreapp/Version1/UnsupportedTypes.generated.txt @@ -38,7 +38,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXPoBAABzcmMtMC5jcw==")] // src-0.cs(18,23) [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXFICAABzcmMtMC5jcw==")] // src-0.cs(21,23) [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXH0CAABzcmMtMC5jcw==")] // src-0.cs(22,15) - public static T? Get(this IConfiguration configuration, Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); + public static T? Get(this IConfiguration configuration, global::System.Action? configureOptions) => (T?)(GetCore(configuration, typeof(T), configureOptions) ?? default(T)); /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. [InterceptsLocation(1, "R+J95cfVt+aeGOS40ZJnXNsBAABzcmMtMC5jcw==")] // src-0.cs(17,23) @@ -115,18 +115,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration temp1.CopyTo(instance, originalCount); } - public static void BindCore(IConfiguration configuration, ref global::Record instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) + public static void BindCore(IConfiguration configuration, ref global::Record instance, bool defaultValueIfNotFound, BinderOptions? binderOptions, bool boundThroughConstructor = false) { ValidateConfigurationKeys(typeof(global::Record), s_configKeys_Record___System__Action___, configuration, binderOptions); + if (!boundThroughConstructor) + { + + int temp2 = instance.x; + + if (TryGetConfigurationValue(configuration, key: "x", out string? value3)) + { + if (!string.IsNullOrEmpty(value3)) + { + temp2 = ParseInt(value3, configuration.GetSection("x").Path); + } + } + if (HasValueOrChildren(configuration.GetSection("x"))) + { + __GenericAccessors_Record___System__Action____0.__set_Record___System__Action____x(instance, temp2); + } + } + else + { + __GenericAccessors_Record___System__Action____0.__set_Record___System__Action____x(instance, instance.x); + } } public static void BindCore(IConfiguration configuration, ref global::Options instance, bool defaultValueIfNotFound, BinderOptions? binderOptions) { ValidateConfigurationKeys(typeof(global::Options), s_configKeys_Options, configuration, binderOptions); - if (TryGetConfigurationValue(configuration, key: "Name", out string? value2)) + if (TryGetConfigurationValue(configuration, key: "Name", out string? value4)) { - instance.Name = value2; + instance.Name = value4; } else if (defaultValueIfNotFound) { @@ -137,11 +158,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration } } - if (TryGetConfigurationValue(configuration, key: "Age", out string? value3)) + if (TryGetConfigurationValue(configuration, key: "Age", out string? value5)) { - if (!string.IsNullOrEmpty(value3)) + if (!string.IsNullOrEmpty(value5)) { - instance.Age = ParseInt(value3, configuration.GetSection("Age").Path); + instance.Age = ParseInt(value5, configuration.GetSection("Age").Path); } } else if (defaultValueIfNotFound) @@ -149,43 +170,44 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration instance.Age = instance.Age; } - var value4 = configuration.GetSection("List"); - if (AsConfigWithChildren(value4) is IConfigurationSection section5) + var value6 = configuration.GetSection("List"); + if (AsConfigWithChildren(value6) is IConfigurationSection section7) { - global::System.Collections.Generic.List? temp7 = instance.List; - temp7 ??= new global::System.Collections.Generic.List(); - BindCore(section5, ref temp7, defaultValueIfNotFound: false, binderOptions); - instance.List = temp7; + global::System.Collections.Generic.List? temp9 = instance.List; + temp9 ??= new global::System.Collections.Generic.List(); + BindCore(section7, ref temp9, defaultValueIfNotFound: false, binderOptions); + instance.List = temp9; } else { instance.List = instance.List; } - var value8 = configuration.GetSection("Array"); - if (AsConfigWithChildren(value8) is IConfigurationSection section9) + var value10 = configuration.GetSection("Array"); + if (AsConfigWithChildren(value10) is IConfigurationSection section11) { - string[]? temp11 = instance.Array; - temp11 ??= new string[0]; - BindCore(section9, ref temp11, defaultValueIfNotFound: false, binderOptions); - instance.Array = temp11; + string[]? temp13 = instance.Array; + temp13 ??= new string[0]; + BindCore(section11, ref temp13, defaultValueIfNotFound: false, binderOptions); + instance.Array = temp13; } else { instance.Array = instance.Array; } - if (instance.Array is null && TryGetConfigurationValue(value8, key: null, out string? value12) && value12 == string.Empty) + if (instance.Array is null && TryGetConfigurationValue(value10, key: null, out string? value14) && value14 == string.Empty) { instance.Array = global::System.Array.Empty(); } - var value13 = configuration.GetSection("Record"); - if (AsConfigWithChildren(value13) is IConfigurationSection section14) + var value15 = configuration.GetSection("Record"); + if (AsConfigWithChildren(value15) is IConfigurationSection section16) { - global::Record? temp16 = instance.Record; - temp16 ??= InitializeRecord___System__Action___(section14, binderOptions); - BindCore(section14, ref temp16, defaultValueIfNotFound: false, binderOptions); - instance.Record = temp16; + global::Record? temp18 = instance.Record; + bool wasNull19 = temp18 is null; + temp18 ??= InitializeRecord___System__Action___(section16, binderOptions); + BindCore(section16, ref temp18, defaultValueIfNotFound: false, binderOptions, boundThroughConstructor: wasNull19); + instance.Record = temp18; } else { @@ -196,15 +218,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration public static global::Record InitializeRecord___System__Action___(IConfiguration configuration, BinderOptions? binderOptions) { int x = (int)(10); - if (TryGetConfigurationValue(configuration, key: "x", out string? value17) && value17 is not null) + if (TryGetConfigurationValue(configuration, key: "x", out string? value20) && value20 is not null) { - x = ParseInt(value17, configuration.GetSection("x").Path); + x = ParseInt(value20, configuration.GetSection("x").Path); } - return new global::Record(x) - { - x = x, - }; + return new global::Record(x); + } + + + private static partial class __GenericAccessors_Record___System__Action____0 + { + [global::System.Runtime.CompilerServices.UnsafeAccessorAttribute(global::System.Runtime.CompilerServices.UnsafeAccessorKind.Method, Name = "set_x")] + public static extern void __set_Record___System__Action____x(global::Record obj, int value); } /// Tries to get the configuration value for the specified key. @@ -221,7 +247,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration /// If required by the binder options, validates that there are no unknown keys in the input configuration object. - public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + public static void ValidateConfigurationKeys(Type type, global::System.Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) { if (binderOptions?.ErrorOnUnknownConfiguration is true) { @@ -260,7 +286,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return null; } - public static BinderOptions? GetBinderOptions(Action? configureOptions) + public static BinderOptions? GetBinderOptions(global::System.Action? configureOptions) { if (configureOptions is null) { 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 eef0a38bf22535..c613a5d2b4843f 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 @@ -210,9 +210,9 @@ private static HashSet GetFilteredAssemblyRefs(IEnumerable exclu return assemblies; } - private static void AssertCanCreateAssemblyImage(Compilation compilation) + private static void Emit(Compilation compilation, Stream peStream) { - var emitResult = compilation.Emit(Stream.Null); + var emitResult = compilation.Emit(peStream); if (!emitResult.Success) { // Explicit failures to include in the test output. @@ -221,16 +221,12 @@ private static void AssertCanCreateAssemblyImage(Compilation compilation) } } + private static void AssertCanCreateAssemblyImage(Compilation compilation) => Emit(compilation, Stream.Null); + private static byte[] CreateAssemblyImage(Compilation compilation) { using MemoryStream stream = new(); - var emitResult = compilation.Emit(stream); - if (!emitResult.Success) - { - // Explicit failures to include in the test output. - string errorMessage = string.Join(Environment.NewLine, emitResult.Diagnostics.Select(d => d.ToString())); - throw new InvalidOperationException(errorMessage); - } + Emit(compilation, stream); return stream.ToArray(); } 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 82fb1fbe46e401..381202d5c78cbf 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs @@ -471,6 +471,147 @@ public static void Main() AssertCanCreateAssemblyImage(result.OutputCompilation); } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] + [InlineData(""" + public record Settings + { + public required string Name { get; init; } + public required Nested Child { get; init; } + } + + public record Nested + { + public required string Value { get; init; } + } + """)] + [InlineData(""" + public class Settings + { + public required string Name { get; set; } + public required Nested Child { get; set; } + } + + public class Nested + { + public required string Value { get; set; } + } + """)] + [InlineData(""" + public struct Settings + { + public required string Name { get; set; } + public required Nested Child { get; set; } + } + + public struct Nested + { + public required string Value { get; set; } + } + """)] + public async Task RequiredPropertyOnParameterlessConstructorType(string settingsType) + { + string source = $$""" + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfiguration config = configurationBuilder.Build(); + + Settings settings = config.GetSection("Settings").Get()!; + } + } + + {{settingsType}} + """; + + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder))); + Assert.NotNull(result.GeneratedSource); + Assert.Empty(result.Diagnostics); + + AssertCanCreateAssemblyImage(result.OutputCompilation); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] + [InlineData(""" + internal class Settings + { + public required string Name { get; internal set; } + } + """)] + [InlineData(""" + internal class Settings + { + public required string Name { get; internal init; } + } + """)] + public async Task RequiredPropertyWithNonPublicSetter_NoDiagnostic(string settingsType) + { + string source = $$""" + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfiguration config = configurationBuilder.Build(); + + var settings = config.GetSection("Settings").Get()!; + } + } + + {{settingsType}} + """; + + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder))); + Assert.NotNull(result.GeneratedSource); + // The instance is constructed through an accessor that bypasses the required-member check, so no diagnostic + // is reported. The member with the non-public setter is simply left at its default, like the reflection + // binder (which does not bind non-public members by default). + Assert.Empty(result.Diagnostics); + + AssertCanCreateAssemblyImage(result.OutputCompilation); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] + public async Task RequiredPropertyWithNonPublicSetter_SetsRequiredMembersConstructor_NoDiagnostic() + { + // The constructor is marked [SetsRequiredMembers], so the compiler does not require the member to be set in + // an object initializer; the generator must not report a diagnostic or mark the type non-constructible. + string source = """ + using System.Diagnostics.CodeAnalysis; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfiguration config = configurationBuilder.Build(); + + var settings = config.GetSection("Settings").Get()!; + } + } + + internal class Settings + { + [SetsRequiredMembers] + public Settings() { Name = "default"; } + + public required string Name { get; internal set; } + } + """; + + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder))); + Assert.NotNull(result.GeneratedSource); + Assert.Empty(result.Diagnostics); + + AssertCanCreateAssemblyImage(result.OutputCompilation); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] public async Task ListOfTupleWithComplexElementInInternalPropertyTest() { @@ -1752,5 +1893,155 @@ class MyConfiguration AssertCanCreateAssemblyImage(result.OutputCompilation); } + + /// + /// An init-only property whose setter validates or has side effects must not be invoked when its + /// configuration key is absent. The generator sets init-only members post-construction and, like the + /// reflection binder, only when a value was bound - so an absent key leaves the (null) default untouched + /// instead of calling the setter with it. Covers both Get<T> and Bind(instance). + /// + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] + [InlineData("NullRejectingOptions options = config.Get();")] + [InlineData("NullRejectingOptions options = new(); config.Bind(options);")] + public async Task InitOnlyProperty_ValidatingSetter_NotInvokedWhenKeyAbsent(string bindStatements) + { + string source = $$""" + using System; + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static bool Result; + + public static void Main() + { + IConfiguration config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { ["Number"] = "42" }) + .Build(); + {{bindStatements}} + Result = options.Number == 42 && options.Name is null; + } + } + + public class NullRejectingOptions + { + private string _name; + public string Name + { + get => _name; + init => _name = value ?? throw new ArgumentNullException(nameof(value)); + } + public int Number { get; set; } + } + """; + + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder))); + Assert.NotNull(result.GeneratedSource); + Assert.Empty(result.Diagnostics); + + // Number binds; Name has no config key, so its validating setter must not be called with the null default. + Assert.True(Assert.IsType(LoadAndInvokeMain(result.OutputCompilation, "Result"))); + } + + /// + /// A type nested in a generic type cannot use a generic-wrapper [UnsafeAccessor] for its init-only + /// members - the wrapper is keyed on the type's own type parameters, which cannot express the enclosing type's - + /// so it falls back to reflection. That must produce compilable code, whether or not the nested type is itself + /// generic. + /// + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] + [InlineData("Outer.Options")] + [InlineData("Outer.GenericOptions")] + [InlineData("Outer.Middle.Option")] + public async Task InitOnlyProperty_OnTypeNestedInGenericType_Compiles(string boundType) + { + string source = $$""" + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + IConfiguration config = new ConfigurationBuilder().Build(); + _ = config.Get<{{boundType}}>(); + } + } + + public class Outer + { + public class Options + { + public T Value { get; init; } + } + + public class GenericOptions + { + public T Value { get; init; } + public TItem Item { get; init; } + } + + // Middle has no type parameters of its own but is effectively generic through Outer, so its own + // nested Option is too - the immediate containing type's own arity is 0 yet a wrapper is still invalid. + public class Middle + { + public class Option + { + public T Value { get; init; } + public string Name { get; init; } + } + } + } + """; + + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder))); + Assert.NotNull(result.GeneratedSource); + Assert.Empty(result.Diagnostics); + + AssertCanCreateAssemblyImage(result.OutputCompilation); + } + + /// + /// A nullable init-only property is set post-construction only when its configuration is present. An absent key + /// preserves a non-null field-initializer default; a present (empty) value that binds to + /// overwrites the default - matching the reflection binder, which sets a property when it bound a value even if + /// that value is null. This distinguishes "absent" from "bound to null", which a plain non-null value check cannot. + /// + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))] + [InlineData("""["Other"] = "x" """, "5")] + [InlineData("""["Value"] = "" """, "null")] + [InlineData("""["Value"] = "7" """, "7")] + public async Task InitOnlyNullableProperty_SetOnlyWhenConfigPresent(string entries, string expected) + { + string source = $$""" + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static string Result; + + public static void Main() + { + IConfiguration config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { {{entries}} }) + .Build(); + Options options = config.Get(); + Result = options.Value?.ToString() ?? "null"; + } + } + + public class Options + { + public int? Value { get; init; } = 5; + } + """; + + ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder))); + Assert.NotNull(result.GeneratedSource); + Assert.Empty(result.Diagnostics); + + Assert.Equal(expected, Assert.IsType(LoadAndInvokeMain(result.OutputCompilation, "Result"))); + } } }