Skip to content

Commit

Permalink
Dictionary expressions: initial binding and lowering support (#76257)
Browse files Browse the repository at this point in the history
  • Loading branch information
cston authored Dec 18, 2024
1 parent 4e6ad7f commit 5439dd5
Show file tree
Hide file tree
Showing 35 changed files with 1,751 additions and 88 deletions.
188 changes: 141 additions & 47 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,10 @@ private BoundCollectionExpression ConvertCollectionExpression(
return BindCollectionExpressionForErrorRecovery(node, targetType, inConversion: true, diagnostics);
}
break;

case CollectionExpressionTypeKind.DictionaryInterface:
MessageID.IDS_FeatureDictionaryExpressions.CheckFeatureAvailability(diagnostics, syntax);
break;
}

var elements = node.Elements;
Expand Down Expand Up @@ -928,12 +932,29 @@ private BoundCollectionExpression ConvertCollectionExpression(
}
else
{
if ((collectionTypeKind is CollectionExpressionTypeKind.ArrayInterface) ||
// Verify the existence of the well-known members that may be used in lowering, even
// though not all will be used for any particular collection expression. Checking all
// gives a consistent behavior, regardless of collection expression elements.
if (collectionTypeKind is CollectionExpressionTypeKind.DictionaryInterface)
{
var dictionaryType = GetWellKnownType(WellKnownType.System_Collections_Generic_Dictionary_KV, diagnostics, syntax).
Construct(((NamedTypeSymbol)targetType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics);
var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
var dictionaryConversion = Conversions.ClassifyConversionFromType(dictionaryType, targetType, isChecked: false, ref useSiteInfo);
diagnostics.Add(syntax, useSiteInfo);
if (!dictionaryConversion.IsImplicit)
{
GenerateImplicitConversionError(diagnostics, Compilation, syntax, dictionaryConversion, dictionaryType, targetType);
}

_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Dictionary_KV__ctor, diagnostics, syntax: syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Dictionary_KV__set_Item, diagnostics, syntax: syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Key, diagnostics, syntax: syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Value, diagnostics, syntax: syntax);
}
else if ((collectionTypeKind is CollectionExpressionTypeKind.ArrayInterface) ||
node.HasSpreadElements(out _, out _))
{
// Verify the existence of the List<T> members that may be used in lowering, even
// though not all will be used for any particular collection expression. Checking all
// gives a consistent behavior, regardless of collection expression elements.
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ctor, diagnostics, syntax: syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ctorInt32, diagnostics, syntax: syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__Add, diagnostics, syntax: syntax);
Expand All @@ -943,29 +964,45 @@ private BoundCollectionExpression ConvertCollectionExpression(
var elementConversions = conversion.UnderlyingConversions;

Debug.Assert(elementType is { });
Debug.Assert(elements.Length == elementConversions.Length);
Debug.Assert(elements.Length <= elementConversions.Length);
Debug.Assert(elementConversions.All(c => c.Exists));

for (int i = 0; i < elements.Length; i++)
int conversionIndex = 0;
foreach (var element in elements)
{
var element = elements[i];
var elementConversion = elementConversions[i];
var convertedElement = element is BoundCollectionExpressionSpreadElement spreadElement ?
bindSpreadElement(
spreadElement,
elementType,
elementConversion,
diagnostics) :
CreateConversion(
element.Syntax,
(BoundExpression)element,
elementConversion,
isCast: false,
conversionGroupOpt: null,
destination: elementType,
diagnostics);
BoundNode convertedElement;
switch (element)
{
case BoundCollectionExpressionSpreadElement spreadElement:
convertedElement = bindSpreadElement(
spreadElement,
elementType,
elementConversions[conversionIndex++],
diagnostics);
break;
case BoundKeyValuePairElement keyValuePairElement:
convertedElement = bindKeyValuePairElement(
keyValuePairElement,
elementType,
elementConversions[conversionIndex],
elementConversions[conversionIndex + 1],
diagnostics);
conversionIndex += 2;
break;
default:
convertedElement = CreateConversion(
element.Syntax,
(BoundExpression)element,
elementConversions[conversionIndex++],
isCast: false,
conversionGroupOpt: null,
destination: elementType,
diagnostics);
break;
}
builder.Add(convertedElement!);
}
Debug.Assert(conversionIndex == elementConversions.Length);
conversion.MarkUnderlyingConversionsChecked();
}

Expand Down Expand Up @@ -1009,6 +1046,30 @@ BoundNode bindSpreadElement(BoundCollectionExpressionSpreadElement element, Type
iteratorBody: new BoundExpressionStatement(expressionSyntax, convertElement) { WasCompilerGenerated = true },
lengthOrCount: element.LengthOrCount);
}

BoundNode bindKeyValuePairElement(BoundKeyValuePairElement element, TypeSymbol elementType, Conversion keyConversion, Conversion valueConversion, BindingDiagnosticBag diagnostics)
{
Debug.Assert(ReferenceEquals(elementType.OriginalDefinition, Compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_KeyValuePair_KV)));

var typeArguments = ((NamedTypeSymbol)elementType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics;
var convertedKey = CreateConversion(
element.Key.Syntax,
element.Key,
keyConversion,
isCast: false,
conversionGroupOpt: null,
destination: typeArguments[0].Type,
diagnostics);
var convertedValue = CreateConversion(
element.Value.Syntax,
element.Value,
valueConversion,
isCast: false,
conversionGroupOpt: null,
destination: typeArguments[1].Type,
diagnostics);
return element.Update(convertedKey, convertedValue);
}
}

