Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.CodeAnalysis;
using System;
using System.Diagnostics;

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
public sealed partial class ConfigurationBindingGenerator
Expand Down Expand Up @@ -39,7 +43,7 @@ private static class FullyQualifiedDisplayName
public static class Identifier
{
public const string binderOptions = nameof(binderOptions);
public const string configureActions = nameof(configureActions);
public const string configureOptions = nameof(configureOptions);
public const string configuration = nameof(configuration);
public const string defaultValue = nameof(defaultValue);
public const string element = nameof(element);
Expand Down Expand Up @@ -79,6 +83,7 @@ public static class Identifier
public const string GetValue = nameof(GetValue);
public const string GetValueCore = nameof(GetValueCore);
public const string HasChildren = nameof(HasChildren);
public const string HasConfig = nameof(HasConfig);
public const string HasValueOrChildren = nameof(HasValueOrChildren);
public const string HasValue = nameof(HasValue);
public const string Helpers = nameof(Helpers);
Expand Down Expand Up @@ -133,6 +138,21 @@ private void EmitCheckForNullArgument_WithBlankLine(string argName, bool useFull
_writer.WriteBlankLine();
}

private bool EmitInitException(TypeSpec type)
{
Debug.Assert(type.InitializationStrategy is not InitializationStrategy.None);

if (!type.CanInitialize)
{
_writer.WriteLine(GetInitException(type.InitExceptionMessage) + ";");
return true;
}

return false;
}

private string GetInitException(string message) => $@"throw new {GetInvalidOperationDisplayName()}(""{message}"")";

private string GetIncrementalVarName(string prefix) => $"{prefix}{_parseValueCount++}";

private string GetTypeDisplayString(TypeSpec type) => _useFullyQualifiedNames ? type.FullyQualifiedDisplayString : type.MinimalDisplayString;
Expand All @@ -146,6 +166,22 @@ private string GetHelperMethodDisplayString(string methodName)

return methodName;
}

private static string GetExpressionForArgument(ParameterSpec parameter)
{
string name = parameter.Name + (parameter.HasExplicitDefaultValue ? string.Empty : $".{Identifier.Value}");

return parameter.RefKind switch
{
RefKind.None => name,
RefKind.Ref => $"ref {name}",
RefKind.Out => "out _",
RefKind.In => $"in {name}",
_ => throw new InvalidOperationException()
};
}

private string GetInvalidOperationDisplayName() => _useFullyQualifiedNames ? FullyQualifiedDisplayName.InvalidOperationException : Identifier.InvalidOperationException;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
// Runtime exception messages; not localized so we keep them in source.
internal static class ExceptionMessages
{
public const string CannotBindToConstructorParameter = "Cannot create instance of type '{0}' because one or more parameters cannot be bound to. Constructor parameters cannot be declared as in, out, or ref. Invalid parameters are: '{1}'";
public const string CannotSpecifyBindNonPublicProperties = "The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'.";
public const string ConstructorParametersDoNotMatchProperties = "Cannot create instance of type '{0}' because one or more parameters cannot be bound to. Constructor parameters must have corresponding properties. Fields are not supported. Missing properties are: '{1}'";
public const string FailedBinding = "Failed to convert configuration value at '{0}' to type '{1}'.";
public const string MissingConfig = "'{0}' was set on the provided {1}, but the following properties were not found on the instance of {2}: {3}";
public const string MissingPublicInstanceConstructor = "Cannot create instance of type '{0}' because it is missing a public instance constructor.";
public const string MultipleParameterizedConstructors = "Cannot create instance of type '{0}' because it has multiple public parameterized constructors.";
public const string ParameterBeingBoundToIsUnnamed = "Cannot create instance of type '{0}' because one or more parameters are unnamed.";
public const string ParameterHasNoMatchingConfig = "Cannot create instance of type '{0}' because parameter '{1}' has no matching config. Each parameter in the constructor that does not have a default value must have a corresponding config entry.";
public const string TypeNotDetectedAsInput = "Unable to bind to type '{0}': generator did not detect the type as input.";
public const string TypeNotSupportedAsInput = "Unable to bind to type '{0}': generator does not support this type as input to this method.";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
internal static class ParserDiagnostics
{
public static DiagnosticDescriptor TypeNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.TypeNotSupported));
public static DiagnosticDescriptor NeedPublicParameterlessConstructor { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.NeedPublicParameterlessConstructor));
public static DiagnosticDescriptor MissingPublicInstanceConstructor { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.MissingPublicInstanceConstructor));
public static DiagnosticDescriptor CollectionNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.CollectionNotSupported));
public static DiagnosticDescriptor DictionaryKeyNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.DictionaryKeyNotSupported));
public static DiagnosticDescriptor ElementTypeNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.ElementTypeNotSupported));
public static DiagnosticDescriptor MultipleParameterizedConstructors { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.MultipleParameterizedConstructors));
public static DiagnosticDescriptor MultiDimArraysNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.MultiDimArraysNotSupported));
public static DiagnosticDescriptor NullableUnderlyingTypeNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.NullableUnderlyingTypeNotSupported));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,23 @@
<Compile Include="$(CommonPath)\Roslyn\GetBestTypeByMetadataName.cs" Link="Common\Roslyn\GetBestTypeByMetadataName.cs" />
<Compile Include="ConfigurationBindingGenerator.cs" />
<Compile Include="ConfigurationBindingGenerator.Emitter.cs" />
<Compile Include="Helpers\Emitter.Helpers.cs" />
<Compile Include="Helpers\ParserDiagnostics.cs" />
<Compile Include="ConfigurationBindingGenerator.Parser.cs" />
<Compile Include="Helpers\Emitter.Helpers.cs" />
<Compile Include="Helpers\ExceptionMessages.cs" />
<Compile Include="Helpers\KnownTypeSymbols.cs" />
<Compile Include="Helpers\ParserDiagnostics.cs" />
<Compile Include="Helpers\SourceWriter.cs" />
<Compile Include="Model\BinderInvocationOperation.cs" />
<Compile Include="Model\BinderMethodSpecifier.cs" />
<Compile Include="Model\SourceGenerationSpec.cs" />
<Compile Include="Helpers\SourceWriter.cs" />
<Compile Include="Model\CollectionSpec.cs" />
<Compile Include="Model\ConstructionStrategy.cs" />
<Compile Include="Model\ConfigurationSectionSpec.cs" />
<Compile Include="Model\InitializationStrategy.cs" />
<Compile Include="Model\NullableSpec.cs" />
<Compile Include="Model\ObjectSpec.cs" />
<Compile Include="Model\ParameterSpec.cs" />
<Compile Include="Model\ParsableFromStringSpec.cs" />
<Compile Include="Model\PropertySpec.cs" />
<Compile Include="Model\SourceGenerationSpec.cs" />
<Compile Include="Model\TypeSpec.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ internal enum BinderMethodSpecifier
// Binding helpers
BindCore = 0x1000,
HasChildren = 0x4000,
Initialize = 0x8000,

// Method groups
Bind = Bind_instance | Bind_instance_BinderOptions | Bind_key_instance,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ public CollectionSpec(ITypeSymbol type) : base(type) { }

public required CollectionPopulationStrategy PopulationStrategy { get; init; }

public override bool CanInitialize => ConcreteType?.CanInitialize ?? CanInitCompexType;

public override required InitializationStrategy InitializationStrategy { get; set; }

public required string? ToEnumerableMethodCall { get; init; }
}

Expand All @@ -38,9 +42,8 @@ public DictionarySpec(INamedTypeSymbol type) : base(type) { }

internal enum CollectionPopulationStrategy
{
Unknown,
Array,
Add,
Cast_Then_Add,
Unknown = 0,
Add = 1,
Cast_Then_Add = 2,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
internal enum ConstructionStrategy
internal enum InitializationStrategy
{
None = 0,
ParameterlessConstructor = 1,
ParameterizedConstructor = 2,
ToEnumerableMethod = 3,
Array = 4,
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.CodeAnalysis;

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
Expand All @@ -12,5 +13,11 @@ public NullableSpec(ITypeSymbol type) : base(type) { }
public override TypeSpecKind SpecKind => TypeSpecKind.Nullable;

public required TypeSpec UnderlyingType { get; init; }

public override string? InitExceptionMessage
{
get => UnderlyingType.InitExceptionMessage;
set => throw new InvalidOperationException();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
internal sealed record ObjectSpec : TypeSpec
{
public ObjectSpec(INamedTypeSymbol type) : base(type) { }
public ObjectSpec(INamedTypeSymbol type) : base(type)
{
InitializeMethodDisplayString = $"Initialize{type.Name.Replace(".", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty)}";
}

public override TypeSpecKind SpecKind => TypeSpecKind.Object;

public Dictionary<string, PropertySpec?> Properties { get; } = new();
public override InitializationStrategy InitializationStrategy { get; set; }

public override bool CanInitialize => CanInitCompexType;

public Dictionary<string, PropertySpec> Properties { get; } = new(StringComparer.OrdinalIgnoreCase);

public List<ParameterSpec> ConstructorParameters { get; } = new();

public string? InitializeMethodDisplayString { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
internal sealed record ParameterSpec
{
public ParameterSpec(IParameterSymbol parameter)
{
Name = parameter.Name;
RefKind = parameter.RefKind;

HasExplicitDefaultValue = parameter.HasExplicitDefaultValue;
if (HasExplicitDefaultValue)
{
string formatted = SymbolDisplay.FormatPrimitive(parameter.ExplicitDefaultValue, quoteStrings: true, useHexadecimalNumbers: false);
DefaultValue = formatted is "null" ? "default!" : formatted;
}
}

public required TypeSpec Type { get; init; }

public string Name { get; }

public required string ConfigurationKeyName { get; init; }

public RefKind RefKind { get; }

public bool HasExplicitDefaultValue { get; init; }

public string DefaultValue { get; } = "default!";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,37 @@ public PropertySpec(IPropertySymbol property)
{
Name = property.Name;
IsStatic = property.IsStatic;
CanGet = property.GetMethod is IMethodSymbol { DeclaredAccessibility: Accessibility.Public, IsInitOnly: false };
CanSet = property.SetMethod is IMethodSymbol { DeclaredAccessibility: Accessibility.Public, IsInitOnly: false };

bool setterIsPublic = property.SetMethod?.DeclaredAccessibility is Accessibility.Public;
IsInitOnly = property.SetMethod?.IsInitOnly == true;
IsRequired = property.IsRequired;
SetOnInit = setterIsPublic && (IsInitOnly || IsRequired);
CanSet = setterIsPublic && !IsInitOnly;
CanGet = property.GetMethod?.DeclaredAccessibility is Accessibility.Public;
}

public required TypeSpec Type { get; init; }

public ParameterSpec? MatchingCtorParam { get; set; }

public string Name { get; }

public bool IsStatic { get; }

public bool IsRequired { get; }

public bool IsInitOnly { get; }

public bool SetOnInit { get; }

public bool CanGet { get; }

public bool CanSet { get; }

public required TypeSpec? Type { get; init; }

public required string ConfigurationKeyName { get; init; }

public bool ShouldBind() =>
(CanGet || CanSet) &&
Type is not null &&
!(!CanSet && (Type as CollectionSpec)?.ConstructionStrategy is ConstructionStrategy.ParameterizedConstructor);
!(!CanSet && (Type as CollectionSpec)?.InitializationStrategy is InitializationStrategy.ParameterizedConstructor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Collections.Immutable;

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
internal sealed record SourceGenerationSpec(
Dictionary<BinderMethodSpecifier, HashSet<TypeSpec>> RootConfigTypes,
Dictionary<BinderMethodSpecifier, HashSet<TypeSpec>> ConfigTypes,
BinderMethodSpecifier MethodsToGen,
HashSet<ParsableFromStringSpec> PrimitivesForHelperGen,
HashSet<string> TypeNamespaces)
ImmutableSortedSet<string> TypeNamespaces)
{
public bool HasRootMethods() =>
ShouldEmitMethods(BinderMethodSpecifier.Get | BinderMethodSpecifier.Bind | BinderMethodSpecifier.Configure | BinderMethodSpecifier.GetValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ internal abstract record TypeSpec

public TypeSpec(ITypeSymbol type)
{
IsValueType = type.IsValueType;
Namespace = type.ContainingNamespace?.ToDisplayString();
FullyQualifiedDisplayString = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
MinimalDisplayString = type.ToDisplayString(s_minimalDisplayFormat);
Namespace = type.ContainingNamespace?.ToDisplayString();
IsValueType = type.IsValueType;
Name = Namespace + "." + MinimalDisplayString.Replace(".", "+");
}

public string Name { get; }

public string FullyQualifiedDisplayString { get; }

public string MinimalDisplayString { get; }
Expand All @@ -31,23 +34,28 @@ public TypeSpec(ITypeSymbol type)

public abstract TypeSpecKind SpecKind { get; }

public virtual ConstructionStrategy ConstructionStrategy { get; init; }
public virtual InitializationStrategy InitializationStrategy { get; set; }

public virtual string? InitExceptionMessage { get; set; }

public virtual bool CanInitialize => true;

/// <summary>
/// Where in the input compilation we picked up a call to Bind, Get, or Configure.
/// Location in the input compilation we picked up a call to Bind, Get, or Configure.
/// </summary>
public required Location? Location { get; init; }

protected bool CanInitCompexType => InitializationStrategy is not InitializationStrategy.None && InitExceptionMessage is null;
}

internal enum TypeSpecKind
{
Unknown = 0,
ParsableFromString = 1,
Object = 2,
Array = 3,
Enumerable = 4,
Dictionary = 5,
IConfigurationSection = 6,
Nullable = 7,
Enumerable = 3,
Dictionary = 4,
IConfigurationSection = 5,
Nullable = 6,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,14 @@
<data name="LanguageVersionIsNotSupportedTitle" xml:space="preserve">
<value>Language version is required to be at least C# 11</value>
</data>
<data name="MissingPublicInstanceConstructor" xml:space="preserve">
<value>Cannot create instance of type '{0}' because it is missing a public instance constructor.</value>
</data>
<data name="MultiDimArraysNotSupported" xml:space="preserve">
<value>Multidimensional arrays are not supported: '{0}'.</value>
</data>
<data name="NeedPublicParameterlessConstructor" xml:space="preserve">
<value>Only objects with public parameterless constructors are supported: '{0}'.</value>
<data name="MultipleParameterizedConstructors" xml:space="preserve">
<value>Cannot create instance of type '{0}' because it has multiple public parameterized constructors.</value>
</data>
<data name="NullableUnderlyingTypeNotSupported" xml:space="preserve">
<value>Nullable underlying type is not supported: '{0}'.</value>
Expand Down
Loading