public override BoundNode VisitIndexerAccess(BoundIndexerAccess node)
        {
            Debug.Assert(node.Indexer.IsIndexer || node.Indexer.IsIndexedProperty);
            Debug.Assert((object)node.Indexer.GetOwnOrInheritedGetMethod() != null);

            return VisitIndexerAccess(node, isLeftOfAssignment: false);
        }
Beispiel #2
0
        public BoundIndexerAccess Update(bool useSetterForDefaultArgumentGeneration)
        {
            if (useSetterForDefaultArgumentGeneration != this.UseSetterForDefaultArgumentGeneration)
            {
                var result = new BoundIndexerAccess(
                    this.Syntax,
                    this.ReceiverOpt,
                    this.Indexer,
                    this.Arguments,
                    this.ArgumentNamesOpt,
                    this.ArgumentRefKindsOpt,
                    this.Expanded,
                    this.ArgsToParamsOpt,
                    this.BinderOpt,
                    useSetterForDefaultArgumentGeneration,
                    this.Type,
                    this.HasErrors)
                {
                    WasCompilerGenerated = this.WasCompilerGenerated,
                    OriginalIndexersOpt  = this.OriginalIndexersOpt
                };

                return(result);
            }

            return(this);
        }
Beispiel #3
0
        public override BoundNode VisitIndexerAccess(BoundIndexerAccess node)
        {
            Debug.Assert(node.Indexer.IsIndexer || node.Indexer.IsIndexedProperty);
            Debug.Assert((object)node.Indexer.GetOwnOrInheritedGetMethod() != null);

            return(VisitIndexerAccess(node, isLeftOfAssignment: false));
        }
Beispiel #4
0
        private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftOfAssignment)
        {
            PropertySymbol indexer = node.Indexer;

            Debug.Assert(indexer.IsIndexer || indexer.IsIndexedProperty);

            // Rewrite the receiver.
            BoundExpression rewrittenReceiver = VisitExpression(node.ReceiverOpt);

            // Rewrite the arguments.
            // NOTE: We may need additional argument rewriting such as generating a params array, re-ordering arguments based on argsToParamsOpt map, inserting arguments for optional parameters, etc.
            // NOTE: This is done later by MakeArguments, for now we just lower each argument.
            ImmutableArray <BoundExpression> rewrittenArguments = VisitList(node.Arguments);

            return(MakeIndexerAccess(
                       node.Syntax,
                       rewrittenReceiver,
                       indexer,
                       rewrittenArguments,
                       node.ArgumentNamesOpt,
                       node.ArgumentRefKindsOpt,
                       node.Expanded,
                       node.ArgsToParamsOpt,
                       node.Type,
                       node,
                       isLeftOfAssignment));
        }
Beispiel #5
0
        private BoundExpression MakeIndexerAccess(
            SyntaxNode syntax,
            BoundExpression rewrittenReceiver,
            PropertySymbol indexer,
            ImmutableArray <BoundExpression> rewrittenArguments,
            ImmutableArray <string> argumentNamesOpt,
            ImmutableArray <RefKind> argumentRefKindsOpt,
            bool expanded,
            ImmutableArray <int> argsToParamsOpt,
            TypeSymbol type,
            BoundIndexerAccess oldNodeOpt,
            bool isLeftOfAssignment)
        {
            if (isLeftOfAssignment && indexer.RefKind == RefKind.None)
            {
                // This is an indexer set access. We return a BoundIndexerAccess node here.
                // This node will be rewritten with MakePropertyAssignment when rewriting the enclosing BoundAssignmentOperator.

                return(oldNodeOpt != null?
                       oldNodeOpt.Update(rewrittenReceiver, indexer, rewrittenArguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, null, isLeftOfAssignment, type) :
                           new BoundIndexerAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, null, isLeftOfAssignment, type));
            }
            else
            {
                var getMethod = indexer.GetOwnOrInheritedGetMethod();
                Debug.Assert((object)getMethod != null);

                // We have already lowered each argument, but we may need some additional rewriting for the arguments,
                // such as generating a params array, re-ordering arguments based on argsToParamsOpt map, inserting arguments for optional parameters, etc.
                ImmutableArray <LocalSymbol> temps;
                rewrittenArguments = MakeArguments(
                    syntax,
                    rewrittenArguments,
                    indexer,
                    getMethod,
                    expanded,
                    argsToParamsOpt,
                    ref argumentRefKindsOpt,
                    out temps,
                    enableCallerInfo: ThreeState.True);

                BoundExpression call = MakePropertyGetAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, getMethod);

                if (temps.IsDefaultOrEmpty)
                {
                    return(call);
                }
                else
                {
                    return(new BoundSequence(
                               syntax,
                               temps,
                               ImmutableArray <BoundExpression> .Empty,
                               call,
                               type));
                }
            }
        }
        /// <summary>
        /// Generates a lowered form of the assignment operator for the given left and right sub-expressions.
        /// Left and right sub-expressions must be in lowered form.
        /// </summary>
        private BoundExpression MakeStaticAssignmentOperator(CSharpSyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, TypeSymbol type, bool used)
        {
            switch (rewrittenLeft.Kind)
            {
            case BoundKind.DynamicIndexerAccess:
            case BoundKind.DynamicMemberAccess:
                throw ExceptionUtilities.UnexpectedValue(rewrittenLeft.Kind);

            case BoundKind.PropertyAccess:
            {
                BoundPropertyAccess propertyAccess    = (BoundPropertyAccess)rewrittenLeft;
                BoundExpression     rewrittenReceiver = propertyAccess.ReceiverOpt;
                PropertySymbol      property          = propertyAccess.PropertySymbol;
                Debug.Assert(!property.IsIndexer);
                return(MakePropertyAssignment(
                           syntax,
                           rewrittenReceiver,
                           property,
                           ImmutableArray <BoundExpression> .Empty,
                           default(ImmutableArray <RefKind>),
                           false,
                           default(ImmutableArray <int>),
                           rewrittenRight,
                           type,
                           used));
            }

            case BoundKind.IndexerAccess:
            {
                BoundIndexerAccess indexerAccess     = (BoundIndexerAccess)rewrittenLeft;
                BoundExpression    rewrittenReceiver = indexerAccess.ReceiverOpt;
                ImmutableArray <BoundExpression> rewrittenArguments = indexerAccess.Arguments;
                PropertySymbol indexer = indexerAccess.Indexer;
                Debug.Assert(indexer.IsIndexer || indexer.IsIndexedProperty);
                return(MakePropertyAssignment(
                           syntax,
                           rewrittenReceiver,
                           indexer,
                           rewrittenArguments,
                           indexerAccess.ArgumentRefKindsOpt,
                           indexerAccess.Expanded,
                           indexerAccess.ArgsToParamsOpt,
                           rewrittenRight,
                           type,
                           used));
            }

            default:
            {
                return(new BoundAssignmentOperator(
                           syntax,
                           rewrittenLeft,
                           rewrittenRight,
                           type));
            }
            }
        }
