2010-05-27 Jb Evain <jbevain@novell.com>
[mcs.git] / mcs / assign.cs
blobbefc203eda07436a95ca558573f261297f1a9c38
1 //
2 // assign.cs: Assignments.
3 //
4 // Author:
5 // Miguel de Icaza (miguel@ximian.com)
6 // Martin Baulig (martin@ximian.com)
7 // Marek Safar (marek.safar@gmail.com)
8 //
9 // Dual licensed under the terms of the MIT X11 or GNU GPL
11 // Copyright 2001, 2002, 2003 Ximian, Inc.
12 // Copyright 2004-2008 Novell, Inc
14 using System;
15 using System.Reflection;
16 using System.Reflection.Emit;
18 namespace Mono.CSharp {
20 /// <summary>
21 /// This interface is implemented by expressions that can be assigned to.
22 /// </summary>
23 /// <remarks>
24 /// This interface is implemented by Expressions whose values can not
25 /// store the result on the top of the stack.
26 ///
27 /// Expressions implementing this (Properties, Indexers and Arrays) would
28 /// perform an assignment of the Expression "source" into its final
29 /// location.
30 ///
31 /// No values on the top of the stack are expected to be left by
32 /// invoking this method.
33 /// </remarks>
34 public interface IAssignMethod {
36 // This is an extra version of Emit. If leave_copy is `true'
37 // A copy of the expression will be left on the stack at the
38 // end of the code generated for EmitAssign
40 void Emit (EmitContext ec, bool leave_copy);
43 // This method does the assignment
44 // `source' will be stored into the location specified by `this'
45 // if `leave_copy' is true, a copy of `source' will be left on the stack
46 // if `prepare_for_load' is true, when `source' is emitted, there will
47 // be data on the stack that it can use to compuatate its value. This is
48 // for expressions like a [f ()] ++, where you can't call `f ()' twice.
50 void EmitAssign (EmitContext ec, Expression source, bool leave_copy, bool prepare_for_load);
53 For simple assignments, this interface is very simple, EmitAssign is called with source
54 as the source expression and leave_copy and prepare_for_load false.
56 For compound assignments it gets complicated.
58 EmitAssign will be called as before, however, prepare_for_load will be
59 true. The @source expression will contain an expression
60 which calls Emit. So, the calls look like:
62 this.EmitAssign (ec, source, false, true) ->
63 source.Emit (ec); ->
64 [...] ->
65 this.Emit (ec, false); ->
66 end this.Emit (ec, false); ->
67 end [...]
68 end source.Emit (ec);
69 end this.EmitAssign (ec, source, false, true)
72 When prepare_for_load is true, EmitAssign emits a `token' on the stack that
73 Emit will use for its state.
75 Let's take FieldExpr as an example. assume we are emitting f ().y += 1;
77 Here is the call tree again. This time, each call is annotated with the IL
78 it produces:
80 this.EmitAssign (ec, source, false, true)
81 call f
82 dup
84 Binary.Emit ()
85 this.Emit (ec, false);
86 ldfld y
87 end this.Emit (ec, false);
89 IntConstant.Emit ()
90 ldc.i4.1
91 end IntConstant.Emit
93 add
94 end Binary.Emit ()
96 stfld
97 end this.EmitAssign (ec, source, false, true)
99 Observe two things:
100 1) EmitAssign left a token on the stack. It was the result of f ().
101 2) This token was used by Emit
103 leave_copy (in both EmitAssign and Emit) tells the compiler to leave a copy
104 of the expression at that point in evaluation. This is used for pre/post inc/dec
105 and for a = x += y. Let's do the above example with leave_copy true in EmitAssign
107 this.EmitAssign (ec, source, true, true)
108 call f
111 Binary.Emit ()
112 this.Emit (ec, false);
113 ldfld y
114 end this.Emit (ec, false);
116 IntConstant.Emit ()
117 ldc.i4.1
118 end IntConstant.Emit
121 end Binary.Emit ()
124 stloc temp
125 stfld
126 ldloc temp
127 end this.EmitAssign (ec, source, true, true)
129 And with it true in Emit
131 this.EmitAssign (ec, source, false, true)
132 call f
135 Binary.Emit ()
136 this.Emit (ec, true);
137 ldfld y
139 stloc temp
140 end this.Emit (ec, true);
142 IntConstant.Emit ()
143 ldc.i4.1
144 end IntConstant.Emit
147 end Binary.Emit ()
149 stfld
150 ldloc temp
151 end this.EmitAssign (ec, source, false, true)
153 Note that these two examples are what happens for ++x and x++, respectively.
157 /// <summary>
158 /// An Expression to hold a temporary value.
159 /// </summary>
160 /// <remarks>
161 /// The LocalTemporary class is used to hold temporary values of a given
162 /// type to "simulate" the expression semantics on property and indexer
163 /// access whose return values are void.
165 /// The local temporary is used to alter the normal flow of code generation
166 /// basically it creates a local variable, and its emit instruction generates
167 /// code to access this value, return its address or save its value.
169 /// If `is_address' is true, then the value that we store is the address to the
170 /// real value, and not the value itself.
172 /// This is needed for a value type, because otherwise you just end up making a
173 /// copy of the value on the stack and modifying it. You really need a pointer
174 /// to the origional value so that you can modify it in that location. This
175 /// Does not happen with a class because a class is a pointer -- so you always
176 /// get the indirection.
178 /// </remarks>
179 public class LocalTemporary : Expression, IMemoryLocation, IAssignMethod {
180 LocalBuilder builder;
182 public LocalTemporary (TypeSpec t)
184 type = t;
185 eclass = ExprClass.Value;
188 public LocalTemporary (LocalBuilder b, TypeSpec t)
189 : this (t)
191 builder = b;
194 public void Release (EmitContext ec)
196 ec.FreeTemporaryLocal (builder, type);
197 builder = null;
200 public override Expression CreateExpressionTree (ResolveContext ec)
202 Arguments args = new Arguments (1);
203 args.Add (new Argument (this));
204 return CreateExpressionFactoryCall (ec, "Constant", args);
207 protected override Expression DoResolve (ResolveContext ec)
209 return this;
212 public override Expression DoResolveLValue (ResolveContext ec, Expression right_side)
214 return this;
217 public override void Emit (EmitContext ec)
219 if (builder == null)
220 throw new InternalErrorException ("Emit without Store, or after Release");
222 ec.Emit (OpCodes.Ldloc, builder);
225 #region IAssignMethod Members
227 public void Emit (EmitContext ec, bool leave_copy)
229 Emit (ec);
231 if (leave_copy)
232 Emit (ec);
235 public void EmitAssign (EmitContext ec, Expression source, bool leave_copy, bool prepare_for_load)
237 if (prepare_for_load)
238 throw new NotImplementedException ();
240 source.Emit (ec);
242 Store (ec);
244 if (leave_copy)
245 Emit (ec);
248 #endregion
250 public LocalBuilder Builder {
251 get { return builder; }
254 public void Store (EmitContext ec)
256 if (builder == null)
257 builder = ec.GetTemporaryLocal (type);
259 ec.Emit (OpCodes.Stloc, builder);
262 public void AddressOf (EmitContext ec, AddressOp mode)
264 if (builder == null)
265 builder = ec.GetTemporaryLocal (type);
267 if (builder.LocalType.IsByRef) {
269 // if is_address, than this is just the address anyways,
270 // so we just return this.
272 ec.Emit (OpCodes.Ldloc, builder);
273 } else {
274 ec.Emit (OpCodes.Ldloca, builder);
279 /// <summary>
280 /// The Assign node takes care of assigning the value of source into
281 /// the expression represented by target.
282 /// </summary>
283 public abstract class Assign : ExpressionStatement {
284 protected Expression target, source;
286 protected Assign (Expression target, Expression source, Location loc)
288 this.target = target;
289 this.source = source;
290 this.loc = loc;
293 public override Expression CreateExpressionTree (ResolveContext ec)
295 ec.Report.Error (832, loc, "An expression tree cannot contain an assignment operator");
296 return null;
299 public Expression Target {
300 get { return target; }
303 public Expression Source {
304 get { return source; }
307 protected override Expression DoResolve (ResolveContext ec)
309 bool ok = true;
310 source = source.Resolve (ec);
312 if (source == null) {
313 ok = false;
314 source = EmptyExpression.Null;
317 target = target.ResolveLValue (ec, source);
319 if (target == null || !ok)
320 return null;
322 TypeSpec target_type = target.Type;
323 TypeSpec source_type = source.Type;
325 eclass = ExprClass.Value;
326 type = target_type;
328 if (!(target is IAssignMethod)) {
329 Error_ValueAssignment (ec, loc);
330 return null;
333 if ((RootContext.Version == LanguageVersion.ISO_1) && (source is MethodGroupExpr)){
334 ((MethodGroupExpr) source).ReportUsageError (ec);
335 return null;
338 if (!TypeManager.IsEqual (target_type, source_type)) {
339 Expression resolved = ResolveConversions (ec);
341 if (resolved != this)
342 return resolved;
345 return this;
348 #if NET_4_0
349 public override System.Linq.Expressions.Expression MakeExpression (BuilderContext ctx)
351 var tassign = target as IDynamicAssign;
352 if (tassign == null)
353 throw new InternalErrorException (target.GetType () + " does not support dynamic assignment");
355 var target_object = tassign.MakeAssignExpression (ctx);
358 // Some hacking is needed as DLR does not support void type and requires
359 // always have object convertible return type to support caching and chaining
361 // We do this by introducing an explicit block which returns RHS value when
362 // available or null
364 if (target_object.NodeType == System.Linq.Expressions.ExpressionType.Block)
365 return target_object;
367 var source_object = System.Linq.Expressions.Expression.Convert (source.MakeExpression (ctx), target_object.Type);
368 return System.Linq.Expressions.Expression.Assign (target_object, source_object);
370 #endif
371 protected virtual Expression ResolveConversions (ResolveContext ec)
373 source = Convert.ImplicitConversionRequired (ec, source, target.Type, loc);
374 if (source == null)
375 return null;
377 return this;
380 void Emit (EmitContext ec, bool is_statement)
382 IAssignMethod t = (IAssignMethod) target;
383 t.EmitAssign (ec, source, !is_statement, this is CompoundAssign);
386 public override void Emit (EmitContext ec)
388 Emit (ec, false);
391 public override void EmitStatement (EmitContext ec)
393 Emit (ec, true);
396 protected override void CloneTo (CloneContext clonectx, Expression t)
398 Assign _target = (Assign) t;
400 _target.target = target.Clone (clonectx);
401 _target.source = source.Clone (clonectx);
405 public class SimpleAssign : Assign {
406 public SimpleAssign (Expression target, Expression source)
407 : this (target, source, target.Location)
411 public SimpleAssign (Expression target, Expression source, Location loc)
412 : base (target, source, loc)
416 bool CheckEqualAssign (Expression t)
418 if (source is Assign) {
419 Assign a = (Assign) source;
420 if (t.Equals (a.Target))
421 return true;
422 return a is SimpleAssign && ((SimpleAssign) a).CheckEqualAssign (t);
424 return t.Equals (source);
427 protected override Expression DoResolve (ResolveContext ec)
429 Expression e = base.DoResolve (ec);
430 if (e == null || e != this)
431 return e;
433 if (CheckEqualAssign (target))
434 ec.Report.Warning (1717, 3, loc, "Assignment made to same variable; did you mean to assign something else?");
436 return this;
440 // This class implements fields and events class initializers
441 public class FieldInitializer : Assign
444 // Keep resolved value because field initializers have their own rules
446 ExpressionStatement resolved;
447 IMemberContext rc;
449 public FieldInitializer (FieldBase field, Expression expression, IMemberContext rc)
450 : base (new FieldExpr (field, expression.Location), expression, expression.Location)
452 this.rc = rc;
453 if (!field.IsStatic)
454 ((FieldExpr)target).InstanceExpression = CompilerGeneratedThis.Instance;
457 protected override Expression DoResolve (ResolveContext ec)
459 // Field initializer can be resolved (fail) many times
460 if (source == null)
461 return null;
463 if (resolved == null) {
465 // Field initializers are tricky for partial classes. They have to
466 // share same constructor (block) but they have they own resolve scope.
469 IMemberContext old = ec.MemberContext;
470 ec.MemberContext = rc;
472 using (ec.Set (ResolveContext.Options.FieldInitializerScope)) {
473 resolved = base.DoResolve (ec) as ExpressionStatement;
476 ec.MemberContext = old;
479 return resolved;
482 public override void EmitStatement (EmitContext ec)
484 if (resolved == null)
485 return;
487 if (resolved != this)
488 resolved.EmitStatement (ec);
489 else
490 base.EmitStatement (ec);
493 public bool IsComplexInitializer {
494 get { return !(source is Constant); }
497 public bool IsDefaultInitializer {
498 get {
499 Constant c = source as Constant;
500 if (c == null)
501 return false;
503 FieldExpr fe = (FieldExpr)target;
504 return c.IsDefaultInitializer (fe.Type);
509 class EventAddOrRemove : ExpressionStatement {
510 EventExpr target;
511 Binary.Operator op;
512 Expression source;
514 public EventAddOrRemove (Expression target, Binary.Operator op, Expression source, Location loc)
516 this.target = target as EventExpr;
517 this.op = op;
518 this.source = source;
519 this.loc = loc;
522 public override Expression CreateExpressionTree (ResolveContext ec)
524 return new SimpleAssign (target, source).CreateExpressionTree (ec);
527 protected override Expression DoResolve (ResolveContext ec)
529 if (op != Binary.Operator.Addition && op != Binary.Operator.Subtraction)
530 target.Error_AssignmentEventOnly (ec);
532 source = source.Resolve (ec);
533 if (source == null)
534 return null;
536 source = Convert.ImplicitConversionRequired (ec, source, target.Type, loc);
537 if (source == null)
538 return null;
540 eclass = ExprClass.Value;
541 type = TypeManager.void_type;
542 return this;
545 public override void Emit (EmitContext ec)
547 if (RootContext.EvalMode)
548 EmitStatement (ec);
549 else
550 throw new InternalErrorException ("don't know what to emit");
553 public override void EmitStatement (EmitContext ec)
555 target.EmitAddOrRemove (ec, op == Binary.Operator.Addition, source);
560 // This class is used for compound assignments.
562 public class CompoundAssign : Assign
564 // This is just a hack implemented for arrays only
565 public sealed class TargetExpression : Expression
567 Expression child;
568 public TargetExpression (Expression child)
570 this.child = child;
571 this.loc = child.Location;
574 public override Expression CreateExpressionTree (ResolveContext ec)
576 throw new NotSupportedException ("ET");
579 protected override Expression DoResolve (ResolveContext ec)
581 type = child.Type;
582 eclass = ExprClass.Value;
583 return this;
586 public override void Emit (EmitContext ec)
588 child.Emit (ec);
592 // Used for underlying binary operator
593 readonly Binary.Operator op;
594 Expression right;
595 Expression left;
597 public CompoundAssign (Binary.Operator op, Expression target, Expression source)
598 : base (target, source, target.Location)
600 right = source;
601 this.op = op;
604 public CompoundAssign (Binary.Operator op, Expression target, Expression source, Expression left)
605 : this (op, target, source)
607 this.left = left;
610 protected override Expression DoResolve (ResolveContext ec)
612 right = right.Resolve (ec);
613 if (right == null)
614 return null;
616 MemberAccess ma = target as MemberAccess;
617 using (ec.Set (ResolveContext.Options.CompoundAssignmentScope)) {
618 target = target.Resolve (ec);
621 if (target == null)
622 return null;
624 if (target is MethodGroupExpr){
625 ec.Report.Error (1656, loc,
626 "Cannot assign to `{0}' because it is a `{1}'",
627 ((MethodGroupExpr)target).Name, target.ExprClassName);
628 return null;
631 if (target is EventExpr)
632 return new EventAddOrRemove (target, op, right, loc).Resolve (ec);
635 // Only now we can decouple the original source/target
636 // into a tree, to guarantee that we do not have side
637 // effects.
639 if (left == null)
640 left = new TargetExpression (target);
642 source = new Binary (op, left, right, true, loc);
644 if (target is DynamicMemberBinder) {
645 Arguments targs = ((DynamicMemberBinder) target).Arguments;
646 source = source.Resolve (ec);
648 Arguments args = new Arguments (2);
649 args.AddRange (targs);
650 args.Add (new Argument (source));
651 source = new DynamicMemberBinder (ma.Name, args, loc).ResolveLValue (ec, right);
653 // Handles possible event addition/subtraction
654 if (op == Binary.Operator.Addition || op == Binary.Operator.Subtraction) {
655 args = new Arguments (2);
656 args.AddRange (targs);
657 args.Add (new Argument (right));
658 string method_prefix = op == Binary.Operator.Addition ?
659 Event.AEventAccessor.AddPrefix : Event.AEventAccessor.RemovePrefix;
661 var invoke = DynamicInvocation.CreateSpecialNameInvoke (
662 new MemberAccess (right, method_prefix + ma.Name, loc), args, loc).Resolve (ec);
664 args = new Arguments (1);
665 args.AddRange (targs);
666 source = new DynamicEventCompoundAssign (ma.Name, args,
667 (ExpressionStatement) source, (ExpressionStatement) invoke, loc).Resolve (ec);
670 return source;
673 return base.DoResolve (ec);
676 protected override Expression ResolveConversions (ResolveContext ec)
678 TypeSpec target_type = target.Type;
681 // 1. the return type is implicitly convertible to the type of target
683 if (Convert.ImplicitConversionExists (ec, source, target_type)) {
684 source = Convert.ImplicitConversion (ec, source, target_type, loc);
685 return this;
689 // Otherwise, if the selected operator is a predefined operator
691 Binary b = source as Binary;
692 if (b != null) {
694 // 2a. the operator is a shift operator
696 // 2b. the return type is explicitly convertible to the type of x, and
697 // y is implicitly convertible to the type of x
699 if ((b.Oper & Binary.Operator.ShiftMask) != 0 ||
700 Convert.ImplicitConversionExists (ec, right, target_type)) {
701 source = Convert.ExplicitConversion (ec, source, target_type, loc);
702 return this;
706 if (source.Type == InternalType.Dynamic) {
707 Arguments arg = new Arguments (1);
708 arg.Add (new Argument (source));
709 return new SimpleAssign (target, new DynamicConversion (target_type, CSharpBinderFlags.ConvertExplicit, arg, loc), loc).Resolve (ec);
712 right.Error_ValueCannotBeConverted (ec, loc, target_type, false);
713 return null;
716 protected override void CloneTo (CloneContext clonectx, Expression t)
718 CompoundAssign ctarget = (CompoundAssign) t;
720 ctarget.right = ctarget.source = source.Clone (clonectx);
721 ctarget.target = target.Clone (clonectx);