2007-03-22 Chris Toshok <toshok@ximian.com>
[mcs.git] / mcs / assign.cs
blob3311ff1a9d16dfdeefec6256cf3d5e79271b9d12
1 //
2 // assign.cs: Assignments.
3 //
4 // Author:
5 // Miguel de Icaza (miguel@ximian.com)
6 // Martin Baulig (martin@ximian.com)
7 //
8 // (C) 2001, 2002, 2003 Ximian, Inc.
9 // (C) 2004 Novell, Inc
11 using System;
12 using System.Reflection;
13 using System.Reflection.Emit;
15 namespace Mono.CSharp {
17 /// <summary>
18 /// This interface is implemented by expressions that can be assigned to.
19 /// </summary>
20 /// <remarks>
21 /// This interface is implemented by Expressions whose values can not
22 /// store the result on the top of the stack.
23 ///
24 /// Expressions implementing this (Properties, Indexers and Arrays) would
25 /// perform an assignment of the Expression "source" into its final
26 /// location.
27 ///
28 /// No values on the top of the stack are expected to be left by
29 /// invoking this method.
30 /// </remarks>
31 public interface IAssignMethod {
33 // This is an extra version of Emit. If leave_copy is `true'
34 // A copy of the expression will be left on the stack at the
35 // end of the code generated for EmitAssign
37 void Emit (EmitContext ec, bool leave_copy);
40 // This method does the assignment
41 // `source' will be stored into the location specified by `this'
42 // if `leave_copy' is true, a copy of `source' will be left on the stack
43 // if `prepare_for_load' is true, when `source' is emitted, there will
44 // be data on the stack that it can use to compuatate its value. This is
45 // for expressions like a [f ()] ++, where you can't call `f ()' twice.
47 void EmitAssign (EmitContext ec, Expression source, bool leave_copy, bool prepare_for_load);
50 For simple assignments, this interface is very simple, EmitAssign is called with source
51 as the source expression and leave_copy and prepare_for_load false.
53 For compound assignments it gets complicated.
55 EmitAssign will be called as before, however, prepare_for_load will be
56 true. The @source expression will contain an expression
57 which calls Emit. So, the calls look like:
59 this.EmitAssign (ec, source, false, true) ->
60 source.Emit (ec); ->
61 [...] ->
62 this.Emit (ec, false); ->
63 end this.Emit (ec, false); ->
64 end [...]
65 end source.Emit (ec);
66 end this.EmitAssign (ec, source, false, true)
69 When prepare_for_load is true, EmitAssign emits a `token' on the stack that
70 Emit will use for its state.
72 Let's take FieldExpr as an example. assume we are emitting f ().y += 1;
74 Here is the call tree again. This time, each call is annotated with the IL
75 it produces:
77 this.EmitAssign (ec, source, false, true)
78 call f
79 dup
81 Binary.Emit ()
82 this.Emit (ec, false);
83 ldfld y
84 end this.Emit (ec, false);
86 IntConstant.Emit ()
87 ldc.i4.1
88 end IntConstant.Emit
90 add
91 end Binary.Emit ()
93 stfld
94 end this.EmitAssign (ec, source, false, true)
96 Observe two things:
97 1) EmitAssign left a token on the stack. It was the result of f ().
98 2) This token was used by Emit
100 leave_copy (in both EmitAssign and Emit) tells the compiler to leave a copy
101 of the expression at that point in evaluation. This is used for pre/post inc/dec
102 and for a = x += y. Let's do the above example with leave_copy true in EmitAssign
104 this.EmitAssign (ec, source, true, true)
105 call f
108 Binary.Emit ()
109 this.Emit (ec, false);
110 ldfld y
111 end this.Emit (ec, false);
113 IntConstant.Emit ()
114 ldc.i4.1
115 end IntConstant.Emit
118 end Binary.Emit ()
121 stloc temp
122 stfld
123 ldloc temp
124 end this.EmitAssign (ec, source, true, true)
126 And with it true in Emit
128 this.EmitAssign (ec, source, false, true)
129 call f
132 Binary.Emit ()
133 this.Emit (ec, true);
134 ldfld y
136 stloc temp
137 end this.Emit (ec, true);
139 IntConstant.Emit ()
140 ldc.i4.1
141 end IntConstant.Emit
144 end Binary.Emit ()
146 stfld
147 ldloc temp
148 end this.EmitAssign (ec, source, false, true)
150 Note that these two examples are what happens for ++x and x++, respectively.
154 /// <summary>
155 /// An Expression to hold a temporary value.
156 /// </summary>
157 /// <remarks>
158 /// The LocalTemporary class is used to hold temporary values of a given
159 /// type to "simulate" the expression semantics on property and indexer
160 /// access whose return values are void.
162 /// The local temporary is used to alter the normal flow of code generation
163 /// basically it creates a local variable, and its emit instruction generates
164 /// code to access this value, return its address or save its value.
166 /// If `is_address' is true, then the value that we store is the address to the
167 /// real value, and not the value itself.
169 /// This is needed for a value type, because otherwise you just end up making a
170 /// copy of the value on the stack and modifying it. You really need a pointer
171 /// to the origional value so that you can modify it in that location. This
172 /// Does not happen with a class because a class is a pointer -- so you always
173 /// get the indirection.
175 /// The `is_address' stuff is really just a hack. We need to come up with a better
176 /// way to handle it.
177 /// </remarks>
178 public class LocalTemporary : Expression, IMemoryLocation {
179 LocalBuilder builder;
180 bool is_address;
182 public LocalTemporary (Type t) : this (t, false) {}
184 public LocalTemporary (Type t, bool is_address)
186 type = t;
187 eclass = ExprClass.Value;
188 this.is_address = is_address;
191 public LocalTemporary (LocalBuilder b, Type t)
193 type = t;
194 eclass = ExprClass.Value;
195 loc = Location.Null;
196 builder = b;
199 public void Release (EmitContext ec)
201 ec.FreeTemporaryLocal (builder, type);
202 builder = null;
205 public override Expression DoResolve (EmitContext ec)
207 return this;
210 public override void Emit (EmitContext ec)
212 ILGenerator ig = ec.ig;
214 if (builder == null)
215 throw new InternalErrorException ("Emit without Store, or after Release");
217 ig.Emit (OpCodes.Ldloc, builder);
218 // we need to copy from the pointer
219 if (is_address)
220 LoadFromPtr (ig, type);
223 // NB: if you have `is_address' on the stack there must
224 // be a managed pointer. Otherwise, it is the type from
225 // the ctor.
226 public void Store (EmitContext ec)
228 ILGenerator ig = ec.ig;
229 if (builder == null)
230 builder = ec.GetTemporaryLocal (is_address ? TypeManager.GetReferenceType (type): type);
232 ig.Emit (OpCodes.Stloc, builder);
235 public void AddressOf (EmitContext ec, AddressOp mode)
237 if (builder == null)
238 builder = ec.GetTemporaryLocal (is_address ? TypeManager.GetReferenceType (type): type);
240 // if is_address, than this is just the address anyways,
241 // so we just return this.
242 ILGenerator ig = ec.ig;
244 if (is_address)
245 ig.Emit (OpCodes.Ldloc, builder);
246 else
247 ig.Emit (OpCodes.Ldloca, builder);
250 public bool PointsToAddress {
251 get {
252 return is_address;
257 /// <summary>
258 /// The Assign node takes care of assigning the value of source into
259 /// the expression represented by target.
260 /// </summary>
261 public class Assign : ExpressionStatement {
262 protected Expression target, source, real_source;
263 protected LocalTemporary temp = null, real_temp = null;
264 protected Assign embedded = null;
265 protected bool is_embedded = false;
266 protected bool must_free_temp = false;
268 public Assign (Expression target, Expression source)
269 : this (target, source, target.Location)
273 public Assign (Expression target, Expression source, Location l)
275 this.target = target;
276 this.source = this.real_source = source;
277 this.loc = l;
280 protected Assign (Assign embedded, Location l)
281 : this (embedded.target, embedded.source, l)
283 this.is_embedded = true;
286 protected virtual Assign GetEmbeddedAssign (Location loc)
288 return new Assign (this, loc);
291 public Expression Target {
292 get {
293 return target;
296 set {
297 target = value;
301 public Expression Source {
302 get {
303 return source;
306 set {
307 source = value;
311 public static void error70 (EventInfo ei, Location l)
313 Report.Error (70, l, "The event `" + TypeManager.CSharpSignature (ei) +
314 "' can only appear on the left hand side of += or -= (except when" +
315 " used from within the type `" + ei.DeclaringType + "')");
319 // Will return either `this' or an instance of `New'.
321 public override Expression DoResolve (EmitContext ec)
323 // Create an embedded assignment if our source is an assignment.
324 if (source is Assign)
325 source = embedded = ((Assign) source).GetEmbeddedAssign (loc);
327 real_source = source = source.Resolve (ec);
328 if (source == null) {
329 // Ensure that we don't propagate the error as spurious "uninitialized variable" errors.
330 target = target.ResolveLValue (ec, EmptyExpression.Null, Location);
331 return null;
335 // This is used in an embedded assignment.
336 // As an example, consider the statement "A = X = Y = Z".
338 if (is_embedded && !(source is Constant)) {
339 // If this is the innermost assignment (the "Y = Z" in our example),
340 // create a new temporary local, otherwise inherit that variable
341 // from our child (the "X = (Y = Z)" inherits the local from the
342 // "Y = Z" assignment).
344 if (embedded == null) {
345 if (this is CompoundAssign)
346 real_temp = temp = new LocalTemporary (target.Type);
347 else
348 real_temp = temp = new LocalTemporary (source.Type);
349 } else
350 temp = embedded.temp;
352 // Set the source to the new temporary variable.
353 // This means that the following target.ResolveLValue () will tell
354 // the target to read it's source value from that variable.
355 source = temp;
358 // If we have an embedded assignment, use the embedded assignment's temporary
359 // local variable as source.
360 if (embedded != null)
361 source = (embedded.temp != null) ? embedded.temp : embedded.source;
363 target = target.ResolveLValue (ec, source, Location);
365 if (target == null)
366 return null;
368 bool same_assignment = (embedded != null) ? embedded.Target.Equals(target) : source.Equals (target);
369 if (same_assignment) {
370 Report.Warning (1717, 3, loc, "Assignment made to same variable; did you mean to assign something else?");
373 Type target_type = target.Type;
374 Type source_type = real_source.Type;
376 // If we're an embedded assignment, our parent will reuse our source as its
377 // source, it won't read from our target.
378 if (is_embedded)
379 type = source_type;
380 else
381 type = target_type;
382 eclass = ExprClass.Value;
384 if (target is EventExpr) {
385 EventInfo ei = ((EventExpr) target).EventInfo;
387 Expression ml = MemberLookup (
388 ec.ContainerType, ec.ContainerType, ei.Name,
389 MemberTypes.Event, AllBindingFlags | BindingFlags.DeclaredOnly, loc);
391 if (ml == null) {
393 // If this is the case, then the Event does not belong
394 // to this Type and so, according to the spec
395 // is allowed to only appear on the left hand of
396 // the += and -= operators
398 // Note that target will not appear as an EventExpr
399 // in the case it is being referenced within the same type container;
400 // it will appear as a FieldExpr in that case.
403 if (!(source is BinaryDelegate)) {
404 error70 (ei, loc);
405 return null;
410 if (!(target is IAssignMethod) && (target.eclass != ExprClass.EventAccess)) {
411 Error_ValueAssignment (loc);
412 return null;
415 if ((source.eclass == ExprClass.Type) && (source is TypeExpr)) {
416 source.Error_UnexpectedKind (ec.DeclContainer, "variable or value", loc);
417 return null;
418 } else if ((RootContext.Version == LanguageVersion.ISO_1) &&
419 (source is MethodGroupExpr)){
420 ((MethodGroupExpr) source).ReportUsageError ();
421 return null;
425 if (target_type == source_type){
426 if (source is New && target_type.IsValueType &&
427 (target.eclass != ExprClass.IndexerAccess) && (target.eclass != ExprClass.PropertyAccess)){
428 New n = (New) source;
430 if (n.SetValueTypeVariable (target))
431 return n;
432 else
433 return null;
436 return this;
440 // If this assignment/operator was part of a compound binary
441 // operator, then we allow an explicit conversion, as detailed
442 // in the spec.
445 if (this is CompoundAssign){
446 CompoundAssign a = (CompoundAssign) this;
448 Binary b = source as Binary;
449 if (b != null){
451 // 1. if the source is explicitly convertible to the
452 // target_type
455 source = Convert.ExplicitConversion (ec, source, target_type, loc);
456 if (source == null){
457 a.original_source.Error_ValueCannotBeConverted (ec, loc, target_type, true);
458 return null;
462 // 2. and the original right side is implicitly convertible to
463 // the type of target
465 if (Convert.ImplicitConversionExists (ec, a.original_source, target_type))
466 return this;
469 // In the spec 2.4 they added: or if type of the target is int
470 // and the operator is a shift operator...
472 if (source_type == TypeManager.int32_type &&
473 (b.Oper == Binary.Operator.LeftShift || b.Oper == Binary.Operator.RightShift))
474 return this;
476 a.original_source.Error_ValueCannotBeConverted (ec, loc, target_type, false);
477 return null;
481 if (source.eclass == ExprClass.MethodGroup && !TypeManager.IsDelegateType (target_type)) {
482 Report.Error (428, source.Location, "Cannot convert method group `{0}' to non-delegate type `{1}'. Did you intend to invoke the method?",
483 ((MethodGroupExpr)source).Name, target.GetSignatureForError ());
484 return null;
487 source = Convert.ImplicitConversionRequired (ec, source, target_type, loc);
488 if (source == null)
489 return null;
491 // If we're an embedded assignment, we need to create a new temporary variable
492 // for the converted value. Our parent will use this new variable as its source.
493 // The same applies when we have an embedded assignment - in this case, we need
494 // to convert our embedded assignment's temporary local variable to the correct
495 // type and store it in a new temporary local.
496 if (is_embedded || embedded != null) {
497 type = target_type;
498 temp = new LocalTemporary (type);
499 must_free_temp = true;
502 return this;
505 Expression EmitEmbedded (EmitContext ec)
507 // Emit an embedded assignment.
509 if (real_temp != null) {
510 // If we're the innermost assignment, `real_source' is the right-hand
511 // expression which gets assigned to all the variables left of it.
512 // Emit this expression and store its result in real_temp.
513 real_source.Emit (ec);
514 real_temp.Store (ec);
517 if (embedded != null)
518 embedded.EmitEmbedded (ec);
520 // This happens when we've done a type conversion, in this case source will be
521 // the expression which does the type conversion from real_temp.
522 // So emit it and store the result in temp; this is the var which will be read
523 // by our parent.
524 if (temp != real_temp) {
525 source.Emit (ec);
526 temp.Store (ec);
529 Expression temp_source = (temp != null) ? temp : source;
530 ((IAssignMethod) target).EmitAssign (ec, temp_source, false, false);
531 return temp_source;
534 void ReleaseEmbedded (EmitContext ec)
536 if (embedded != null)
537 embedded.ReleaseEmbedded (ec);
539 if (real_temp != null)
540 real_temp.Release (ec);
542 if (must_free_temp)
543 temp.Release (ec);
546 void Emit (EmitContext ec, bool is_statement)
548 if (target is EventExpr) {
549 ((EventExpr) target).EmitAddOrRemove (ec, source);
550 return;
553 IAssignMethod am = (IAssignMethod) target;
555 Expression temp_source;
556 if (embedded != null) {
557 temp_source = embedded.EmitEmbedded (ec);
559 if (temp != null) {
560 source.Emit (ec);
561 temp.Store (ec);
562 temp_source = temp;
564 } else
565 temp_source = source;
567 am.EmitAssign (ec, temp_source, !is_statement, this is CompoundAssign);
569 if (embedded != null) {
570 if (temp != null)
571 temp.Release (ec);
572 embedded.ReleaseEmbedded (ec);
576 public override void Emit (EmitContext ec)
578 Emit (ec, false);
581 public override void EmitStatement (EmitContext ec)
583 Emit (ec, true);
586 protected override void CloneTo (CloneContext clonectx, Expression t)
588 Assign _target = (Assign) t;
590 _target.target = target.Clone (clonectx);
591 _target.source = source.Clone (clonectx);
596 // This class implements fields and events class initializers
597 public class FieldInitializer : Assign
599 public readonly DeclSpace TypeContainer;
601 public FieldInitializer (FieldBuilder field, Expression expression, DeclSpace container)
602 : base (new FieldExpr (field, expression.Location, true), expression)
604 this.TypeContainer = container;
605 if (!field.IsStatic)
606 ((FieldExpr)target).InstanceExpression = CompilerGeneratedThis.Instance;
609 public bool IsComplexInitializer {
610 get {
611 if (embedded != null)
612 return true;
614 return !(source is Constant);
618 public bool IsDefaultInitializer {
619 get {
620 Constant c = source as Constant;
621 if (c == null)
622 return false;
624 FieldExpr fe = (FieldExpr)target;
625 return c.IsDefaultInitializer (fe.Type);
632 // This class is used for compound assignments.
634 class CompoundAssign : Assign {
635 Binary.Operator op;
636 public Expression original_source;
638 public CompoundAssign (Binary.Operator op, Expression target, Expression source)
639 : base (target, source, target.Location)
641 original_source = source;
642 this.op = op;
645 protected CompoundAssign (CompoundAssign embedded, Location l)
646 : this (embedded.op, embedded.target, embedded.source)
648 this.is_embedded = true;
651 protected override Assign GetEmbeddedAssign (Location loc)
653 return new CompoundAssign (this, loc);
656 public override Expression DoResolve (EmitContext ec)
658 original_source = original_source.Resolve (ec);
659 if (original_source == null)
660 return null;
662 target = target.Resolve (ec);
663 if (target == null)
664 return null;
666 if (target is MethodGroupExpr){
667 Error_CannotAssign (((MethodGroupExpr)target).Name, target.ExprClassName);
668 return null;
671 // Only now we can decouple the original source/target
672 // into a tree, to guarantee that we do not have side
673 // effects.
675 source = new Binary (op, target, original_source);
676 return base.DoResolve (ec);
679 protected override void CloneTo (CloneContext clonectx, Expression t)
681 CompoundAssign target = (CompoundAssign) t;
683 target.original_source = original_source.Clone (clonectx);