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); }
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); }
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)); }
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)); }
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)); } } }
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 { });
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); }
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); } } }
/// <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); }