private bool HasCollectionInitializerTypeInProgress(SyntaxNode syntax, TypeSymbol targetType)
Expand Down Expand Up @@ -1716,11 +1777,18 @@ private BoundCollectionExpression BindCollectionExpressionForErrorRecovery(
{
var syntax = node.Syntax;
var builder = ArrayBuilder<BoundNode>.GetInstance(node.Elements.Length);
bool reportNoTargetType = !targetType.IsErrorType();
foreach (var element in node.Elements)
{
var result = element is BoundExpression expression ?
BindToNaturalType(expression, diagnostics, reportNoTargetType: !targetType.IsErrorType()) :
element;
var result = element switch
{
BoundCollectionExpressionSpreadElement spreadElement => (BoundNode)spreadElement,
BoundKeyValuePairElement keyValuePairElement =>
keyValuePairElement.Update(
BindToNaturalType(keyValuePairElement.Key, diagnostics, reportNoTargetType),
BindToNaturalType(keyValuePairElement.Value, diagnostics, reportNoTargetType)),
_ => BindToNaturalType((BoundExpression)element, diagnostics, reportNoTargetType)
};
builder.Add(result);
}
return new BoundCollectionExpression(
Expand Down Expand Up @@ -1787,35 +1855,46 @@ internal void GenerateImplicitConversionErrorForCollectionExpression(
}
}

bool usesKeyValuePairs = ConversionsBase.CollectionUsesKeyValuePairs(Compilation, collectionTypeKind, elementType, out var keyType, out var valueType);
var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
foreach (var element in elements)
{
if (element is BoundCollectionExpressionSpreadElement spreadElement)
switch (element)
{
var enumeratorInfo = spreadElement.EnumeratorInfoOpt;
if (enumeratorInfo is null)
{
Error(diagnostics, ErrorCode.ERR_NoImplicitConv, spreadElement.Expression.Syntax, spreadElement.Expression.Display, elementType);
reportedErrors = true;
}
else
{
Conversion elementConversion = Conversions.GetCollectionExpressionSpreadElementConversion(spreadElement, elementType, ref useSiteInfo);
if (!elementConversion.Exists)
case BoundCollectionExpressionSpreadElement spreadElement:
{
var enumeratorInfo = spreadElement.EnumeratorInfoOpt;
if (enumeratorInfo is null)
{
Error(diagnostics, ErrorCode.ERR_NoImplicitConv, spreadElement.Expression.Syntax, spreadElement.Expression.Display, elementType);
reportedErrors = true;
}
else
{
Conversion elementConversion = Conversions.GetCollectionExpressionSpreadElementConversion(spreadElement, elementType, ref useSiteInfo);
if (!elementConversion.Exists)
{
GenerateImplicitConversionError(diagnostics, this.Compilation, spreadElement.Expression.Syntax, elementConversion, enumeratorInfo.ElementType, elementType);
reportedErrors = true;
}
}
}
break;
case BoundKeyValuePairElement keyValuePairElement:
if (usesKeyValuePairs)
{
generateImplicitConversionError(diagnostics, keyValuePairElement.Key, keyType.Type, ref useSiteInfo, ref reportedErrors);
generateImplicitConversionError(diagnostics, keyValuePairElement.Value, valueType.Type, ref useSiteInfo, ref reportedErrors);
}
else
{
GenerateImplicitConversionError(diagnostics, this.Compilation, spreadElement.Expression.Syntax, elementConversion, enumeratorInfo.ElementType, elementType);
Error(diagnostics, ErrorCode.ERR_CollectionExpressionKeyValuePairNotSupported, keyValuePairElement.Syntax, targetType);
reportedErrors = true;
}
}
}
else
{
Conversion elementConversion = Conversions.ClassifyImplicitConversionFromExpression((BoundExpression)element, elementType, ref useSiteInfo);
if (!elementConversion.Exists)
{
GenerateImplicitConversionError(diagnostics, element.Syntax, elementConversion, (BoundExpression)element, elementType);
reportedErrors = true;
}
break;
default:
generateImplicitConversionError(diagnostics, (BoundExpression)element, elementType, ref useSiteInfo, ref reportedErrors);
break;
}
}
Debug.Assert(reportedErrors);
Expand All @@ -1827,6 +1906,21 @@ internal void GenerateImplicitConversionErrorForCollectionExpression(
}

return;

void generateImplicitConversionError(
BindingDiagnosticBag diagnostics,
BoundExpression expression,
TypeSymbol targetType,
ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
ref bool reportedErrors)
{
Conversion elementConversion = Conversions.ClassifyImplicitConversionFromExpression(expression, targetType, ref useSiteInfo);
if (!elementConversion.Exists)
{
GenerateImplicitConversionError(diagnostics, expression.Syntax, elementConversion, expression, targetType);
reportedErrors = true;
}
}
}

private MethodSymbol? GetCollectionBuilderMethod(
Expand Down
21 changes: 9 additions & 12 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5199,18 +5199,6 @@ private BoundExpression BindCollectionExpression(CollectionExpressionSyntax synt

MessageID.IDS_FeatureCollectionExpressions.CheckFeatureAvailability(diagnostics, syntax, syntax.OpenBracketToken.GetLocation());

foreach (var element in syntax.Elements)
{
if (element is KeyValuePairElementSyntax keyValuePairElement)
{
MessageID.IDS_FeatureDictionaryExpressions.CheckFeatureAvailability(diagnostics, syntax, keyValuePairElement.ColonToken.GetLocation());

// PROTOTYPE: Error for now. Flesh this out when we do the binding for kvp elements.
Error(diagnostics, ErrorCode.ERR_SyntaxError, keyValuePairElement.ColonToken, ",");
return new BoundBadExpression(syntax, LookupResultKind.Empty, symbols: [], childBoundNodes: [], CreateErrorType());
}
}

var builder = ArrayBuilder<BoundNode>.GetInstance(syntax.Elements.Count);
foreach (var element in syntax.Elements)
{
Expand All @@ -5224,6 +5212,7 @@ static BoundNode bindElement(CollectionElementSyntax syntax, BindingDiagnosticBa
{
ExpressionElementSyntax { Expression: CollectionExpressionSyntax nestedCollectionExpression } => @this.BindCollectionExpression(nestedCollectionExpression, diagnostics, nestingLevel + 1),
ExpressionElementSyntax expressionElementSyntax => @this.BindValue(expressionElementSyntax.Expression, diagnostics, BindValueKind.RValue),
KeyValuePairElementSyntax kvpElementSyntax => @this.BindKeyValuePair(kvpElementSyntax, diagnostics),
SpreadElementSyntax spreadElementSyntax => bindSpreadElement(spreadElementSyntax, diagnostics, @this),
_ => throw ExceptionUtilities.UnexpectedValue(syntax.Kind())
};
Expand Down Expand Up @@ -5285,6 +5274,14 @@ static BoundNode bindSpreadElement(SpreadElementSyntax syntax, BindingDiagnostic
hasErrors: false);
}
}

private BoundNode BindKeyValuePair(KeyValuePairElementSyntax syntax, BindingDiagnosticBag diagnostics)
{
MessageID.IDS_FeatureDictionaryExpressions.CheckFeatureAvailability(diagnostics, syntax, syntax.ColonToken.GetLocation());
var key = BindValue(syntax.KeyExpression, diagnostics, BindValueKind.RValue);
var value = BindValue(syntax.ValueExpression, diagnostics, BindValueKind.RValue);
return new BoundKeyValuePairElement(syntax, key, value);
}
#nullable disable

private BoundExpression BindDelegateCreationExpression(ObjectCreationExpressionSyntax node, NamedTypeSymbol type, BindingDiagnosticBag diagnostics)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ internal enum CollectionExpressionTypeKind
CollectionBuilder,
ImplementsIEnumerable,
ArrayInterface,
DictionaryInterface,
}
}
Loading

0 comments on commit 5439dd5

Please sign in to comment.