Beispiel #7
0
        private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftOfAssignment)
        {
            PropertySymbol indexer = node.Indexer;

            Debug.Assert(indexer.IsIndexer || indexer.IsIndexedProperty);

            // Rewrite the receiver.
            BoundExpression?rewrittenReceiver = VisitExpression(node.ReceiverOpt);

            Debug.Assert(rewrittenReceiver is { });
Beispiel #8
0
        public override BoundNode VisitIndexerAccess(BoundIndexerAccess node)
        {
            var indexer = node.Indexer;
            var method  = indexer.GetOwnOrInheritedGetMethod() ?? indexer.GetOwnOrInheritedSetMethod();

            if ((object)method != null)
            {
                VisitCall(method, indexer, node.Arguments, node.ArgumentRefKindsOpt, node.ArgumentNamesOpt, node.Expanded, node);
            }
            CheckReceiverIfField(node.ReceiverOpt);
            return(base.VisitIndexerAccess(node));
        }
                public override BoundNode?VisitIndexerAccess(BoundIndexerAccess node)
                {
                    // Although property arguments with ref indexers are not declarable in C#, they may be usable
                    if (!node.ArgumentRefKindsOpt.IsDefault)
                    {
                        _mightAssignSomething = true;
                    }
                    else
                    {
                        base.VisitIndexerAccess(node);
                    }

                    return(null);
                }
        private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftOfAssignment)
        {
            PropertySymbol indexer = node.Indexer;
            Debug.Assert(indexer.IsIndexer || indexer.IsIndexedProperty);

            // Rewrite the receiver.
            BoundExpression rewrittenReceiver = VisitExpression(node.ReceiverOpt);

            // Rewrite the arguments.
            // NOTE: We may need additional argument rewriting such as generating a params array, re-ordering arguments based on argsToParamsOpt map, inserting arguments for optional parameters, etc.
            // NOTE: This is done later by MakeArguments, for now we just lower each argument.
            ImmutableArray<BoundExpression> rewrittenArguments = VisitList(node.Arguments);

            return MakeIndexerAccess(node.Syntax, rewrittenReceiver, indexer, rewrittenArguments, node.ArgumentNamesOpt,
                 node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.Type, node, isLeftOfAssignment);
        }
                public override BoundNode VisitIndexerAccess(BoundIndexerAccess node)
                {
                    bool mightMutate =
                        !node.ArgumentRefKindsOpt.IsDefault ||
                        // We only need to check the get accessor because an assignment would cause _mightAssignSomething to be set to true in the caller
                        MethodMayMutateReceiver(node.ReceiverOpt, node.Indexer.GetMethod);

                    if (mightMutate)
                    {
                        _mightAssignSomething = true;
                    }
                    else
                    {
                        base.VisitIndexerAccess(node);
                    }

                    return(null);
                }
