|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
namespace Microsoft.CodeAnalysis.CSharp
{
// Shared code for rewriting Object and Collection initializer expressions
internal sealed partial class LocalRewriter
{
private static BoundObjectInitializerExpressionBase UpdateInitializers(BoundObjectInitializerExpressionBase initializerExpression, ImmutableArray<BoundExpression> newInitializers)
{
switch (initializerExpression)
{
case BoundObjectInitializerExpression objectInitializer:
return objectInitializer.Update(objectInitializer.Placeholder, newInitializers, initializerExpression.Type);
case BoundCollectionInitializerExpression collectionInitializer:
return collectionInitializer.Update(collectionInitializer.Placeholder, newInitializers, initializerExpression.Type);
default:
throw ExceptionUtilities.UnexpectedValue(initializerExpression.Kind);
}
}
private void AddObjectOrCollectionInitializers(
ref ArrayBuilder<BoundExpression>? dynamicSiteInitializers,
ref ArrayBuilder<LocalSymbol>? temps,
ArrayBuilder<BoundExpression> result,
BoundExpression rewrittenReceiver,
BoundExpression initializerExpression)
{
Debug.Assert(!_inExpressionLambda);
Debug.Assert(rewrittenReceiver != null);
switch (initializerExpression)
{
case BoundObjectInitializerExpression objectInitializer:
{
var placeholder = objectInitializer.Placeholder;
AddPlaceholderReplacement(placeholder, rewrittenReceiver);
AddObjectInitializers(ref dynamicSiteInitializers, ref temps, result, rewrittenReceiver, objectInitializer.Initializers);
RemovePlaceholderReplacement(placeholder);
}
return;
case BoundCollectionInitializerExpression collectionInitializer:
{
var placeholder = collectionInitializer.Placeholder;
AddPlaceholderReplacement(placeholder, rewrittenReceiver);
AddCollectionInitializers(result, rewrittenReceiver, collectionInitializer.Initializers);
RemovePlaceholderReplacement(placeholder);
}
return;
default:
throw ExceptionUtilities.UnexpectedValue(initializerExpression.Kind);
}
}
private ImmutableArray<BoundExpression> MakeObjectOrCollectionInitializersForExpressionTree(BoundExpression initializerExpression)
{
Debug.Assert(_inExpressionLambda);
switch (initializerExpression.Kind)
{
case BoundKind.ObjectInitializerExpression:
return VisitList(((BoundObjectInitializerExpression)initializerExpression).Initializers);
case BoundKind.CollectionInitializerExpression:
var result = ArrayBuilder<BoundExpression>.GetInstance();
AddCollectionInitializers(result, null, ((BoundCollectionInitializerExpression)initializerExpression).Initializers);
return result.ToImmutableAndFree();
default:
throw ExceptionUtilities.UnexpectedValue(initializerExpression.Kind);
}
}
// Rewrite collection initializer add method calls:
// 2) new List<int> { 1 };
// ~
private void AddCollectionInitializers(ArrayBuilder<BoundExpression> result, BoundExpression? rewrittenReceiver, ImmutableArray<BoundExpression> initializers)
{
Debug.Assert(rewrittenReceiver is { } || _inExpressionLambda);
foreach (var initializer in initializers)
{
// In general bound initializers may contain bad expressions or element initializers.
// We don't lower them if they contain errors, so it's safe to assume an element initializer.
BoundExpression? rewrittenInitializer;
if (initializer.Kind == BoundKind.CollectionElementInitializer)
{
rewrittenInitializer = MakeCollectionInitializer(rewrittenReceiver, (BoundCollectionElementInitializer)initializer);
}
else
{
Debug.Assert(!_inExpressionLambda);
Debug.Assert(initializer.Kind == BoundKind.DynamicCollectionElementInitializer);
rewrittenInitializer = MakeDynamicCollectionInitializer(rewrittenReceiver!, (BoundDynamicCollectionElementInitializer)initializer);
}
// the call to Add may be omitted
if (rewrittenInitializer != null)
{
result.Add(rewrittenInitializer);
}
}
}
private BoundExpression MakeDynamicCollectionInitializer(BoundExpression rewrittenReceiver, BoundDynamicCollectionElementInitializer initializer)
{
var rewrittenArguments = VisitList(initializer.Arguments);
// If we are calling a method on a NoPIA type, we need to embed all methods/properties
// with the matching name of this dynamic invocation.
EmbedIfNeedTo(rewrittenReceiver, initializer.ApplicableMethods, initializer.Syntax);
return _dynamicFactory.MakeDynamicMemberInvocation(
WellKnownMemberNames.CollectionInitializerAddMethodName,
rewrittenReceiver,
ImmutableArray<TypeWithAnnotations>.Empty,
rewrittenArguments,
default(ImmutableArray<string>),
default(ImmutableArray<RefKind>),
hasImplicitReceiver: false,
resultDiscarded: true).ToExpression();
}
// Rewrite collection initializer element Add method call:
// new List<int> { 1, 2, 3 }; OR new List<int> { { 1, 2 }, 3 };
// ~ ~~~~~~~~
private BoundExpression? MakeCollectionInitializer(BoundExpression? rewrittenReceiver, BoundCollectionElementInitializer initializer)
{
MethodSymbol addMethod = initializer.AddMethod;
Debug.Assert(addMethod.Name == "Add");
Debug.Assert(addMethod.Parameters
.Skip(addMethod.IsExtensionMethod ? 1 : 0)
.All(p => p.RefKind == RefKind.None || p.RefKind == RefKind.In));
Debug.Assert(initializer.Arguments.Any());
Debug.Assert(rewrittenReceiver != null || _inExpressionLambda);
var syntax = initializer.Syntax;
if (_allowOmissionOfConditionalCalls)
{
// NOTE: Calls cannot be omitted within an expression tree (CS0765); this should already
// have been checked.
if (addMethod.CallsAreOmitted(initializer.SyntaxTree))
{
return null;
}
}
var argumentRefKindsOpt = default(ImmutableArray<RefKind>);
if (addMethod.Parameters[0].RefKind == RefKind.Ref)
{
// If the Add method is an extension which takes a `ref this` as the first parameter, implicitly add a `ref` to the argument
// Initializer element syntax cannot have `ref`, `in`, or `out` keywords.
// Arguments to `in` parameters will be converted to have RefKind.In later on.
var builder = ArrayBuilder<RefKind>.GetInstance(addMethod.Parameters.Length, RefKind.None);
builder[0] = RefKind.Ref;
argumentRefKindsOpt = builder.ToImmutableAndFree();
}
// The receiver for a collection initializer is already a temp, so we don't need to preserve any additional temp stores beyond this method.
ArrayBuilder<LocalSymbol>? temps = null;
ImmutableArray<BoundExpression> rewrittenArguments = VisitArgumentsAndCaptureReceiverIfNeeded(
ref rewrittenReceiver,
captureReceiverMode: ReceiverCaptureMode.Default,
initializer.Arguments,
addMethod,
initializer.ArgsToParamsOpt,
argumentRefKindsOpt,
storesOpt: null,
ref temps);
rewrittenArguments = MakeArguments(syntax, rewrittenArguments, addMethod, initializer.Expanded, initializer.ArgsToParamsOpt, ref argumentRefKindsOpt, ref temps);
var rewrittenType = VisitType(initializer.Type);
if (initializer.InvokedAsExtensionMethod)
{
Debug.Assert(addMethod.IsStatic);
Debug.Assert(addMethod.IsExtensionMethod);
Debug.Assert(!_inExpressionLambda, "Expression trees do not support extension Add");
rewrittenReceiver = null;
}
if (_inExpressionLambda)
{
Debug.Assert(temps.Count == 0);
temps.Free();
return initializer.Update(addMethod, rewrittenArguments, rewrittenReceiver, expanded: false, argsToParamsOpt: default, defaultArguments: default, initializer.InvokedAsExtensionMethod, initializer.ResultKind, rewrittenType);
}
return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.InvokedAsExtensionMethod, initializer.ResultKind, addMethod.ReturnType, temps.ToImmutableAndFree());
}
private BoundExpression VisitObjectInitializerMember(BoundObjectInitializerMember node, ref BoundExpression rewrittenReceiver, ArrayBuilder<BoundExpression> sideEffects, ref ArrayBuilder<LocalSymbol>? temps)
{
if (node.MemberSymbol is null)
{
return (BoundExpression)base.VisitObjectInitializerMember(node)!;
}
var originalReceiver = rewrittenReceiver;
ArrayBuilder<LocalSymbol>? constructionTemps = null;
var rewrittenArguments = VisitArgumentsAndCaptureReceiverIfNeeded(ref rewrittenReceiver, captureReceiverMode: ReceiverCaptureMode.Default, node.Arguments, node.MemberSymbol, node.ArgsToParamsOpt, node.ArgumentRefKindsOpt,
storesOpt: null, ref constructionTemps);
if (constructionTemps != null)
{
if (temps == null)
{
temps = constructionTemps;
}
else
{
temps.AddRange(constructionTemps);
constructionTemps.Free();
}
}
if (originalReceiver != rewrittenReceiver && rewrittenReceiver is BoundSequence sequence)
{
Debug.Assert(temps != null);
temps.AddRange(sequence.Locals);
sideEffects.AddRange(sequence.SideEffects);
rewrittenReceiver = sequence.Value;
}
return node.Update(node.MemberSymbol, rewrittenArguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.DefaultArguments, node.ResultKind, node.ReceiverType, node.Type);
}
// Rewrite object initializer member assignments and add them to the result.
private void AddObjectInitializers(
ref ArrayBuilder<BoundExpression>? dynamicSiteInitializers,
ref ArrayBuilder<LocalSymbol>? temps,
ArrayBuilder<BoundExpression> result,
BoundExpression rewrittenReceiver,
ImmutableArray<BoundExpression> initializers)
{
Debug.Assert(!_inExpressionLambda);
foreach (var initializer in initializers)
{
// In general bound initializers may contain bad expressions or assignments.
// We don't lower them if they contain errors, so it's safe to assume an assignment.
AddObjectInitializer(ref dynamicSiteInitializers, ref temps, result, rewrittenReceiver, (BoundAssignmentOperator)initializer);
}
}
// Rewrite object initializer member assignment and add it to the result.
// new SomeType { Member = 0 };
// ~~~~~~~~~~
private void AddObjectInitializer(
ref ArrayBuilder<BoundExpression>? dynamicSiteInitializers,
ref ArrayBuilder<LocalSymbol>? temps,
ArrayBuilder<BoundExpression> result,
BoundExpression rewrittenReceiver,
BoundAssignmentOperator assignment)
{
Debug.Assert(rewrittenReceiver != null);
Debug.Assert(!_inExpressionLambda);
// Update the receiver for the field/property access as we might have introduced a temp for the initializer rewrite.
BoundExpression? rewrittenLeft = null;
// Do not lower pointer access yet, we'll do it later.
if (assignment.Left.Kind != BoundKind.PointerElementAccess)
{
rewrittenLeft = assignment.Left is BoundObjectInitializerMember member ? VisitObjectInitializerMember(member, ref rewrittenReceiver, result, ref temps) : VisitExpression(assignment.Left);
}
BoundKind rhsKind = assignment.Right.Kind;
bool isRhsNestedInitializer = rhsKind == BoundKind.ObjectInitializerExpression || rhsKind == BoundKind.CollectionInitializerExpression;
BoundExpression rewrittenAccess;
switch ((rewrittenLeft ?? assignment.Left).Kind)
{
case BoundKind.ObjectInitializerMember:
{
var memberInit = (BoundObjectInitializerMember?)rewrittenLeft;
Debug.Assert(memberInit is { });
if (!memberInit.Arguments.IsDefaultOrEmpty)
{
var args = EvaluateSideEffectingArgumentsToTemps(
memberInit.Arguments,
memberInit.MemberSymbol?.GetParameterRefKinds() ?? default(ImmutableArray<RefKind>),
result,
ref temps);
memberInit = memberInit.Update(
memberInit.MemberSymbol,
args,
memberInit.ArgumentNamesOpt,
memberInit.ArgumentRefKindsOpt,
memberInit.Expanded,
memberInit.ArgsToParamsOpt,
memberInit.DefaultArguments,
memberInit.ResultKind,
memberInit.ReceiverType,
memberInit.Type);
}
if (memberInit.MemberSymbol == null && memberInit.Type.IsDynamic())
{
if (dynamicSiteInitializers == null)
{
dynamicSiteInitializers = ArrayBuilder<BoundExpression>.GetInstance();
}
if (!isRhsNestedInitializer)
{
var rewrittenRight = VisitExpression(assignment.Right);
var setMember = _dynamicFactory.MakeDynamicSetIndex(
rewrittenReceiver,
memberInit.Arguments,
memberInit.ArgumentNamesOpt,
memberInit.ArgumentRefKindsOpt,
rewrittenRight);
Debug.Assert(setMember.SiteInitialization is { });
dynamicSiteInitializers.Add(setMember.SiteInitialization);
result.Add(setMember.SiteInvocation);
return;
}
var getMember = _dynamicFactory.MakeDynamicGetIndex(
rewrittenReceiver,
memberInit.Arguments,
memberInit.ArgumentNamesOpt,
memberInit.ArgumentRefKindsOpt);
Debug.Assert(getMember.SiteInitialization is { });
dynamicSiteInitializers.Add(getMember.SiteInitialization);
rewrittenAccess = getMember.SiteInvocation;
}
else
{
rewrittenAccess = MakeObjectInitializerMemberAccess(rewrittenReceiver, memberInit, isRhsNestedInitializer);
if (!isRhsNestedInitializer)
{
// Rewrite simple assignment to field/property.
var rewrittenRight = VisitExpression(assignment.Right);
result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: assignment.IsRef, assignment.Type, used: false));
return;
}
}
break;
}
case BoundKind.DynamicObjectInitializerMember:
{
if (dynamicSiteInitializers == null)
{
dynamicSiteInitializers = ArrayBuilder<BoundExpression>.GetInstance();
}
Debug.Assert(rewrittenLeft is { });
var initializerMember = (BoundDynamicObjectInitializerMember)rewrittenLeft;
if (!isRhsNestedInitializer)
{
var rewrittenRight = VisitExpression(assignment.Right);
var setMember = _dynamicFactory.MakeDynamicSetMember(rewrittenReceiver, initializerMember.MemberName, rewrittenRight);
Debug.Assert(setMember.SiteInitialization is { });
dynamicSiteInitializers.Add(setMember.SiteInitialization);
result.Add(setMember.SiteInvocation);
return;
}
var getMember = _dynamicFactory.MakeDynamicGetMember(rewrittenReceiver, initializerMember.MemberName, resultIndexed: false);
Debug.Assert(getMember.SiteInitialization is { });
dynamicSiteInitializers.Add(getMember.SiteInitialization);
rewrittenAccess = getMember.SiteInvocation;
break;
}
case BoundKind.ArrayAccess:
{
Debug.Assert(rewrittenLeft is { });
var arrayAccess = (BoundArrayAccess)rewrittenLeft;
var indices = EvaluateSideEffectingArgumentsToTemps(
arrayAccess.Indices,
paramRefKindsOpt: default,
result,
ref temps);
rewrittenAccess = arrayAccess.Update(rewrittenReceiver, indices, arrayAccess.Type);
if (!isRhsNestedInitializer)
{
// Rewrite simple assignment to field/property.
var rewrittenRight = VisitExpression(assignment.Right);
result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, assignment.Type, used: false));
return;
}
break;
}
case BoundKind.PointerElementAccess:
{
// Remember we haven't lowered this node yet.
var pointerAccess = (BoundPointerElementAccess)assignment.Left;
var rewrittenIndex = VisitExpression(pointerAccess.Index);
if (CanChangeValueBetweenReads(rewrittenIndex))
{
BoundAssignmentOperator store;
var temp = _factory.StoreToTemp(rewrittenIndex, out store);
rewrittenIndex = temp;
if (temps == null)
{
temps = ArrayBuilder<LocalSymbol>.GetInstance();
}
temps.Add(temp.LocalSymbol);
result.Add(store);
}
rewrittenAccess = RewritePointerElementAccess(pointerAccess, rewrittenReceiver, rewrittenIndex);
if (!isRhsNestedInitializer)
{
// Rewrite as simple assignment.
var rewrittenRight = VisitExpression(assignment.Right);
result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, assignment.Type, used: false));
return;
}
break;
}
default:
throw ExceptionUtilities.UnexpectedValue((rewrittenLeft ?? assignment.Left).Kind);
}
AddObjectOrCollectionInitializers(ref dynamicSiteInitializers, ref temps, result, rewrittenAccess, assignment.Right);
}
private ImmutableArray<BoundExpression> EvaluateSideEffectingArgumentsToTemps(
ImmutableArray<BoundExpression> args,
ImmutableArray<RefKind> paramRefKindsOpt,
ArrayBuilder<BoundExpression> sideeffects,
ref ArrayBuilder<LocalSymbol>? temps)
{
ArrayBuilder<BoundExpression>? newArgs = null;
for (int i = 0; i < args.Length; i++)
{
var arg = args[i];
if (CanChangeValueBetweenReads(arg))
{
if (newArgs == null)
{
newArgs = ArrayBuilder<BoundExpression>.GetInstance(args.Length);
newArgs.AddRange(args, i);
}
RefKind refKind = paramRefKindsOpt.RefKinds(i);
BoundAssignmentOperator store;
var temp = _factory.StoreToTemp(arg, out store, refKind);
newArgs.Add(temp);
if (temps == null)
{
temps = ArrayBuilder<LocalSymbol>.GetInstance();
}
temps.Add(temp.LocalSymbol);
sideeffects.Add(store);
}
else if (newArgs != null)
{
newArgs.Add(arg);
}
}
return newArgs?.ToImmutableAndFree() ?? args;
}
private BoundExpression MakeObjectInitializerMemberAccess(
BoundExpression rewrittenReceiver,
BoundObjectInitializerMember rewrittenLeft,
bool isRhsNestedInitializer)
{
var memberSymbol = rewrittenLeft.MemberSymbol;
Debug.Assert(memberSymbol is object);
#if DEBUG
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
Debug.Assert(_compilation.Conversions.ClassifyConversionFromType(rewrittenReceiver.Type, memberSymbol.ContainingType, isChecked: false, ref discardedUseSiteInfo).IsImplicit);
// It is possible there are use site diagnostics from the above, but none that we need report as we aren't generating code for the conversion
#endif
switch (memberSymbol.Kind)
{
case SymbolKind.Field:
var fieldSymbol = (FieldSymbol)memberSymbol;
return MakeFieldAccess(rewrittenLeft.Syntax, rewrittenReceiver, fieldSymbol, null, rewrittenLeft.ResultKind, fieldSymbol.Type);
case SymbolKind.Property:
var propertySymbol = (PropertySymbol)memberSymbol;
var arguments = rewrittenLeft.Arguments;
if (!arguments.IsEmpty || propertySymbol.IsIndexedProperty)
{
return MakeIndexerAccess(
rewrittenLeft.Syntax,
rewrittenReceiver,
propertySymbol,
rewrittenLeft.Arguments,
rewrittenLeft.ArgumentNamesOpt,
rewrittenLeft.ArgumentRefKindsOpt,
rewrittenLeft.Expanded,
rewrittenLeft.ArgsToParamsOpt,
rewrittenLeft.DefaultArguments,
type: propertySymbol.Type,
oldNodeOpt: null,
isLeftOfAssignment: !isRhsNestedInitializer);
}
else
{
return MakePropertyAccess(
rewrittenLeft.Syntax,
rewrittenReceiver,
propertySymbol,
rewrittenLeft.ResultKind,
propertySymbol.Type,
isLeftOfAssignment: !isRhsNestedInitializer);
}
case SymbolKind.Event:
var eventSymbol = (EventSymbol)memberSymbol;
return MakeEventAccess(rewrittenLeft.Syntax, rewrittenReceiver, eventSymbol, null, rewrittenLeft.ResultKind, eventSymbol.Type);
default:
throw ExceptionUtilities.UnexpectedValue(memberSymbol.Kind);
}
}
}
}
|