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

Dictionary expressions: initial binding and lowering support #76257

Merged
merged 17 commits into from
Dec 18, 2024
Merged
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