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

Collection expressions: IOperation support #70650

Merged
merged 10 commits into from
Nov 30, 2023
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
11 changes: 6 additions & 5 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ private BoundCollectionExpression ConvertCollectionExpression(
}

Debug.Assert(collectionBuilderReturnTypeConversion.Exists);
collectionBuilderInvocationPlaceholder = new BoundValuePlaceholder(syntax, collectionBuilderMethod.ReturnType);
collectionBuilderInvocationPlaceholder = new BoundValuePlaceholder(syntax, collectionBuilderMethod.ReturnType) { WasCompilerGenerated = true };
collectionBuilderInvocationConversion = CreateConversion(collectionBuilderInvocationPlaceholder, targetType, diagnostics);

ReportUseSite(collectionBuilderMethod, diagnostics, syntax.Location);
Expand Down Expand Up @@ -650,9 +650,10 @@ private BoundCollectionExpression ConvertCollectionExpression(
}
else if (targetType is TypeParameterSymbol typeParameter)
{
var arguments = AnalyzedArguments.GetInstance();
collectionCreation = BindTypeParameterCreationExpression(syntax, typeParameter, arguments, initializerOpt: null, typeSyntax: syntax, wasTargetTyped: true, diagnostics);
arguments.Free();
var analyzedArguments = AnalyzedArguments.GetInstance();
collectionCreation = BindTypeParameterCreationExpression(syntax, typeParameter, analyzedArguments, initializerOpt: null, typeSyntax: syntax, wasTargetTyped: true, diagnostics);
collectionCreation.WasCompilerGenerated = true;
analyzedArguments.Free();
}
else
{
Expand Down Expand Up @@ -735,7 +736,7 @@ BoundNode bindSpreadElement(BoundCollectionExpressionSpreadElement element, Type
Debug.Assert(enumeratorInfo is { });
Debug.Assert(enumeratorInfo.ElementType is { }); // ElementType is set always, even for IEnumerable.

var elementPlaceholder = new BoundValuePlaceholder(syntax, enumeratorInfo.ElementType);
var elementPlaceholder = new BoundValuePlaceholder(syntax, enumeratorInfo.ElementType) { WasCompilerGenerated = true };
var convertElement = CreateConversion(
element.Syntax,
elementPlaceholder,
Expand Down
16 changes: 8 additions & 8 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4807,8 +4807,7 @@ static BoundNode bindSpreadElement(SpreadElementSyntax syntax, BindingDiagnostic
lengthOrCount: lengthOrCount,
elementPlaceholder: null,
iteratorBody: null,
hasErrors: false)
{ WasCompilerGenerated = true };
hasErrors: false);
}
}
#nullable disable
Expand Down Expand Up @@ -5978,12 +5977,13 @@ private BoundCollectionExpressionSpreadElement BindCollectionExpressionSpreadEle

Debug.Assert(enumeratorInfo.ElementType is { }); // ElementType is set always, even for IEnumerable.
var addElementPlaceholder = new BoundValuePlaceholder(syntax, enumeratorInfo.ElementType);
var addMethodInvocation = collectionInitializerAddMethodBinder.MakeInvocationExpression(
syntax,
implicitReceiver,
methodName: WellKnownMemberNames.CollectionInitializerAddMethodName,
args: ImmutableArray.Create<BoundExpression>(addElementPlaceholder),
diagnostics);
var addMethodInvocation = BindCollectionInitializerElementAddMethod(
syntax.Expression,
ImmutableArray.Create((BoundExpression)addElementPlaceholder),
hasEnumerableInitializerType: true,
collectionInitializerAddMethodBinder,
diagnostics,
implicitReceiver);
return element.Update(
element.Expression,
expressionPlaceholder: element.ExpressionPlaceholder,
Expand Down
23 changes: 23 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,29 @@ public static Conversion GetOutConversion(this ICompoundAssignmentOperation comp
}
}

