Skip to content

Commit

Permalink
Fix polymorphic issue and address feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
layomia committed Aug 19, 2023
1 parent b764c56 commit 8bd9571
Show file tree
Hide file tree
Showing 57 changed files with 784 additions and 1,582 deletions.
12 changes: 0 additions & 12 deletions src/libraries/Common/src/SourceGenerators/GeneratorHelpers.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,17 @@ private sealed partial class Emitter
private readonly SourceProductionContext _context;
private readonly SourceGenerationSpec _sourceGenSpec;

private readonly string _generatedNamespaceName = ProjectName;
private bool _emitBlankLineBeforeNextStatement;
private int _valueSuffixIndex;

private static readonly Regex s_arrayBracketsRegex = new(Regex.Escape("[]"));

private readonly SourceWriter _writer = new();

public Emitter(SourceProductionContext context, SourceGenerationSpec sourceGenSpec, bool emitUniqueHelperNames)
public Emitter(SourceProductionContext context, SourceGenerationSpec sourceGenSpec)
{
_context = context;
_sourceGenSpec = sourceGenSpec;

if (emitUniqueHelperNames)
{
GeneratorHelpers.MakeNameUnique(ref _generatedNamespaceName);
}
}

public void Emit()
Expand All @@ -51,7 +45,7 @@ public void Emit()

EmitInterceptsLocationAttrDecl();

EmitStartBlock($"namespace {_generatedNamespaceName}");
EmitStartBlock($"namespace {ProjectName}");
EmitUsingStatements();

