Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support remaining collections in config binder generator #86285

Merged
merged 3 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -606,11 +606,6 @@ private void EmitBindCoreImpl(TypeSpec type)
{
switch (type.SpecKind)
{
case TypeSpecKind.Array:
{
EmitBindCoreImplForArray((ArraySpec)type);
}
break;
case TypeSpecKind.Enumerable:
{
EmitBindCoreImplForEnumerable((EnumerableSpec)type);
Expand Down Expand Up @@ -643,11 +638,23 @@ private void EmitBindCoreImpl(TypeSpec type)
}
}

private void EmitBindCoreImplForArray(ArraySpec type)
private void EmitBindCoreImplForEnumerable(EnumerableSpec type)
{
EnumerableSpec concreteType = (EnumerableSpec)type.ConcreteType;
EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);

if (type.PopulationStrategy is CollectionPopulationStrategy.Array)
{
EmitPopulationImplForArray(type);
}
else
{
EmitPopulationImplForEnumerableWithAdd(type);
}
}

EmitCheckForNullArgument_WithBlankLine_IfRequired(isValueType: false);
private void EmitPopulationImplForArray(EnumerableSpec type)
{
EnumerableSpec concreteType = (EnumerableSpec)type.ConcreteType;

// Create, bind, and add elements to temp list.
string tempVarName = GetIncrementalVarName(Identifier.temp);
Expand All @@ -661,15 +668,15 @@ private void EmitBindCoreImplForArray(ArraySpec type)
""");
}

private void EmitBindCoreImplForEnumerable(EnumerableSpec type)
private void EmitPopulationImplForEnumerableWithAdd(EnumerableSpec type)
{
EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);

TypeSpec elementType = type.ElementType;

EmitCollectionCastIfRequired(type, out string objIdentifier);

_writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())");

string addStatement = $"{Identifier.obj}.{Identifier.Add}({Identifier.element})";
string addExpression = $"{objIdentifier}.{Identifier.Add}({Identifier.element})";

if (elementType.SpecKind is TypeSpecKind.ParsableFromString)
{
Expand All @@ -678,19 +685,19 @@ private void EmitBindCoreImplForEnumerable(EnumerableSpec type)
{
string tempVarName = GetIncrementalVarName(Identifier.stringValue);
_writer.WriteBlockStart($"if ({Expression.sectionValue} is string {tempVarName})");
_writer.WriteLine($"{Identifier.obj}.{Identifier.Add}({tempVarName});");
_writer.WriteLine($"{objIdentifier}.{Identifier.Add}({tempVarName});");
_writer.WriteBlockEnd();
}
else
{
EmitVarDeclaration(elementType, Identifier.element);
EmitBindLogicFromString(stringParsableType, Identifier.element, Expression.sectionValue, Expression.sectionPath, () => _writer.WriteLine($"{addStatement};"));
EmitBindLogicFromString(stringParsableType, Identifier.element, Expression.sectionValue, Expression.sectionPath, () => _writer.WriteLine($"{addExpression};"));
}
}
else
{
EmitBindCoreCall(elementType, Identifier.element, Identifier.section, InitializationKind.Declaration);
_writer.WriteLine($"{addStatement};");
_writer.WriteLine($"{addExpression};");
}

_writer.WriteBlockEnd();
Expand All @@ -700,11 +707,14 @@ private void EmitBindCoreImplForDictionary(DictionarySpec type)
{
EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);

EmitCollectionCastIfRequired(type, out string objIdentifier);

_writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())");

// Parse key
ParsableFromStringTypeSpec keyType = type.KeyType;
TypeSpec elementType = type.ElementType;

// Parse key
if (keyType.StringParsableTypeKind is StringParsableTypeKind.ConfigValue)
{
_writer.WriteLine($"{keyType.MinimalDisplayString} {Identifier.key} = {Expression.sectionKey};");
Expand All @@ -723,16 +733,14 @@ private void EmitBindCoreImplForDictionary(DictionarySpec type)

void Emit_BindAndAddLogic_ForElement()
{
TypeSpec elementType = type.ElementType;

if (elementType.SpecKind == TypeSpecKind.ParsableFromString)
{
ParsableFromStringTypeSpec stringParsableType = (ParsableFromStringTypeSpec)elementType;
if (stringParsableType.StringParsableTypeKind is StringParsableTypeKind.ConfigValue)
{
string tempVarName = GetIncrementalVarName(Identifier.stringValue);
_writer.WriteBlockStart($"if ({Expression.sectionValue} is string {tempVarName})");
_writer.WriteLine($"{Identifier.obj}[{Identifier.key}] = {tempVarName};");
_writer.WriteLine($"{objIdentifier}[{Identifier.key}] = {tempVarName};");
_writer.WriteBlockEnd();
}
else
Expand All @@ -743,25 +751,42 @@ void Emit_BindAndAddLogic_ForElement()
Identifier.element,
Expression.sectionValue,
Expression.sectionPath,
() => _writer.WriteLine($"{Identifier.obj}[{Identifier.key}] = {Identifier.element};"));
() => _writer.WriteLine($"{objIdentifier}[{Identifier.key}] = {Identifier.element};"));
}
}
else // For complex types:
{
bool isValueType = elementType.IsValueType;
string expressionForElementIsNotNull = $"{Identifier.element} is not null";
string elementTypeDisplayString = elementType.MinimalDisplayString + (elementType.IsValueType ? string.Empty : "?");

// If key already exists, bind to value to existing element instance if not null (for ref types).
string conditionToUseExistingElement = $"{Identifier.obj}.{Identifier.TryGetValue}({Identifier.key}, out {elementTypeDisplayString} {Identifier.element})";
if (!elementType.IsValueType)
string expressionForElementExists = $"{objIdentifier}.{Identifier.TryGetValue}({Identifier.key}, out {elementTypeDisplayString} {Identifier.element})";
string conditionToUseExistingElement = expressionForElementExists;

// If key already exists, bind to existing element instance if not null (for ref types).
if (!isValueType)
{
conditionToUseExistingElement += $" && {Identifier.element} is not null";
conditionToUseExistingElement += $" && {expressionForElementIsNotNull}";
}

_writer.WriteBlockStart($"if (!({conditionToUseExistingElement}))");
EmitObjectInit(elementType, Identifier.element, InitializationKind.SimpleAssignment);
_writer.WriteBlockEnd();

if (elementType is CollectionSpec { ConstructionStrategy: ConstructionStrategy.ParameterizedConstructor } collectionSpec)
{
// This is a read-only collection. If the element exists and is not null,
// we need to copy its contents into a new instance & then append/bind to that.
_writer.WriteBlock($$"""
else
{
{{Identifier.element}} = new {{collectionSpec.ConcreteType.MinimalDisplayString}}({{Identifier.element}});
}
""");
}

EmitBindCoreCall(elementType, $"{Identifier.element}!", Identifier.section, InitializationKind.None);
_writer.WriteLine($"{Identifier.obj}[{Identifier.key}] = {Identifier.element};");
_writer.WriteLine($"{objIdentifier}[{Identifier.key}] = {Identifier.element};");
}
}

Expand All @@ -788,9 +813,11 @@ private void EmitBindCoreImplForObject(ObjectSpec type)
{
_writer.WriteBlockStart($@"case ""{property.ConfigurationKeyName}"":");

TypeSpec propertyType = property.Type;
if (property.ShouldBind())
{
EmitBindCoreImplForProperty(property, property.Type!, parentType: type);
}

EmitBindCoreImplForProperty(property, propertyType, parentType: type);
_writer.WriteBlockEnd();
_writer.WriteLine("break;");
}
Expand Down Expand Up @@ -870,10 +897,7 @@ private void EmitBindCoreImplForProperty(PropertySpec property, TypeSpec propert
break;
default:
{
EmitBindCoreCallForProperty(
property,
propertyType,
expressionForPropertyAccess);
EmitBindCoreCallForProperty(property, propertyType, expressionForPropertyAccess);
}
break;
}
Expand Down Expand Up @@ -1034,40 +1058,80 @@ private void EmitObjectInit(TypeSpec type, string expressionForMemberAccess, Ini
return;
}

string displayString = GetTypeDisplayString(type);
string expressionForInit;
CollectionSpec? collectionType = type as CollectionSpec;

string expressionForInit = null;
if (type is ArraySpec)
string displayString;
if (collectionType is not null)
{
expressionForInit = $"new {_arrayBracketsRegex.Replace(displayString, "[0]", 1)}";
if (collectionType is EnumerableSpec { PopulationStrategy: CollectionPopulationStrategy.Array })
{
displayString = GetTypeDisplayString(type);
expressionForInit = $"new {_arrayBracketsRegex.Replace(displayString, "[0]", 1)}";
}
else
{
displayString = GetTypeDisplayString(collectionType.ConcreteType ?? collectionType);
expressionForInit = $"new {displayString}()";
}
}
else if (type.ConstructionStrategy != ConstructionStrategy.ParameterlessConstructor)
else if (type.ConstructionStrategy is ConstructionStrategy.ParameterlessConstructor)
{
return;
displayString = GetTypeDisplayString(type);
expressionForInit = $"new {displayString}()";
}
else if (type is CollectionSpec { ConcreteType: { } concreteType })
else
{
displayString = GetTypeDisplayString(concreteType);
return;
}

// Not an array.
expressionForInit ??= $"new {displayString}()";

if (initKind == InitializationKind.Declaration)
{
Debug.Assert(!expressionForMemberAccess.Contains("."));
EmitAssignment($"var {expressionForMemberAccess}", expressionForInit);
}
else if (initKind == InitializationKind.AssignmentWithNullCheck)
{
_writer.WriteLine($"{expressionForMemberAccess} ??= {expressionForInit};");
if (collectionType?.ConstructionStrategy is not ConstructionStrategy.ParameterizedConstructor)
{
_writer.WriteLine($"{expressionForMemberAccess} ??= {expressionForInit};");
}
else
{
_writer.WriteBlock($$"""
if ({{expressionForMemberAccess}} is null)
{
{{expressionForMemberAccess}} = {{expressionForInit}};
}
else
{
{{expressionForMemberAccess}} = new {{displayString}}({{expressionForMemberAccess}});
}
""");
}
}
else
{
EmitAssignment(expressionForMemberAccess, expressionForInit);
}
}

private void EmitCollectionCastIfRequired(CollectionSpec type, out string objIdentifier)
{
objIdentifier = Identifier.obj;
if (type.PopulationStrategy is CollectionPopulationStrategy.Cast_Then_Add)
{
objIdentifier = Identifier.temp;
_writer.WriteBlock($$"""
if ({{Identifier.obj}} is not {{type.PopulationCastType!.MinimalDisplayString}} {{objIdentifier}})
{
return;
}
""");
_writer.WriteBlankLine();
}
}

private void EmitCastToIConfigurationSection()
{
string sectionTypeDisplayString;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public sealed partial class ConfigurationBindingSourceGenerator
internal sealed class Helpers
{
public static DiagnosticDescriptor TypeNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.TypeNotSupported));
public static DiagnosticDescriptor AbstractOrInterfaceNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.AbstractOrInterfaceNotSupported));
public static DiagnosticDescriptor NeedPublicParameterlessConstructor { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.NeedPublicParameterlessConstructor));
public static DiagnosticDescriptor CollectionNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.CollectionNotSupported));
public static DiagnosticDescriptor DictionaryKeyNotSupported { get; } = CreateTypeNotSupportedDescriptor(nameof(SR.DictionaryKeyNotSupported));
Expand Down