Beispiel #12
0
        private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftOfAssignment)
        {
            PropertySymbol indexer = node.Indexer;
            Debug.Assert(indexer.IsIndexer || indexer.IsIndexedProperty);

            // Rewrite the receiver.
            BoundExpression rewrittenReceiver = VisitExpression(node.ReceiverOpt);

            // Rewrite the arguments.
            // NOTE: We may need additional argument rewriting such as generating a params array, re-ordering arguments based on argsToParamsOpt map, inserting arguments for optional parameters, etc.
            // NOTE: This is done later by MakeArguments, for now we just lower each argument.
            ImmutableArray<BoundExpression> rewrittenArguments = VisitList(node.Arguments);

            // https://github.com/dotnet/roslyn/issues/30620
            if (rewrittenReceiver?.Type.SpecialType == SpecialType.System_String &&
                rewrittenArguments.Length == 1 && rewrittenArguments[0].Type.SpecialType == SpecialType.None)
            {
                var F = _factory;
                var indexLocal = F.StoreToTemp(rewrittenArguments[0], out BoundAssignmentOperator indexAssign);
                var stringLocal = F.StoreToTemp(rewrittenReceiver, out BoundAssignmentOperator stringAssign);

                var indexValueSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Index__Value);
                var indexFromEndSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Index__FromEnd);

                var argType = rewrittenArguments[0].Type;
                if (argType == _compilation.GetWellKnownType(WellKnownType.System_Index))
                {
                    // string[Index] is rewritten as:
                    // index.FromEnd ? s[s.Length - index.Value] : s[index.Value];

                    var indexValueExpr = F.Property(indexLocal, indexValueSymbol);

                    return F.Sequence(
                        ImmutableArray.Create<LocalSymbol>(
                            indexLocal.LocalSymbol,
                            stringLocal.LocalSymbol),
                        ImmutableArray.Create<BoundExpression>(
                            indexAssign,
                            stringAssign),
                        F.Conditional(
                            F.Property(indexLocal, indexFromEndSymbol),
                            F.Indexer(stringLocal, node.Indexer,
                                F.Binary(
                                    BinaryOperatorKind.Subtraction,
                                    F.SpecialType(SpecialType.System_Int32),
                                    F.Call(stringLocal, F.SpecialMethod(SpecialMember.System_String__Length)),
                                    indexValueExpr)),
                            F.Indexer(stringLocal, node.Indexer, indexValueExpr),
                            F.SpecialType(SpecialType.System_Char)));
                }
                else if (argType == _compilation.GetWellKnownType(WellKnownType.System_Range))
                {
                    // string[Range] is translated to:
                    // var start = range.Start.FromEnd ? array.Length - range.Start.Value : range.Start.Value;
                    // var end = range.End.FromEnd ? array.Length - range.End.Value : range.End.Value;
                    // string.Substring(start, end - start)
                    var rangeStartSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Range__Start);
                    var rangeEndSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Range__End);
                    var arrayCopySymbol = F.WellKnownMethod(WellKnownMember.System_Array__Copy);

                    var startLocal = F.StoreToTemp(
                        F.Conditional(
                            F.Property(F.Property(indexLocal, rangeStartSymbol), indexFromEndSymbol),
                            F.Binary(
                                BinaryOperatorKind.Subtraction,
                                F.SpecialType(SpecialType.System_Int32),
                                F.Call(stringLocal, F.SpecialMethod(SpecialMember.System_String__Length)),
                                F.Property(F.Property(indexLocal, rangeStartSymbol), indexValueSymbol)),
                            F.Property(F.Property(indexLocal, rangeStartSymbol), indexValueSymbol),
                            F.SpecialType(SpecialType.System_Int32)),
                        out BoundAssignmentOperator startAssign);
                    var endLocal = F.StoreToTemp(
                        F.Conditional(
                            F.Property(F.Property(indexLocal, rangeEndSymbol), indexFromEndSymbol),
                            F.Binary(
                                BinaryOperatorKind.Subtraction,
                                F.SpecialType(SpecialType.System_Int32),
                                F.Call(stringLocal, F.SpecialMethod(SpecialMember.System_String__Length)),
                                F.Property(F.Property(indexLocal, rangeEndSymbol), indexValueSymbol)),
                            F.Property(F.Property(indexLocal, rangeEndSymbol), indexValueSymbol),
                            F.SpecialType(SpecialType.System_Int32)),
                        out BoundAssignmentOperator endAssign);
                    var substringExpr = F.Call(
                        stringLocal,
                        F.WellKnownMethod(WellKnownMember.System_String__Substring),
                        startLocal,
                        F.Binary(BinaryOperatorKind.Subtraction, F.SpecialType(SpecialType.System_Int32), endLocal, startLocal));
                    return F.Sequence(
                        ImmutableArray.Create(
                            indexLocal.LocalSymbol,
                            stringLocal.LocalSymbol,
                            startLocal.LocalSymbol,
                            endLocal.LocalSymbol),
                        ImmutableArray.Create<BoundExpression>(
                            indexAssign,
                            stringAssign,
                            startAssign,
                            endAssign),
                        substringExpr);

                }
                else
                {
                    throw ExceptionUtilities.Unreachable;
                }
            }


            return MakeIndexerAccess(
                node.Syntax,
                rewrittenReceiver,
                indexer,
                rewrittenArguments,
                node.ArgumentNamesOpt,
                node.ArgumentRefKindsOpt,
                node.Expanded,
                node.ArgsToParamsOpt,
                node.Type,
                node,
                isLeftOfAssignment);
        }
        private BoundIndexerAccess TransformIndexerAccess(BoundIndexerAccess indexerAccess, ArrayBuilder <BoundExpression> stores, ArrayBuilder <LocalSymbol> temps)
        {
            var receiverOpt = indexerAccess.ReceiverOpt;

            Debug.Assert(receiverOpt != null);

            BoundExpression transformedReceiver;

            if (CanChangeValueBetweenReads(receiverOpt))
            {
                BoundExpression rewrittenReceiver = VisitExpression(receiverOpt);

                BoundAssignmentOperator assignmentToTemp;

                // SPEC VIOLATION: It is not very clear when receiver of constrained callvirt is dereferenced - when pushed (in lexical order),
                // SPEC VIOLATION: or when actual call is executed. The actual behavior seems to be implementation specific in different JITs.
                // SPEC VIOLATION: To not depend on that, the right thing to do here is to store the value of the variable
                // SPEC VIOLATION: when variable has reference type (regular temp), and store variable's location when it has a value type. (ref temp)
                // SPEC VIOLATION: in a case of unconstrained generic type parameter a runtime test (default(T) == null) would be needed
                // SPEC VIOLATION: However, for compatibility with Dev12 we will continue treating all generic type parameters, constrained or not,
                // SPEC VIOLATION: as value types.
                var variableRepresentsLocation = rewrittenReceiver.Type.IsValueType || rewrittenReceiver.Type.Kind == SymbolKind.TypeParameter;

                var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind: variableRepresentsLocation ? RefKind.Ref : RefKind.None);
                transformedReceiver = receiverTemp;
                stores.Add(assignmentToTemp);
                temps.Add(receiverTemp.LocalSymbol);
            }
            else
            {
                transformedReceiver = VisitExpression(receiverOpt);
            }

            // Dealing with the arguments is a bit tricky because they can be named out-of-order arguments;
            // we have to preserve both the source-code order of the side effects and the side effects
            // only being executed once.
            //
            // This is a subtly different problem than the problem faced by the conventional call
            // rewriter; with the conventional call rewriter we already know that the side effects
            // will only be executed once because the arguments are only being pushed on the stack once.
            // In a compound equality operator on an indexer the indices are placed on the stack twice.
            // That is to say, if you have:
            //
            // C().M(z : Z(), x : X(), y : Y())
            //
            // then we can rewrite that into
            //
            // tempc = C()
            // tempz = Z()
            // tempc.M(X(), Y(), tempz)
            //
            // See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the
            // temporaries in
            //
            // C().Collection[z : Z(), x : X(), y : Y()] += 1;
            //
            // because we have to ensure not just that Z() happens first, but in addition that X() and Y() are only
            // called once.  We have to generate this as
            //
            // tempc = C().Collection
            // tempz = Z()
            // tempx = X()
            // tempy = Y()
            // tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1;
            //
            // Fortunately arguments to indexers are never ref or out, so we don't need to worry about that.
            // However, we can still do the optimization where constants are not stored in
            // temporaries; if we have
            //
            // C().Collection[z : 123, y : Y(), x : X()] += 1;
            //
            // Then we can generate that as
            //
            // tempc = C().Collection
            // tempx = X()
            // tempy = Y()
            // tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1;

            ImmutableArray <BoundExpression> rewrittenArguments = VisitList(indexerAccess.Arguments);

            SyntaxNode               syntax           = indexerAccess.Syntax;
            PropertySymbol           indexer          = indexerAccess.Indexer;
            ImmutableArray <RefKind> argumentRefKinds = indexerAccess.ArgumentRefKindsOpt;
            bool expanded = indexerAccess.Expanded;
            ImmutableArray <int> argsToParamsOpt = indexerAccess.ArgsToParamsOpt;

            ImmutableArray <ParameterSymbol> parameters = indexer.Parameters;

            BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter.
            ArrayBuilder <BoundAssignmentOperator> storesToTemps = ArrayBuilder <BoundAssignmentOperator> .GetInstance(rewrittenArguments.Length);

            ArrayBuilder <RefKind> refKinds = ArrayBuilder <RefKind> .GetInstance(parameters.Length, RefKind.None);

            // Step one: Store everything that is non-trivial into a temporary; record the
            // stores in storesToTemps and make the actual argument a reference to the temp.
            // Do not yet attempt to deal with params arrays or optional arguments.
            BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKinds, rewrittenArguments, actualArguments, refKinds, storesToTemps);

            // Step two: If we have a params array, build the array and fill in the argument.
            if (expanded)
            {
                BoundExpression         array = BuildParamsArray(syntax, indexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]);
                BoundAssignmentOperator storeToTemp;
                var boundTemp = _factory.StoreToTemp(array, out storeToTemp);
                stores.Add(storeToTemp);
                temps.Add(boundTemp.LocalSymbol);
                actualArguments[actualArguments.Length - 1] = boundTemp;
            }

            // Step three: Now fill in the optional arguments. (Dev11 uses the getter for optional arguments in
            // compound assignments, but for deconstructions we use the setter if the getter is missing.)
            var accessor = indexer.GetOwnOrInheritedGetMethod() ?? indexer.GetOwnOrInheritedSetMethod();

            InsertMissingOptionalArguments(syntax, accessor.Parameters, actualArguments);

            // For a call, step four would be to optimize away some of the temps.  However, we need them all to prevent
            // duplicate side-effects, so we'll skip that step.

            if (indexer.ContainingType.IsComImport)
            {
                RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temps);
            }

            rewrittenArguments = actualArguments.AsImmutableOrNull();

            foreach (BoundAssignmentOperator tempAssignment in storesToTemps)
            {
                temps.Add(((BoundLocal)tempAssignment.Left).LocalSymbol);
                stores.Add(tempAssignment);
            }

            storesToTemps.Free();
            argumentRefKinds = GetRefKindsOrNull(refKinds);
            refKinds.Free();

            // This is a temporary object that will be rewritten away before the lowering completes.
            return(new BoundIndexerAccess(
                       syntax,
                       transformedReceiver,
                       indexer,
                       rewrittenArguments,
                       default(ImmutableArray <string>),
                       argumentRefKinds,
                       false,
                       default(ImmutableArray <int>),
                       null,
                       indexerAccess.UseSetterForDefaultArgumentGeneration,
                       indexerAccess.Type));
        }
        private BoundIndexerAccess TransformIndexerAccess(BoundIndexerAccess indexerAccess, ArrayBuilder<BoundExpression> stores, ArrayBuilder<LocalSymbol> temps)
        {
            var receiverOpt = indexerAccess.ReceiverOpt;
            Debug.Assert(receiverOpt != null);

            BoundExpression transformedReceiver;
            if (CanChangeValueBetweenReads(receiverOpt))
            {
                BoundExpression rewrittenReceiver = VisitExpression(receiverOpt);

                BoundAssignmentOperator assignmentToTemp;

                // SPEC VIOLATION: It is not very clear when receiver of constrained callvirt is dereferenced - when pushed (in lexical order),
                // SPEC VIOLATION: or when actual call is executed. The actual behavior seems to be implementation specific in different JITs.
                // SPEC VIOLATION: To not depend on that, the right thing to do here is to store the value of the variable 
                // SPEC VIOLATION: when variable has reference type (regular temp), and store variable's location when it has a value type. (ref temp)
                // SPEC VIOLATION: in a case of unconstrained generic type parameter a runtime test (default(T) == null) would be needed
                // SPEC VIOLATION: However, for compatibility with Dev12 we will continue treating all generic type parameters, constrained or not,
                // SPEC VIOLATION: as value types.
                var variableRepresentsLocation = rewrittenReceiver.Type.IsValueType || rewrittenReceiver.Type.Kind == SymbolKind.TypeParameter;

                var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind: variableRepresentsLocation ? RefKind.Ref : RefKind.None);
                transformedReceiver = receiverTemp;
                stores.Add(assignmentToTemp);
                temps.Add(receiverTemp.LocalSymbol);
            }
            else
            {
                transformedReceiver = VisitExpression(receiverOpt);
            }

            // Dealing with the arguments is a bit tricky because they can be named out-of-order arguments;
            // we have to preserve both the source-code order of the side effects and the side effects
            // only being executed once.
            // 
            // This is a subtly different problem than the problem faced by the conventional call
            // rewriter; with the conventional call rewriter we already know that the side effects
            // will only be executed once because the arguments are only being pushed on the stack once. 
            // In a compound equality operator on an indexer the indices are placed on the stack twice. 
            // That is to say, if you have:
            // 
            // C().M(z : Z(), x : X(), y : Y())
            // 
            // then we can rewrite that into
            // 
            // tempc = C()
            // tempz = Z()
            // tempc.M(X(), Y(), tempz)
            // 
            // See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the
            // temporaries in
            // 
            // C().Collection[z : Z(), x : X(), y : Y()] += 1;
            // 
            // because we have to ensure not just that Z() happens first, but in addition that X() and Y() are only 
            // called once.  We have to generate this as
            // 
            // tempc = C().Collection
            // tempz = Z()
            // tempx = X()
            // tempy = Y()
            // tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1;
            // 
            // Fortunately arguments to indexers are never ref or out, so we don't need to worry about that.
            // However, we can still do the optimization where constants are not stored in
            // temporaries; if we have
            // 
            // C().Collection[z : 123, y : Y(), x : X()] += 1;
            // 
            // Then we can generate that as
            // 
            // tempc = C().Collection
            // tempx = X()
            // tempy = Y()
            // tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1;

            ImmutableArray<BoundExpression> rewrittenArguments = VisitList(indexerAccess.Arguments);

            SyntaxNode syntax = indexerAccess.Syntax;
            PropertySymbol indexer = indexerAccess.Indexer;
            ImmutableArray<RefKind> argumentRefKinds = indexerAccess.ArgumentRefKindsOpt;
            bool expanded = indexerAccess.Expanded;
            ImmutableArray<int> argsToParamsOpt = indexerAccess.ArgsToParamsOpt;

            ImmutableArray<ParameterSymbol> parameters = indexer.Parameters;
            BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter.
            ArrayBuilder<BoundAssignmentOperator> storesToTemps = ArrayBuilder<BoundAssignmentOperator>.GetInstance(rewrittenArguments.Length);
            ArrayBuilder<RefKind> refKinds = ArrayBuilder<RefKind>.GetInstance(parameters.Length, RefKind.None);

            // Step one: Store everything that is non-trivial into a temporary; record the
            // stores in storesToTemps and make the actual argument a reference to the temp.
            // Do not yet attempt to deal with params arrays or optional arguments.
            BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKinds, rewrittenArguments, actualArguments, refKinds, storesToTemps);

            // Step two: If we have a params array, build the array and fill in the argument.
            if (expanded)
            {
                BoundExpression array = BuildParamsArray(syntax, indexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]);
                BoundAssignmentOperator storeToTemp;
                var boundTemp = _factory.StoreToTemp(array, out storeToTemp);
                stores.Add(storeToTemp);
                temps.Add(boundTemp.LocalSymbol);
                actualArguments[actualArguments.Length - 1] = boundTemp;
            }

            // Step three: Now fill in the optional arguments. (Dev11 uses the
            // getter for optional arguments in compound assignments.)
            var getMethod = indexer.GetOwnOrInheritedGetMethod();
            Debug.Assert((object)getMethod != null);
            InsertMissingOptionalArguments(syntax, getMethod.Parameters, actualArguments);

            // For a call, step four would be to optimize away some of the temps.  However, we need them all to prevent
            // duplicate side-effects, so we'll skip that step.

            if (indexer.ContainingType.IsComImport)
            {
                RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temps);
            }

            rewrittenArguments = actualArguments.AsImmutableOrNull();

            foreach (BoundAssignmentOperator tempAssignment in storesToTemps)
            {
                temps.Add(((BoundLocal)tempAssignment.Left).LocalSymbol);
                stores.Add(tempAssignment);
            }

            storesToTemps.Free();
            argumentRefKinds = GetRefKindsOrNull(refKinds);
            refKinds.Free();

            // CONSIDER: this is a temporary object that will be rewritten away before this lowering completes.
            // Mitigation: this will only produce short-lived garbage for compound assignments and increments/decrements of indexers.
            return new BoundIndexerAccess(
                syntax,
                transformedReceiver,
                indexer,
                rewrittenArguments,
                default(ImmutableArray<string>),
                argumentRefKinds,
                false,
                default(ImmutableArray<int>),
                indexerAccess.Type);
        }
        private BoundExpression MakeIndexerAccess(
            CSharpSyntaxNode syntax,
            BoundExpression rewrittenReceiver,
            PropertySymbol indexer,
            ImmutableArray<BoundExpression> rewrittenArguments,
            ImmutableArray<string> argumentNamesOpt,
            ImmutableArray<RefKind> argumentRefKindsOpt,
            bool expanded,
            ImmutableArray<int> argsToParamsOpt,
            TypeSymbol type,
            BoundIndexerAccess oldNodeOpt,
            bool isLeftOfAssignment)
        {
            if (isLeftOfAssignment)
            {
                // This is an indexer set access. We return a BoundIndexerAccess node here.
                // This node will be rewritten with MakePropertyAssignment when rewriting the enclosing BoundAssignmentOperator.

                return oldNodeOpt != null ?
                    oldNodeOpt.Update(rewrittenReceiver, indexer, rewrittenArguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, type) :
                    new BoundIndexerAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, type);
            }
            else
            {
                var getMethod = indexer.GetOwnOrInheritedGetMethod();
                Debug.Assert((object)getMethod != null);

                // We have already lowered each argument, but we may need some additional rewriting for the arguments,
                // such as generating a params array, re-ordering arguments based on argsToParamsOpt map, inserting arguments for optional parameters, etc.
                ImmutableArray<LocalSymbol> temps;
                rewrittenArguments = MakeArguments(syntax, rewrittenArguments, indexer, getMethod, expanded, argsToParamsOpt, ref argumentRefKindsOpt, out temps, enableCallerInfo: ThreeState.True);

                BoundExpression call = MakePropertyGetAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, getMethod);

                if (temps.IsDefaultOrEmpty)
                {
                    return call;
                }
                else
                {
                    return new BoundSequence(
                        syntax,
                        temps,
                        ImmutableArray<BoundExpression>.Empty,
                        call,
                        type);
                }
            }
        }