_writer.WriteLine();
Expand Down Expand Up @@ -260,10 +254,8 @@ private void EmitUsingStatements()
private void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn)
{
string returnPostfix = voidReturn ? string.Empty : " null";
string methodIdentifier = Identifier.HasValueOrChildren;

_writer.WriteLine($$"""
if (!{{methodIdentifier}}({{Identifier.configuration}}))
if (!{{Identifier.HasValueOrChildren}}({{Identifier.configuration}}))
{
return{{returnPostfix}};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,14 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error ||
return _createdSpecs[type] = spec;
}

private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type)
private void RegisterTypeForBindCoreMainGen(TypeSpec typeSpec)
{
if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(method, out HashSet<TypeSpec>? types))
if (typeSpec.NeedsMemberBinding)
{
_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods[method] = types = new HashSet<TypeSpec>();
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreMain, typeSpec);
RegisterTypeForBindCoreGen(typeSpec);
_sourceGenSpec.MethodsToGen_CoreBindingHelper |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren;
}

types.Add(type);
_sourceGenSpec.MethodsToGen_CoreBindingHelper |= method;
}

private void RegisterTypeForBindCoreGen(TypeSpec typeSpec)
Expand All @@ -196,16 +195,30 @@ private void RegisterTypeForBindCoreGen(TypeSpec typeSpec)
}
}

private void RegisterGenMethodAsInterceptor(Enum method, IInvocationOperation operation)
private void RegisterTypeForGetCoreGen(TypeSpec typeSpec)
{
RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.GetCore, typeSpec);
_sourceGenSpec.MethodsToGen_CoreBindingHelper |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren;
}

private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type)
{
if (!_sourceGenSpec.GenMethodsInterceptionInfo.TryGetValue(method, out List<InterceptorLocationInfo>? info))
if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(method, out HashSet<TypeSpec>? types))
{
_sourceGenSpec.GenMethodsInterceptionInfo[method] = info = new List<InterceptorLocationInfo>();
_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods[method] = types = new HashSet<TypeSpec>();
}

info.Add(new InterceptorLocationInfo(operation));
types.Add(type);
_sourceGenSpec.MethodsToGen_CoreBindingHelper |= method;
}

/// <summary>
/// Registers interceptors for root binding methods, except for ConfigurationBinder.Bind,
/// which is handled by <see cref="RegisterAsInterceptor_ConfigBinder_BindMethod"/>
/// </summary>
private void RegisterAsInterceptor(Enum method, IInvocationOperation operation) =>
_sourceGenSpec.InterceptionInfo.RegisterCacheEntry(method, new InterceptorLocationInfo(operation));

private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType)
{
if (type is INamedTypeSymbol { IsGenericType: true } genericType &&
Expand Down Expand Up @@ -901,4 +914,19 @@ private void RegisterTypeDiagnostic(ITypeSymbol causingType, InvocationDiagnosti
}
}
}

public static class ParserExtensions
{
public static void RegisterCacheEntry<TKey, TValue, TEntry>(this Dictionary<TKey, TValue> cache, TKey key, TEntry entry)
where TKey : notnull
where TValue : ICollection<TEntry>, new()
{
if (!cache.TryGetValue(key, out TValue? entryCollection))
{
cache[key] = entryCollection = new TValue();
}

entryCollection.Add(entry);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
public sealed partial class ConfigurationBindingGenerator
{
/// <summary>
/// Supresses false-positive diagnostics emitted by the linker analyzers
/// when analyzing binding invocations that we have substituted.Workaround
/// for https://github.com/dotnet/roslyn/issues/68669.
/// Supresses false-positive diagnostics emitted by the linker
/// when analyzing binding invocations that we have intercepted.
/// Workaround for https://github.com/dotnet/roslyn/issues/68669.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class Suppressor : DiagnosticSuppressor
{
private const string Justification = "The target method has been intercepted by a generated static variant.";

/// <summary>
/// Suppression descriptor for IL2026: Members attributed with RequiresUnreferencedCode may break when trimming.
/// </summary>
private static readonly SuppressionDescriptor RUCDiagnostic = new(id: "SYSLIBSUPPRESS0002", suppressedDiagnosticId: "IL2026", Justification);

/// <summary>
/// Suppression descriptor for IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as native AOT.
/// </summary>
private static readonly SuppressionDescriptor RDCDiagnostic = new(id: "SYSLIBSUPPRESS0003", suppressedDiagnosticId: "IL3050", Justification);

public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(RUCDiagnostic, RDCDiagnostic);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerato
{
private static readonly string ProjectName = Emitter.s_assemblyName.Name;

public bool EmitUniqueHelperNames { get; init; } = true;

public void Initialize(IncrementalGeneratorInitializationContext context)
{
#if LAUNCH_DEBUGGER
Expand All @@ -43,7 +41,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
context.RegisterSourceOutput(inputData, (spc, source) => Execute(source.Item1, source.Item2, spc));
}

private void Execute(CompilationData compilationData, ImmutableArray<BinderInvocation> inputCalls, SourceProductionContext context)
private static void Execute(CompilationData compilationData, ImmutableArray<BinderInvocation> inputCalls, SourceProductionContext context)
{
if (inputCalls.IsDefaultOrEmpty)
{
Expand All @@ -59,7 +57,7 @@ private void Execute(CompilationData compilationData, ImmutableArray<BinderInvoc
Parser parser = new(context, compilationData.TypeSymbols!, inputCalls);
if (parser.GetSourceGenerationSpec() is SourceGenerationSpec spec)
{
Emitter emitter = new(context, spec, EmitUniqueHelperNames);
Emitter emitter = new(context, spec);
emitter.Emit();
}
}
Expand All @@ -71,7 +69,11 @@ private sealed record CompilationData

public CompilationData(CSharpCompilation compilation)
{
LanguageVersionIsSupported = compilation.LanguageVersion >= LanguageVersion.Preview;
// We don't have a CSharp21 value available yet. Polyfill the value here for forward compat, rather than use the LangugeVersion.Preview enum value.
// https://github.com/dotnet/roslyn/blob/168689931cb4e3150641ec2fb188a64ce4b3b790/src/Compilers/CSharp/Portable/LanguageVersion.cs#L218-L232
const int LangVersion_CSharp12 = 1200;
LanguageVersionIsSupported = (int)compilation.LanguageVersion >= LangVersion_CSharp12;

if (LanguageVersionIsSupported)
{
TypeSymbols = new KnownTypeSymbols(compilation);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using SourceGenerators;

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
public sealed partial class ConfigurationBindingGenerator
Expand Down Expand Up @@ -45,14 +48,14 @@ private void EmitGetMethods()
if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_TypeOf))
{
StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_TypeOf, documentation);
_writer.WriteLine($"public static object? {Identifier.Get}(this {Identifier.IConfiguration} {Identifier.configuration}, {Identifier.Type} {Identifier.type}) => " +
_writer.WriteLine($"public static object? {Identifier.Get}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}) => " +
$"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureOptions}: null);");
}

if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_TypeOf_BinderOptions))
{
StartMethodDefinition(MethodsToGen_ConfigurationBinder.Get_TypeOf_BinderOptions, documentation);
_writer.WriteLine($"public static object? {Identifier.Get}(this {Identifier.IConfiguration} {Identifier.configuration}, {Identifier.Type} {Identifier.type}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}) => " +
_writer.WriteLine($"public static object? {Identifier.Get}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}) => " +
$"{expressionForGetCore}({Identifier.configuration}, {Identifier.type}, {Identifier.configureOptions});");
}
}
Expand All @@ -79,25 +82,30 @@ private void EmitGetValueMethods()
if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key))
{
StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key, documentation);
_writer.WriteLine($"public static object? {Identifier.GetValue}(this {Identifier.IConfiguration} {Identifier.configuration}, {Identifier.Type} {Identifier.type}, string {Identifier.key}) => " +
_writer.WriteLine($"public static object? {Identifier.GetValue}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key}) => " +
$"{expressionForGetValueCore}({Identifier.configuration}, {Identifier.type}, {Identifier.key});");
}

if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key_defaultValue))
{
StartMethodDefinition(MethodsToGen_ConfigurationBinder.GetValue_TypeOf_key_defaultValue, documentation);
_writer.WriteLine($"public static object? {Identifier.GetValue}(this {Identifier.IConfiguration} {Identifier.configuration}, {Identifier.Type} {Identifier.type}, string {Identifier.key}, object? {Identifier.defaultValue}) => " +
_writer.WriteLine($"public static object? {Identifier.GetValue}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key}, object? {Identifier.defaultValue}) => " +
$"{expressionForGetValueCore}({Identifier.configuration}, {Identifier.type}, {Identifier.key}) ?? {Identifier.defaultValue};");
}
}

private void EmitBindMethods_ConfigurationBinder()
{
if (!ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind))
{
return;
}

string objParamExpr = $"object? {Identifier.obj}";

if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance))
{
EmitMethodImplementation(
EmitMethods(
MethodsToGen_ConfigurationBinder.Bind_instance,
additionalParams: objParamExpr,
configExpression: Identifier.configuration,
Expand All @@ -106,7 +114,7 @@ private void EmitBindMethods_ConfigurationBinder()

if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions))
{
EmitMethodImplementation(
EmitMethods(
MethodsToGen_ConfigurationBinder.Bind_instance_BinderOptions,
additionalParams: $"{objParamExpr}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions}",
configExpression: Identifier.configuration,
Expand All @@ -115,21 +123,41 @@ private void EmitBindMethods_ConfigurationBinder()

if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Bind_key_instance))
{
EmitMethodImplementation(
EmitMethods(
MethodsToGen_ConfigurationBinder.Bind_key_instance,
additionalParams: $"string {Identifier.key}, {objParamExpr}",
configExpression: $"{Identifier.configuration}?.{Identifier.GetSection}({Identifier.key})",
configExpression: $"{Expression.configurationGetSection}({Identifier.key})",
configureOptions: false);
}

void EmitMethodImplementation(MethodsToGen_ConfigurationBinder method, string additionalParams, string configExpression, bool configureOptions)
void EmitMethods(MethodsToGen_ConfigurationBinder method, string additionalParams, string configExpression, bool configureOptions)
{
string configureOptionsArg = configureOptions ? Identifier.configureOptions : $"{Identifier.configureOptions}: null";
string returnExpression = $"{Identifier.BindCoreMain}({configExpression}, {Identifier.obj}, {configureOptionsArg})";

StartMethodDefinition(method, "Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.");
_writer.WriteLine($"public static void {Identifier.Bind}(this {Identifier.IConfiguration} {Identifier.configuration}, {additionalParams}) => "
+ $"{returnExpression};");
foreach (KeyValuePair<TypeSpec, List<InterceptorLocationInfo>> pair in _sourceGenSpec.InterceptionInfo_ConfigBinder.GetOverloadInfo(method))
{
(TypeSpec type, List<InterceptorLocationInfo> interceptorInfoList) = (pair.Key, pair.Value);

EmitBlankLineIfRequired();
_writer.WriteLine($"/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>");
EmitInterceptsLocationAnnotations(interceptorInfoList);
EmitStartBlock($"public static void {Identifier.Bind}_{type.DisplayString.ToIdentifierSubstring()}(this {Identifier.IConfiguration} {Identifier.configuration}, {additionalParams})");

if (!EmitInitException(type) && type.NeedsMemberBinding)
{
string binderOptionsArg = configureOptions ? $"{Identifier.GetBinderOptions}({Identifier.configureOptions})" : $"{Identifier.binderOptions}: null";

EmitCheckForNullArgument_WithBlankLine(Identifier.configuration);
if (!type.IsValueType)
{
EmitCheckForNullArgument_WithBlankLine(Identifier.obj);
}
_writer.WriteLine($$"""
var {{Identifier.typedObj}} = ({{type.EffectiveType.DisplayString}}){{Identifier.obj}};
{{nameof(MethodsToGen_CoreBindingHelper.BindCore)}}({{configExpression}}, ref {{Identifier.typedObj}}, {{binderOptionsArg}});
""");
}

EmitEndBlock();
}
}
}

Expand Down
Loading

0 comments on commit 8bd9571

Please sign in to comment.