/// <summary>
/// Gets the underlying element <see cref="Conversion"/> information from this <see cref="ISpreadOperation"/>.
/// </summary>
/// <remarks>
/// This spread operation must have been created from C# code.
/// </remarks>
public static Conversion GetElementConversion(this ISpreadOperation spread)
{
if (spread == null)
{
throw new ArgumentNullException(nameof(spread));
}

if (spread.Language == LanguageNames.CSharp)
{
return (Conversion)((SpreadOperation)spread).ElementConversionConvertible;
}
else
{
throw new ArgumentException(string.Format(CSharpResources.ISpreadOperationIsNotCSharpSpread, nameof(spread)), nameof(spread));
}
}

public static Conversion GetSpeculativeConversion(this SemanticModel? semanticModel, int position, ExpressionSyntax expression, SpeculativeBindingOption bindingOption)
{
var csmodel = semanticModel as CSharpSemanticModel;
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -5941,6 +5941,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ICompoundAssignmentOperationIsNotCSharpCompoundAssignment" xml:space="preserve">
<value>{0} is not a valid C# compound assignment operation</value>
</data>
<data name="ISpreadOperationIsNotCSharpSpread" xml:space="preserve">
<value>{0} is not a valid C# spread operation</value>
</data>
<data name="WRN_FilterIsConstantFalse" xml:space="preserve">
<value>Filter expression is a constant 'false', consider removing the catch clause</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3491,9 +3491,10 @@ protected override void VisitStatement(BoundStatement statement)
switch (element)
{
case BoundCollectionElementInitializer initializer:
var collectionType = initializer.AddMethod.ContainingType;

var completion = VisitCollectionElementInitializer(initializer, collectionType,
Debug.Assert(node.Placeholder is { });
var completion = VisitCollectionElementInitializer(
initializer,
containingType: node.Placeholder.Type,
delayCompletionForType: false /* All collection expressions are target-typed */);

Debug.Assert(completion is null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,17 +181,20 @@ private BoundExpression VisitCollectionInitializerCollectionExpression(BoundColl

foreach (var element in elements)
{
var rewrittenElement = element switch
{
BoundCollectionElementInitializer collectionInitializer => MakeCollectionInitializer(temp, collectionInitializer),
BoundDynamicCollectionElementInitializer dynamicInitializer => MakeDynamicCollectionInitializer(temp, dynamicInitializer),
BoundCollectionExpressionSpreadElement spreadElement =>
MakeCollectionExpressionSpreadElement(
spreadElement,
VisitExpression(spreadElement.Expression),
static (rewriter, iteratorBody) => rewriter.VisitStatement(iteratorBody)!),
_ => throw ExceptionUtilities.UnexpectedValue(element)
};
var rewrittenElement = element is BoundCollectionExpressionSpreadElement spreadElement ?
MakeCollectionExpressionSpreadElement(
spreadElement,
VisitExpression(spreadElement.Expression),
iteratorBody =>
{
var syntax = iteratorBody.Syntax;
var rewrittenValue = rewriteCollectionInitializer(temp, ((BoundExpressionStatement)iteratorBody).Expression);
// MakeCollectionInitializer() may return null if Add() is marked [Conditional].
return rewrittenValue is { } ?
new BoundExpressionStatement(syntax, rewrittenValue) :
new BoundNoOpStatement(syntax, NoOpStatementFlavor.Default);
}) :
rewriteCollectionInitializer(temp, (BoundExpression)element);
if (rewrittenElement != null)
{
sideEffects.Add(rewrittenElement);
Expand All @@ -206,6 +209,16 @@ private BoundExpression VisitCollectionInitializerCollectionExpression(BoundColl
sideEffects.ToImmutableAndFree(),
temp,
collectionType);

BoundExpression? rewriteCollectionInitializer(BoundLocal rewrittenReceiver, BoundExpression expressionElement)
{
return expressionElement switch
{
BoundCollectionElementInitializer collectionInitializer => MakeCollectionInitializer(rewrittenReceiver, collectionInitializer),
BoundDynamicCollectionElementInitializer dynamicInitializer => MakeDynamicCollectionInitializer(rewrittenReceiver, dynamicInitializer),
var e => throw ExceptionUtilities.UnexpectedValue(e)
};
}
}

private BoundExpression VisitListInterfaceCollectionExpression(BoundCollectionExpression node)
Expand Down Expand Up @@ -722,7 +735,7 @@ private void AddCollectionExpressionElements(
var rewrittenElement = MakeCollectionExpressionSpreadElement(
spreadElement,
rewrittenExpression,
(_, iteratorBody) =>
iteratorBody =>
{
var rewrittenValue = VisitExpression(((BoundExpressionStatement)iteratorBody).Expression);
var builder = ArrayBuilder<BoundExpression>.GetInstance();
Expand Down Expand Up @@ -796,7 +809,7 @@ BoundExpression add(BoundExpression? sum, BoundExpression value)
private BoundExpression MakeCollectionExpressionSpreadElement(
BoundCollectionExpressionSpreadElement node,
BoundExpression rewrittenExpression,
Func<LocalRewriter, BoundStatement, BoundStatement> getRewrittenBody)
Func<BoundStatement, BoundStatement> rewriteBody)
{
var enumeratorInfo = node.EnumeratorInfoOpt;
var convertedExpression = (BoundConversion?)node.Conversion;
Expand All @@ -816,7 +829,7 @@ private BoundExpression MakeCollectionExpressionSpreadElement(
var iterationLocal = _factory.Local(iterationVariable);

AddPlaceholderReplacement(elementPlaceholder, iterationLocal);
var rewrittenBody = getRewrittenBody(this, iteratorBody);
var rewrittenBody = rewriteBody(iteratorBody);
RemovePlaceholderReplacement(elementPlaceholder);

var iterationVariables = ImmutableArray.Create(iterationVariable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ public CSharpOperationFactory(SemanticModel semanticModel)
return CreateBoundArrayInitializationOperation((BoundArrayInitialization)boundNode);
case BoundKind.CollectionExpression:
return CreateBoundCollectionExpression((BoundCollectionExpression)boundNode);
case BoundKind.CollectionExpressionSpreadElement:
return CreateBoundCollectionExpressionSpreadElement((BoundCollectionExpressionSpreadElement)boundNode);
case BoundKind.DefaultLiteral:
return CreateBoundDefaultLiteralOperation((BoundDefaultLiteral)boundNode);
case BoundKind.DefaultExpression:
Expand Down Expand Up @@ -1222,45 +1220,81 @@ private IArrayInitializerOperation CreateBoundArrayInitializationOperation(Bound
return new ArrayInitializerOperation(elementValues, _semanticModel, syntax, isImplicit);
}

private IOperation CreateBoundCollectionExpression(BoundCollectionExpression boundCollectionExpression)
private ICollectionExpressionOperation CreateBoundCollectionExpression(BoundCollectionExpression expr)
{
ImmutableArray<IOperation> elements = createChildren(boundCollectionExpression.Elements);
SyntaxNode syntax = boundCollectionExpression.Syntax;
ITypeSymbol? type = boundCollectionExpression.GetPublicTypeSymbol();
bool isImplicit = boundCollectionExpression.WasCompilerGenerated;
return new NoneOperation(elements, _semanticModel, syntax, type: type, constantValue: null, isImplicit);
SyntaxNode syntax = expr.Syntax;
ITypeSymbol? collectionType = expr.GetPublicTypeSymbol();
bool isImplicit = expr.WasCompilerGenerated;
IMethodSymbol? constructMethod = getConstructMethod((CSharpCompilation)_semanticModel.Compilation, expr).GetPublicSymbol();
ImmutableArray<IOperation> elements = expr.Elements.SelectAsArray(e => CreateBoundCollectionExpressionElement(e));
return new CollectionExpressionOperation(
constructMethod,
elements,
_semanticModel,
syntax,
collectionType,
isImplicit);

ImmutableArray<IOperation> createChildren(ImmutableArray<BoundNode> elements)
static MethodSymbol? getConstructMethod(CSharpCompilation compilation, BoundCollectionExpression expr)
{
var builder = ArrayBuilder<IOperation>.GetInstance(elements.Length);
foreach (var element in elements)
switch (expr.CollectionTypeKind)
{
var child = createChild(element);
if (child is { })
{
builder.Add(child);
}
case CollectionExpressionTypeKind.None:
case CollectionExpressionTypeKind.Array:
case CollectionExpressionTypeKind.ArrayInterface:
case CollectionExpressionTypeKind.ReadOnlySpan:
case CollectionExpressionTypeKind.Span:
return null;
case CollectionExpressionTypeKind.ImplementsIEnumerable:
case CollectionExpressionTypeKind.ImplementsIEnumerableT:
return (expr.CollectionCreation as BoundObjectCreationExpression)?.Constructor;
case CollectionExpressionTypeKind.CollectionBuilder:
return expr.CollectionBuilderMethod;
case CollectionExpressionTypeKind.ImmutableArray:
// https://github.com/dotnet/roslyn/issues/70880: Return the [CollectionBuilder] method.
return null;
case CollectionExpressionTypeKind.List:
Debug.Assert(expr.Type is { });
return ((MethodSymbol?)compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ctor))?.AsMember((NamedTypeSymbol)expr.Type);
default:
throw ExceptionUtilities.UnexpectedValue(expr.CollectionTypeKind);
}
return builder.ToImmutableAndFree();
}
}

IOperation? createChild(BoundNode element)
private IOperation CreateBoundCollectionExpressionElement(BoundNode element)
{
return element is BoundCollectionExpressionSpreadElement spreadElement ?
CreateBoundCollectionExpressionSpreadElement(spreadElement) :
Create(GetUnderlyingCollectionExpressionElement((BoundExpression)element));
}

private static BoundExpression GetUnderlyingCollectionExpressionElement(BoundExpression element)
{
return element switch
{
var result = element switch
{
BoundCollectionElementInitializer initializer => initializer.Arguments.First(),
_ => element,
};
return Create(result);
}
BoundCollectionElementInitializer collectionInitializer => collectionInitializer.Arguments[collectionInitializer.InvokedAsExtensionMethod ? 1 : 0],
BoundDynamicCollectionElementInitializer dynamicInitializer => dynamicInitializer.Arguments[0],
_ => element,
};
}

private IOperation CreateBoundCollectionExpressionSpreadElement(BoundCollectionExpressionSpreadElement boundSpreadExpression)
private ISpreadOperation CreateBoundCollectionExpressionSpreadElement(BoundCollectionExpressionSpreadElement element)
{
SyntaxNode syntax = boundSpreadExpression.Syntax;
bool isImplicit = boundSpreadExpression.WasCompilerGenerated;
var children = ImmutableArray.Create<IOperation>(Create(boundSpreadExpression.Expression));
return new NoneOperation(children, _semanticModel, syntax, type: null, constantValue: null, isImplicit);
var iteratorBody = ((BoundExpressionStatement?)element.IteratorBody)?.Expression;
var iteratorItem = iteratorBody is null ? null : GetUnderlyingCollectionExpressionElement(iteratorBody);
var collection = Create(element.Expression);
SyntaxNode syntax = element.Syntax;
bool isImplicit = element.WasCompilerGenerated;
var elementType = element.EnumeratorInfoOpt?.ElementType.GetPublicSymbol();
var elementConversion = BoundNode.GetConversion(iteratorItem, element.ElementPlaceholder);
return new SpreadOperation(
collection,
elementType: elementType,
elementConversion,
_semanticModel,
syntax,
isImplicit);
}

private IDefaultValueOperation CreateBoundDefaultLiteralOperation(BoundDefaultLiteral boundDefaultLiteral)
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ Microsoft.CodeAnalysis.CSharp.Conversion.IsCollectionExpression.get -> bool
Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.ReadOnlyKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken
Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax!
Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.WithReadOnlyKeyword(Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax!
static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetElementConversion(this Microsoft.CodeAnalysis.Operations.ISpreadOperation! spread) -> Microsoft.CodeAnalysis.CSharp.Conversion
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CrefParameter(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax!
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading