merge -r 61110:61111
[mono.git] / mcs / mcs / assign.cs
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
10 //
11 using System;
12 using System.Reflection;
13 using System.Reflection.Emit;
14
15 namespace Mono.CSharp {
16
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 {
32                 //
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
36                 //
37                 void Emit (EmitContext ec, bool leave_copy);
38
39                 //
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.
46                 //
47                 void EmitAssign (EmitContext ec, Expression source, bool leave_copy, bool prepare_for_load);
48
49                 /*
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.
52
53                 For compound assignments it gets complicated.
54
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:
58
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)
67
68
69                 When prepare_for_load is true, EmitAssign emits a `token' on the stack that
70                 Emit will use for its state.
71
72                 Let's take FieldExpr as an example. assume we are emitting f ().y += 1;
73
74                 Here is the call tree again. This time, each call is annotated with the IL
75                 it produces:
76
77                 this.EmitAssign (ec, source, false, true)
78                         call f
79                         dup
80
81                         Binary.Emit ()
82                                 this.Emit (ec, false);
83                                 ldfld y
84                                 end this.Emit (ec, false);
85
86                                 IntConstant.Emit ()
87                                 ldc.i4.1
88                                 end IntConstant.Emit
89
90                                 add
91                         end Binary.Emit ()
92
93                         stfld
94                 end this.EmitAssign (ec, source, false, true)
95
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
99
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
103
104                 this.EmitAssign (ec, source, true, true)
105                         call f
106                         dup
107
108                         Binary.Emit ()
109                                 this.Emit (ec, false);
110                                 ldfld y
111                                 end this.Emit (ec, false);
112
113                                 IntConstant.Emit ()
114                                 ldc.i4.1
115                                 end IntConstant.Emit
116
117                                 add
118                         end Binary.Emit ()
119
120                         dup
121                         stloc temp
122                         stfld
123                         ldloc temp
124                 end this.EmitAssign (ec, source, true, true)
125
126                 And with it true in Emit
127
128                 this.EmitAssign (ec, source, false, true)
129                         call f
130                         dup
131
132                         Binary.Emit ()
133                                 this.Emit (ec, true);
134                                 ldfld y
135                                 dup
136                                 stloc temp
137                                 end this.Emit (ec, true);
138
139                                 IntConstant.Emit ()
140                                 ldc.i4.1
141                                 end IntConstant.Emit
142
143                                 add
144                         end Binary.Emit ()
145
146                         stfld
147                         ldloc temp
148                 end this.EmitAssign (ec, source, false, true)
149
150                 Note that these two examples are what happens for ++x and x++, respectively.
151                 */
152         }
153
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.
161         ///
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.
165         ///
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.
168         ///
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.
174         ///
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;
181
182                 public LocalTemporary (Type t) : this (t, false) {}
183
184                 public LocalTemporary (Type t, bool is_address)
185                 {
186                         type = t;
187                         eclass = ExprClass.Value;
188                         this.is_address = is_address;
189                 }
190
191                 public LocalTemporary (LocalBuilder b, Type t)
192                 {
193                         type = t;
194                         eclass = ExprClass.Value;
195                         loc = Location.Null;
196                         builder = b;
197                 }
198
199                 public void Release (EmitContext ec)
200                 {
201                         ec.FreeTemporaryLocal (builder, type);
202                         builder = null;
203                 }
204
205                 public override Expression DoResolve (EmitContext ec)
206                 {
207                         return this;
208                 }
209
210                 public override void Emit (EmitContext ec)
211                 {
212                         ILGenerator ig = ec.ig;
213
214                         ig.Emit (OpCodes.Ldloc, builder);
215                         // we need to copy from the pointer
216                         if (is_address)
217                                 LoadFromPtr (ig, type);
218                 }
219
220                 // NB: if you have `is_address' on the stack there must
221                 // be a managed pointer. Otherwise, it is the type from
222                 // the ctor.
223                 public void Store (EmitContext ec)
224                 {
225                         ILGenerator ig = ec.ig;
226                         if (builder == null)
227                                 builder = ec.GetTemporaryLocal (is_address ? TypeManager.GetReferenceType (type): type);
228
229                         ig.Emit (OpCodes.Stloc, builder);
230                 }
231
232                 public void AddressOf (EmitContext ec, AddressOp mode)
233                 {
234                         if (builder == null)
235                                 builder = ec.GetTemporaryLocal (is_address ? TypeManager.GetReferenceType (type): type);
236
237                         // if is_address, than this is just the address anyways,
238                         // so we just return this.
239                         ILGenerator ig = ec.ig;
240
241                         if (is_address)
242                                 ig.Emit (OpCodes.Ldloc, builder);
243                         else
244                                 ig.Emit (OpCodes.Ldloca, builder);
245                 }
246
247                 public bool PointsToAddress {
248                         get {
249                                 return is_address;
250                         }
251                 }
252         }
253
254         /// <summary>
255         ///   The Assign node takes care of assigning the value of source into
256         ///   the expression represented by target.
257         /// </summary>
258         public class Assign : ExpressionStatement {
259                 protected Expression target, source, real_source;
260                 protected LocalTemporary temp = null, real_temp = null;
261                 protected Assign embedded = null;
262                 protected bool is_embedded = false;
263                 protected bool must_free_temp = false;
264
265                 public Assign (Expression target, Expression source)
266                         : this (target, source, target.Location)
267                 {
268                 }
269
270                 public Assign (Expression target, Expression source, Location l)
271                 {
272                         this.target = target;
273                         this.source = this.real_source = source;
274                         this.loc = l;
275                 }
276
277                 protected Assign (Assign embedded, Location l)
278                         : this (embedded.target, embedded.source, l)
279                 {
280                         this.is_embedded = true;
281                 }
282
283                 protected virtual Assign GetEmbeddedAssign (Location loc)
284                 {
285                         return new Assign (this, loc);
286                 }
287
288                 public Expression Target {
289                         get {
290                                 return target;
291                         }
292
293                         set {
294                                 target = value;
295                         }
296                 }
297
298                 public Expression Source {
299                         get {
300                                 return source;
301                         }
302
303                         set {
304                                 source = value;
305                         }
306                 }
307
308                 public static void error70 (EventInfo ei, Location l)
309                 {
310                         Report.Error (70, l, "The event `" + TypeManager.CSharpSignature (ei) +
311                                       "' can only appear on the left hand side of += or -= (except when" +
312                                       " used from within the type `" + ei.DeclaringType + "')");
313                 }
314
315                 //
316                 // Will return either `this' or an instance of `New'.
317                 //
318                 public override Expression DoResolve (EmitContext ec)
319                 {
320                         // Create an embedded assignment if our source is an assignment.
321                         if (source is Assign)
322                                 source = embedded = ((Assign) source).GetEmbeddedAssign (loc);
323
324                         real_source = source = source.Resolve (ec);
325                         if (source == null) {
326                                 // Ensure that we don't propagate the error as spurious "uninitialized variable" errors.
327                                 target = target.ResolveLValue (ec, EmptyExpression.Null, Location);
328                                 return null;
329                         }
330
331                         //
332                         // This is used in an embedded assignment.
333                         // As an example, consider the statement "A = X = Y = Z".
334                         //
335                         if (is_embedded && !(source is Constant)) {
336                                 // If this is the innermost assignment (the "Y = Z" in our example),
337                                 // create a new temporary local, otherwise inherit that variable
338                                 // from our child (the "X = (Y = Z)" inherits the local from the
339                                 // "Y = Z" assignment).
340
341                                 if (embedded == null) {
342                                         if (this is CompoundAssign)
343                                                 real_temp = temp = new LocalTemporary (target.Type);
344                                         else
345                                                 real_temp = temp = new LocalTemporary (source.Type);
346                                 } else
347                                         temp = embedded.temp;
348
349                                 // Set the source to the new temporary variable.
350                                 // This means that the following target.ResolveLValue () will tell
351                                 // the target to read it's source value from that variable.
352                                 source = temp;
353                         }
354
355                         // If we have an embedded assignment, use the embedded assignment's temporary
356                         // local variable as source.
357                         if (embedded != null)
358                                 source = (embedded.temp != null) ? embedded.temp : embedded.source;
359
360                         target = target.ResolveLValue (ec, source, Location);
361
362                         if (target == null)
363                                 return null;
364
365                         bool same_assignment = (embedded != null) ? embedded.Target.Equals(target) : source.Equals (target);
366                         if (same_assignment) {
367                                 Report.Warning (1717, 3, loc, "Assignment made to same variable; did you mean to assign something else?");
368                         }
369
370                         Type target_type = target.Type;
371                         Type source_type = real_source.Type;
372
373                         // If we're an embedded assignment, our parent will reuse our source as its
374                         // source, it won't read from our target.
375                         if (is_embedded)
376                                 type = source_type;
377                         else
378                                 type = target_type;
379                         eclass = ExprClass.Value;
380
381                         if (target is EventExpr) {
382                                 EventInfo ei = ((EventExpr) target).EventInfo;
383
384                                 Expression ml = MemberLookup (
385                                         ec.ContainerType, ec.ContainerType, ei.Name,
386                                         MemberTypes.Event, AllBindingFlags | BindingFlags.DeclaredOnly, loc);
387
388                                 if (ml == null) {
389                                         //
390                                         // If this is the case, then the Event does not belong
391                                         // to this Type and so, according to the spec
392                                         // is allowed to only appear on the left hand of
393                                         // the += and -= operators
394                                         //
395                                         // Note that target will not appear as an EventExpr
396                                         // in the case it is being referenced within the same type container;
397                                         // it will appear as a FieldExpr in that case.
398                                         //
399
400                                         if (!(source is BinaryDelegate)) {
401                                                 error70 (ei, loc);
402                                                 return null;
403                                         }
404                                 }
405                         }
406
407                         if (!(target is IAssignMethod) && (target.eclass != ExprClass.EventAccess)) {
408                                 Report.Error (131, loc,
409                                               "Left hand of an assignment must be a variable, " +
410                                               "a property or an indexer");
411                                 return null;
412                         }
413
414                         if ((source.eclass == ExprClass.Type) && (source is TypeExpr)) {
415                                 source.Error_UnexpectedKind (ec.DeclContainer, "variable or value", loc);
416                                 return null;
417                         } else if ((RootContext.Version == LanguageVersion.ISO_1) &&
418                                    (source is MethodGroupExpr)){
419                                 ((MethodGroupExpr) source).ReportUsageError ();
420                                 return null;
421
422                         }
423
424                         if (target_type == source_type){
425                                 if (source is New && target_type.IsValueType &&
426                                     (target.eclass != ExprClass.IndexerAccess) && (target.eclass != ExprClass.PropertyAccess)){
427                                         New n = (New) source;
428
429                                         if (n.SetValueTypeVariable (target))
430                                                 return n;
431                                         else
432                                                 return null;
433                                 }
434
435                                 return this;
436                         }
437
438                         //
439                         // If this assignment/operator was part of a compound binary
440                         // operator, then we allow an explicit conversion, as detailed
441                         // in the spec.
442                         //
443
444                         if (this is CompoundAssign){
445                                 CompoundAssign a = (CompoundAssign) this;
446
447                                 Binary b = source as Binary;
448                                 if (b != null){
449                                         //
450                                         // 1. if the source is explicitly convertible to the
451                                         //    target_type
452                                         //
453
454                                         source = Convert.ExplicitConversion (ec, source, target_type, loc);
455                                         if (source == null){
456                                                 a.original_source.Error_ValueCannotBeConverted (loc, target_type, true);
457                                                 return null;
458                                         }
459
460                                         //
461                                         // 2. and the original right side is implicitly convertible to
462                                         // the type of target
463                                         //
464                                         if (Convert.ImplicitStandardConversionExists (a.original_source, target_type))
465                                                 return this;
466
467                                         //
468                                         // In the spec 2.4 they added: or if type of the target is int
469                                         // and the operator is a shift operator...
470                                         //
471                                         if (source_type == TypeManager.int32_type &&
472                                             (b.Oper == Binary.Operator.LeftShift || b.Oper == Binary.Operator.RightShift))
473                                                 return this;
474
475                                         a.original_source.Error_ValueCannotBeConverted (loc, target_type, false);
476                                         return null;
477                                 }
478                         }
479
480                         if (source.eclass == ExprClass.MethodGroup && !TypeManager.IsDelegateType (target_type)) {
481                                 Report.Error (428, source.Location, "Cannot convert method group `{0}' to non-delegate type `{1}'. Did you intend to invoke the method?",
482                                         ((MethodGroupExpr)source).Name, target.GetSignatureForError ());
483                                 return null;
484                         }
485
486                         source = Convert.ImplicitConversionRequired (ec, source, target_type, loc);
487                         if (source == null)
488                                 return null;
489
490                         // If we're an embedded assignment, we need to create a new temporary variable
491                         // for the converted value.  Our parent will use this new variable as its source.
492                         // The same applies when we have an embedded assignment - in this case, we need
493                         // to convert our embedded assignment's temporary local variable to the correct
494                         // type and store it in a new temporary local.
495                         if (is_embedded || embedded != null) {
496                                 type = target_type;
497                                 temp = new LocalTemporary (type);
498                                 must_free_temp = true;
499                         }
500
501                         return this;
502                 }
503
504                 Expression EmitEmbedded (EmitContext ec)
505                 {
506                         // Emit an embedded assignment.
507
508                         if (real_temp != null) {
509                                 // If we're the innermost assignment, `real_source' is the right-hand
510                                 // expression which gets assigned to all the variables left of it.
511                                 // Emit this expression and store its result in real_temp.
512                                 real_source.Emit (ec);
513                                 real_temp.Store (ec);
514                         }
515
516                         if (embedded != null)
517                                 embedded.EmitEmbedded (ec);
518
519                         // This happens when we've done a type conversion, in this case source will be
520                         // the expression which does the type conversion from real_temp.
521                         // So emit it and store the result in temp; this is the var which will be read
522                         // by our parent.
523                         if (temp != real_temp) {
524                                 source.Emit (ec);
525                                 temp.Store (ec);
526                         }
527
528                         Expression temp_source = (temp != null) ? temp : source;
529                         ((IAssignMethod) target).EmitAssign (ec, temp_source, false, false);
530                         return temp_source;
531                 }
532
533                 void ReleaseEmbedded (EmitContext ec)
534                 {
535                         if (embedded != null)
536                                 embedded.ReleaseEmbedded (ec);
537
538                         if (real_temp != null)
539                                 real_temp.Release (ec);
540
541                         if (must_free_temp)
542                                 temp.Release (ec);
543                 }
544
545                 void Emit (EmitContext ec, bool is_statement)
546                 {
547                         if (target is EventExpr) {
548                                 ((EventExpr) target).EmitAddOrRemove (ec, source);
549                                 return;
550                         }
551
552                         IAssignMethod am = (IAssignMethod) target;
553
554                         Expression temp_source;
555                         if (embedded != null) {
556                                 temp_source = embedded.EmitEmbedded (ec);
557
558                                 if (temp != null) {
559                                         source.Emit (ec);
560                                         temp.Store (ec);
561                                         temp_source = temp;
562                                 }
563                         } else
564                                 temp_source = source;
565
566                         am.EmitAssign (ec, temp_source, !is_statement, this is CompoundAssign);
567
568                         if (embedded != null) {
569                                 if (temp != null)
570                                         temp.Release (ec);
571                                 embedded.ReleaseEmbedded (ec);
572                         }
573                 }
574
575                 public override void Emit (EmitContext ec)
576                 {
577                         Emit (ec, false);
578                 }
579
580                 public override void EmitStatement (EmitContext ec)
581                 {
582                         Emit (ec, true);
583                 }
584         }
585
586
587         //
588         // This class is used for compound assignments.
589         //
590         class CompoundAssign : Assign {
591                 Binary.Operator op;
592                 public Expression original_source;
593
594                 public CompoundAssign (Binary.Operator op, Expression target, Expression source)
595                         : base (target, source, target.Location)
596                 {
597                         original_source = source;
598                         this.op = op;
599                 }
600
601                 protected CompoundAssign (CompoundAssign embedded, Location l)
602                         : this (embedded.op, embedded.target, embedded.source)
603                 {
604                         this.is_embedded = true;
605                 }
606
607                 protected override Assign GetEmbeddedAssign (Location loc)
608                 {
609                         return new CompoundAssign (this, loc);
610                 }
611
612                 public override Expression DoResolve (EmitContext ec)
613                 {
614                         original_source = original_source.Resolve (ec);
615                         if (original_source == null)
616                                 return null;
617
618                         target = target.Resolve (ec);
619                         if (target == null)
620                                 return null;
621
622                         //
623                         // Only now we can decouple the original source/target
624                         // into a tree, to guarantee that we do not have side
625                         // effects.
626                         //
627                         source = new Binary (op, target, original_source);
628                         return base.DoResolve (ec);
629                 }
630         }
631 }