Beispiel #16
0
        /// <summary>
        /// Generates a lowered form of the assignment operator for the given left and right sub-expressions.
        /// Left and right sub-expressions must be in lowered form.
        /// </summary>
        private BoundExpression MakeStaticAssignmentOperator(
            SyntaxNode syntax,
            BoundExpression rewrittenLeft,
            BoundExpression rewrittenRight,
            bool isRef,
            TypeSymbol type,
            bool used)
        {
            switch (rewrittenLeft.Kind)
            {
            case BoundKind.DynamicIndexerAccess:
            case BoundKind.DynamicMemberAccess:
                throw ExceptionUtilities.UnexpectedValue(rewrittenLeft.Kind);

            case BoundKind.PropertyAccess:
            {
                Debug.Assert(!isRef);
                BoundPropertyAccess propertyAccess    = (BoundPropertyAccess)rewrittenLeft;
                BoundExpression?    rewrittenReceiver = propertyAccess.ReceiverOpt;
                PropertySymbol      property          = propertyAccess.PropertySymbol;
                Debug.Assert(!property.IsIndexer);
                return(MakePropertyAssignment(
                           syntax,
                           rewrittenReceiver,
                           property,
                           ImmutableArray <BoundExpression> .Empty,
                           default(ImmutableArray <RefKind>),
                           false,
                           default(ImmutableArray <int>),
                           rewrittenRight,
                           type,
                           used));
            }

            case BoundKind.IndexerAccess:
            {
                Debug.Assert(!isRef);
                BoundIndexerAccess indexerAccess           = (BoundIndexerAccess)rewrittenLeft;
                BoundExpression?   rewrittenReceiver       = indexerAccess.ReceiverOpt;
                ImmutableArray <BoundExpression> arguments = indexerAccess.Arguments;
                PropertySymbol indexer = indexerAccess.Indexer;
                Debug.Assert(indexer.IsIndexer || indexer.IsIndexedProperty);
                return(MakePropertyAssignment(
                           syntax,
                           rewrittenReceiver,
                           indexer,
                           arguments,
                           indexerAccess.ArgumentRefKindsOpt,
                           indexerAccess.Expanded,
                           indexerAccess.ArgsToParamsOpt,
                           rewrittenRight,
                           type,
                           used));
            }

            case BoundKind.Local:
            case BoundKind.Parameter:
            case BoundKind.FieldAccess:
            {
                Debug.Assert(!isRef || rewrittenLeft.GetRefKind() != RefKind.None);
                return(new BoundAssignmentOperator(
                           syntax,
                           rewrittenLeft,
                           rewrittenRight,
                           isRef,
                           type));
            }

            case BoundKind.DiscardExpression:
            {
                return(rewrittenRight);
            }

            case BoundKind.Sequence:
                // An Index or Range pattern-based indexer, or an interpolated string handler conversion
                // that uses an indexer argument, produces a sequence with a nested
                // BoundIndexerAccess. We need to lower the final expression and produce an
                // update sequence
                var sequence = (BoundSequence)rewrittenLeft;
                if (sequence.Value.Kind == BoundKind.IndexerAccess)
                {
                    return(sequence.Update(
                               sequence.Locals,
                               sequence.SideEffects,
                               MakeStaticAssignmentOperator(
                                   syntax,
                                   sequence.Value,
                                   rewrittenRight,
                                   isRef,
                                   type,
                                   used),
                               type));
                }
                goto default;

            default:
            {
                Debug.Assert(!isRef);
                return(new BoundAssignmentOperator(
                           syntax,
                           rewrittenLeft,
                           rewrittenRight,
                           type));
            }
            }
        }
 public override BoundNode VisitIndexerAccess(BoundIndexerAccess node)
 {
     var indexer = node.Indexer;
     var method = indexer.GetOwnOrInheritedGetMethod() ?? indexer.GetOwnOrInheritedSetMethod();
     if ((object)method != null)
     {
         VisitCall(method, indexer, node.Arguments, node.ArgumentRefKindsOpt, node.ArgumentNamesOpt, node.Expanded, node);
     }
     CheckReceiverIfField(node.ReceiverOpt);
     return base.VisitIndexerAccess(node);
 }
        /// <summary>
        /// In the expanded form of a compound assignment (or increment/decrement), the LHS appears multiple times.
        /// If we aren't careful, this can result in repeated side-effects.  This creates (ordered) temps for all of the
        /// subexpressions that could result in side-effects and returns a side-effect-free expression that can be used
        /// in place of the LHS in the expanded form.
        /// </summary>
        /// <param name="originalLHS">The LHS sub-expression of the compound assignment (or increment/decrement).</param>
        /// <param name="stores">Populated with a list of assignment expressions that initialize the temporary locals.</param>
        /// <param name="temps">Populated with a list of temporary local symbols.</param>
        /// <param name="isDynamicAssignment">True if the compound assignment is a dynamic operation.</param>
        /// <returns>
        /// A side-effect-free expression representing the LHS.
        /// The returned node needs to be lowered but its children are already lowered.
        /// </returns>
        private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalLHS, ArrayBuilder <BoundExpression> stores, ArrayBuilder <LocalSymbol> temps, bool isDynamicAssignment)
        {
            // There are five possible cases.
            //
            // Case 1: receiver.Prop += value is transformed into
            // temp = receiver
            // temp.Prop = temp.Prop + value
            // and a later rewriting will turn that into calls to getters and setters.
            //
            // Case 2: collection[i1, i2, i3] += value is transformed into
            // tc = collection
            // t1 = i1
            // t2 = i2
            // t3 = i3
            // tc[t1, t2, t3] = tc[t1, t2, t3] + value
            // and again, a later rewriting will turn that into getters and setters of the indexer.
            //
            // Case 3: local += value (and param += value) needs no temporaries; it simply
            // becomes local = local + value.
            //
            // Case 4: staticField += value needs no temporaries either. However, classInst.field += value becomes
            // temp = classInst
            // temp.field = temp.field + value
            //
            // Case 5: otherwise, it must be structVariable.field += value or array[index] += value. Either way
            // we have a variable on the left. Transform it into:
            // ref temp = ref variable
            // temp = temp + value

            switch (originalLHS.Kind)
            {
            case BoundKind.PropertyAccess:
            {
                // We need to stash away the receiver so that it does not get evaluated twice.
                // If the receiver is classified as a value of reference type then we can simply say
                //
                // R temp = receiver
                // temp.prop = temp.prop + rhs
                //
                // But if the receiver is classified as a variable of struct type then we
                // cannot make a copy of the value; we need to make sure that we mutate
                // the original receiver, not the copy.  We have to generate
                //
                // ref R temp = ref receiver
                // temp.prop = temp.prop + rhs
                //
                // The rules of C# (in section 7.17.1) require that if you have receiver.prop
                // as the target of an assignment such that receiver is a value type, it must
                // be classified as a variable. If we've gotten this far in the rewriting,
                // assume that was the case.

                var prop = (BoundPropertyAccess)originalLHS;

                // If the property is static or if the receiver is of kind "Base" or "this", then we can just generate prop = prop + value
                if (prop.ReceiverOpt == null || prop.PropertySymbol.IsStatic || !IntroducingReadCanBeObservable(prop.ReceiverOpt))
                {
                    return(prop);
                }

                Debug.Assert(prop.ReceiverOpt.Kind != BoundKind.TypeExpression);

                BoundExpression rewrittenReceiver = VisitExpression(prop.ReceiverOpt);

                BoundAssignmentOperator assignmentToTemp;

                // SPEC VIOLATION: It is not very clear when receiver of constrained callvirt is dereferenced - when pushed (in lexical order),
                // SPEC VIOLATION: or when actual call is executed. The actual behavior seems to be implementation specific in different JITs.
                // SPEC VIOLATION: To not depend on that, the right thing to do here is to store the value of the variable
                // SPEC VIOLATION: when variable has reference type (regular temp), and store variable's location when it has a value type. (ref temp)
                // SPEC VIOLATION: in a case of unconstrained generic type parameter a runtime test (default(T) == null) would be needed
                // SPEC VIOLATION: However, for compatibility with Dev12 we will continue treating all generic type parameters, constrained or not,
                // SPEC VIOLATION: as value types.
                var variableRepresentsLocation = rewrittenReceiver.Type.IsValueType || rewrittenReceiver.Type.Kind == SymbolKind.TypeParameter;

                var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind: variableRepresentsLocation ? RefKind.Ref : RefKind.None);
                stores.Add(assignmentToTemp);
                temps.Add(receiverTemp.LocalSymbol);

                // CONSIDER: this is a temporary object that will be rewritten away before this lowering completes.
                // Mitigation: this will only produce short-lived garbage for compound assignments and increments/decrements of properties.
                return(new BoundPropertyAccess(prop.Syntax, receiverTemp, prop.PropertySymbol, prop.ResultKind, prop.Type));
            }

            case BoundKind.DynamicMemberAccess:
            {
                var memberAccess = (BoundDynamicMemberAccess)originalLHS;
                if (!IntroducingReadCanBeObservable(memberAccess.Receiver))
                {
                    return(memberAccess);
                }

                // store receiver to temp:
                var rewrittenReceiver = VisitExpression(memberAccess.Receiver);
                BoundAssignmentOperator assignmentToTemp;
                var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp);
                stores.Add(assignmentToTemp);
                temps.Add(receiverTemp.LocalSymbol);

                return(new BoundDynamicMemberAccess(memberAccess.Syntax, receiverTemp, memberAccess.TypeArgumentsOpt, memberAccess.Name, memberAccess.Invoked, memberAccess.Indexed, memberAccess.Type));
            }

            case BoundKind.IndexerAccess:
            {
                BoundIndexerAccess indexerAccess = (BoundIndexerAccess)originalLHS;

                var receiverOpt = indexerAccess.ReceiverOpt;
                Debug.Assert(receiverOpt != null);

                BoundExpression transformedReceiver;
                if (IntroducingReadCanBeObservable(receiverOpt))
                {
                    BoundExpression rewrittenReceiver = VisitExpression(receiverOpt);

                    BoundAssignmentOperator assignmentToTemp;

                    // SPEC VIOLATION: It is not very clear when receiver of constrained callvirt is dereferenced - when pushed (in lexical order),
                    // SPEC VIOLATION: or when actual call is executed. The actual behavior seems to be implementation specific in different JITs.
                    // SPEC VIOLATION: To not depend on that, the right thing to do here is to store the value of the variable
                    // SPEC VIOLATION: when variable has reference type (regular temp), and store variable's location when it has a value type. (ref temp)
                    // SPEC VIOLATION: in a case of unconstrained generic type parameter a runtime test (default(T) == null) would be needed
                    // SPEC VIOLATION: However, for compatibility with Dev12 we will continue treating all generic type parameters, constrained or not,
                    // SPEC VIOLATION: as value types.
                    var variableRepresentsLocation = rewrittenReceiver.Type.IsValueType || rewrittenReceiver.Type.Kind == SymbolKind.TypeParameter;

                    var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind: variableRepresentsLocation ? RefKind.Ref : RefKind.None);
                    transformedReceiver = receiverTemp;
                    stores.Add(assignmentToTemp);
                    temps.Add(receiverTemp.LocalSymbol);
                }
                else
                {
                    transformedReceiver = VisitExpression(receiverOpt);
                }

                // Dealing with the arguments is a bit tricky because they can be named out-of-order arguments;
                // we have to preserve both the source-code order of the side effects and the side effects
                // only being executed once.
                //
                // This is a subtly different problem than the problem faced by the conventional call
                // rewriter; with the conventional call rewriter we already know that the side effects
                // will only be executed once because the arguments are only being pushed on the stack once.
                // In a compound equality operator on an indexer the indices are placed on the stack twice.
                // That is to say, if you have:
                //
                // C().M(z : Z(), x : X(), y : Y())
                //
                // then we can rewrite that into
                //
                // tempc = C()
                // tempz = Z()
                // tempc.M(X(), Y(), tempz)
                //
                // See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the
                // temporaries in
                //
                // C().Collection[z : Z(), x : X(), y : Y()] += 1;
                //
                // because we have to ensure not just that Z() happens first, but in addition that X() and Y() are only
                // called once.  We have to generate this as
                //
                // tempc = C().Collection
                // tempz = Z()
                // tempx = X()
                // tempy = Y()
                // tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1;
                //
                // Fortunately arguments to indexers are never ref or out, so we don't need to worry about that.
                // However, we can still do the optimization where constants are not stored in
                // temporaries; if we have
                //
                // C().Collection[z : 123, y : Y(), x : X()] += 1;
                //
                // Then we can generate that as
                //
                // tempc = C().Collection
                // tempx = X()
                // tempy = Y()
                // tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1;

                ImmutableArray <BoundExpression> rewrittenArguments = VisitList(indexerAccess.Arguments);

                CSharpSyntaxNode         syntax           = indexerAccess.Syntax;
                PropertySymbol           indexer          = indexerAccess.Indexer;
                ImmutableArray <RefKind> argumentRefKinds = indexerAccess.ArgumentRefKindsOpt;
                bool expanded = indexerAccess.Expanded;
                ImmutableArray <int> argsToParamsOpt = indexerAccess.ArgsToParamsOpt;

                ImmutableArray <ParameterSymbol> parameters          = indexer.Parameters;
                BoundExpression[] actualArguments                    = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter.
                ArrayBuilder <BoundAssignmentOperator> storesToTemps = ArrayBuilder <BoundAssignmentOperator> .GetInstance(rewrittenArguments.Length);

                ArrayBuilder <RefKind> refKinds = ArrayBuilder <RefKind> .GetInstance(parameters.Length, RefKind.None);

                // Step one: Store everything that is non-trivial into a temporary; record the
                // stores in storesToTemps and make the actual argument a reference to the temp.
                // Do not yet attempt to deal with params arrays or optional arguments.
                BuildStoresToTemps(expanded, argsToParamsOpt, argumentRefKinds, rewrittenArguments, actualArguments, refKinds, storesToTemps);

                // Step two: If we have a params array, build the array and fill in the argument.
                if (expanded)
                {
                    BoundExpression         array = BuildParamsArray(syntax, indexer, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]);
                    BoundAssignmentOperator storeToTemp;
                    var boundTemp = _factory.StoreToTemp(array, out storeToTemp);
                    stores.Add(storeToTemp);
                    temps.Add(boundTemp.LocalSymbol);
                    actualArguments[actualArguments.Length - 1] = boundTemp;
                }

                // Step three: Now fill in the optional arguments. (Dev11 uses the
                // getter for optional arguments in compound assignments.)
                var getMethod = indexer.GetOwnOrInheritedGetMethod();
                Debug.Assert((object)getMethod != null);
                InsertMissingOptionalArguments(syntax, getMethod.Parameters, actualArguments);

                // For a call, step four would be to optimize away some of the temps.  However, we need them all to prevent
                // duplicate side-effects, so we'll skip that step.

                if (indexer.ContainingType.IsComImport)
                {
                    RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temps);
                }

                rewrittenArguments = actualArguments.AsImmutableOrNull();

                foreach (BoundAssignmentOperator tempAssignment in storesToTemps)
                {
                    temps.Add(((BoundLocal)tempAssignment.Left).LocalSymbol);
                    stores.Add(tempAssignment);
                }

                storesToTemps.Free();
                argumentRefKinds = GetRefKindsOrNull(refKinds);
                refKinds.Free();

                // CONSIDER: this is a temporary object that will be rewritten away before this lowering completes.
                // Mitigation: this will only produce short-lived garbage for compound assignments and increments/decrements of indexers.
                return(new BoundIndexerAccess(
                           syntax,
                           transformedReceiver,
                           indexer,
                           rewrittenArguments,
                           default(ImmutableArray <string>),
                           argumentRefKinds,
                           false,
                           default(ImmutableArray <int>),
                           indexerAccess.Type));
            }

            case BoundKind.Local:
            case BoundKind.Parameter:
            case BoundKind.ThisReference:     // a special kind of parameter
                // No temporaries are needed. Just generate local = local + value
                return(originalLHS);

            case BoundKind.FieldAccess:
            {
                // * If the field is static then no temporaries are needed.
                // * If the field is not static and the receiver is of reference type then generate t = r; t.f = t.f + value
                // * If the field is not static and the receiver is a variable of value type then we'll fall into the
                //   general variable case below.

                var             fieldAccess = (BoundFieldAccess)originalLHS;
                BoundExpression receiverOpt = fieldAccess.ReceiverOpt;

                //If the receiver is static or is the receiver is of kind "Base" or "this", then we can just generate field = field + value
                if (fieldAccess.FieldSymbol.IsStatic || !IntroducingReadCanBeObservable(receiverOpt))
                {
                    return(fieldAccess);
                }

                if (receiverOpt.Type.IsReferenceType)
                {
                    Debug.Assert(receiverOpt.Kind != BoundKind.TypeExpression);
                    BoundExpression rewrittenReceiver = VisitExpression(receiverOpt);

                    if (rewrittenReceiver.Type.IsTypeParameter())
                    {
                        var memberContainingType = fieldAccess.FieldSymbol.ContainingType;

                        // From the verifier prospective type parameters do not contain fields or methods.
                        // the instance must be "boxed" to access the field
                        // It makes sense to box receiver before storing into a temp - no need to box twice.
                        rewrittenReceiver = BoxReceiver(rewrittenReceiver, memberContainingType);
                    }

                    BoundAssignmentOperator assignmentToTemp;
                    var receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp);
                    stores.Add(assignmentToTemp);
                    temps.Add(receiverTemp.LocalSymbol);
                    return(new BoundFieldAccess(fieldAccess.Syntax, receiverTemp, fieldAccess.FieldSymbol, null));
                }

                break;
            }

            case BoundKind.DynamicIndexerAccess:
            {
                var indexerAccess = (BoundDynamicIndexerAccess)originalLHS;

                BoundExpression loweredReceiver;
                if (IntroducingReadCanBeObservable(indexerAccess.ReceiverOpt))
                {
                    BoundAssignmentOperator assignmentToTemp;
                    var temp = _factory.StoreToTemp(VisitExpression(indexerAccess.ReceiverOpt), out assignmentToTemp);
                    stores.Add(assignmentToTemp);
                    temps.Add(temp.LocalSymbol);
                    loweredReceiver = temp;
                }
                else
                {
                    loweredReceiver = indexerAccess.ReceiverOpt;
                }

                var arguments        = indexerAccess.Arguments;
                var loweredArguments = new BoundExpression[arguments.Length];

                for (int i = 0; i < arguments.Length; i++)
                {
                    if (IntroducingReadCanBeObservable(arguments[i]))
                    {
                        BoundAssignmentOperator assignmentToTemp;
                        var temp = _factory.StoreToTemp(VisitExpression(arguments[i]), out assignmentToTemp, refKind: indexerAccess.ArgumentRefKindsOpt.RefKinds(i));
                        stores.Add(assignmentToTemp);
                        temps.Add(temp.LocalSymbol);
                        loweredArguments[i] = temp;
                    }
                    else
                    {
                        loweredArguments[i] = arguments[i];
                    }
                }

                return(new BoundDynamicIndexerAccess(
                           indexerAccess.Syntax,
                           loweredReceiver,
                           loweredArguments.AsImmutableOrNull(),
                           indexerAccess.ArgumentNamesOpt,
                           indexerAccess.ArgumentRefKindsOpt,
                           indexerAccess.ApplicableIndexers,
                           indexerAccess.Type));
            }

            case BoundKind.ArrayAccess:
                if (isDynamicAssignment)
                {
                    // In non-dynamic array[index] op= R we emit:
                    //   T& tmp = &array[index];
                    //   *tmp = *L op R;
                    // where T is the type of L.
                    //
                    // If L is an array access, the assignment is dynamic, the compile-time of the array is dynamic[]
                    // and the runtime type of the array is not object[] (but e.g. string[]) the pointer approach is broken.
                    // T is Object in such case and we can't take a read-write pointer of type Object& to an array element of non-object type.
                    //
                    // In this case we rewrite the assignment as follows:
                    //
                    //   E t_array = array;
                    //   I t_index = index; (possibly more indices)
                    //   T value = t_array[t_index];
                    //   t_array[t_index] = value op R;

                    var arrayAccess    = (BoundArrayAccess)originalLHS;
                    var loweredArray   = VisitExpression(arrayAccess.Expression);
                    var loweredIndices = VisitList(arrayAccess.Indices);

                    return(SpillArrayElementAccess(loweredArray, loweredIndices, stores, temps));
                }

                break;

            case BoundKind.PointerElementAccess:
            case BoundKind.PointerIndirectionOperator:
            case BoundKind.RefValueOperator:
                break;

            default:
                throw ExceptionUtilities.UnexpectedValue(originalLHS.Kind);
            }

            // We made no transformation above. Either we have array[index] += value or
            // structVariable.field += value; either way we have a potentially complicated variable-
            // producing expression on the left. Generate
            // ref temp = ref variable; temp = temp + value

            // Rewrite the variable.  Here we depend on the fact that the only forms
            // rewritten here are rewritten the same for lvalues and rvalues.
            BoundExpression rewrittenVariable = VisitExpression(originalLHS);

            BoundAssignmentOperator assignmentToTemp2;
            var variableTemp = _factory.StoreToTemp(rewrittenVariable, out assignmentToTemp2, refKind: RefKind.Ref);

            stores.Add(assignmentToTemp2);
            temps.Add(variableTemp.LocalSymbol);
            return(variableTemp);
        }