2004-11-01 Miguel de Icaza <miguel@ximian.com>
[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@gnome.org)
7 //
8 // (C) 2001, 2002, 2003 Ximian, Inc.
9 //
10 using System;
11 using System.Reflection;
12 using System.Reflection.Emit;
13
14 namespace Mono.CSharp {
15
16         /// <summary>
17         ///   This interface is implemented by expressions that can be assigned to.
18         /// </summary>
19         /// <remarks>
20         ///   This interface is implemented by Expressions whose values can not
21         ///   store the result on the top of the stack.
22         ///
23         ///   Expressions implementing this (Properties, Indexers and Arrays) would
24         ///   perform an assignment of the Expression "source" into its final
25         ///   location.
26         ///
27         ///   No values on the top of the stack are expected to be left by
28         ///   invoking this method.
29         /// </remarks>
30         public interface IAssignMethod {
31                 //
32                 // This is an extra version of Emit. If leave_copy is `true'
33                 // A copy of the expression will be left on the stack at the
34                 // end of the code generated for EmitAssign
35                 //
36                 void Emit (EmitContext ec, bool leave_copy);
37                 
38                 //
39                 // This method does the assignment
40                 // `source' will be stored into the location specified by `this'
41                 // if `leave_copy' is true, a copy of `source' will be left on the stack
42                 // if `prepare_for_load' is true, when `source' is emitted, there will
43                 // be data on the stack that it can use to compuatate its value. This is
44                 // for expressions like a [f ()] ++, where you can't call `f ()' twice.
45                 //
46                 void EmitAssign (EmitContext ec, Expression source, bool leave_copy, bool prepare_for_load);
47                 
48                 /*
49                 For simple assignments, this interface is very simple, EmitAssign is called with source
50                 as the source expression and leave_copy and prepare_for_load false.
51                 
52                 For compound assignments it gets complicated.
53                 
54                 EmitAssign will be called as before, however, prepare_for_load will be
55                 true. The @source expression will contain an expression
56                 which calls Emit. So, the calls look like:
57                 
58                 this.EmitAssign (ec, source, false, true) ->
59                         source.Emit (ec); ->
60                                 [...] ->
61                                         this.Emit (ec, false); ->
62                                         end this.Emit (ec, false); ->
63                                 end [...]
64                         end source.Emit (ec);
65                 end this.EmitAssign (ec, source, false, true)
66                 
67                 
68                 When prepare_for_load is true, EmitAssign emits a `token' on the stack that
69                 Emit will use for its state.
70                 
71                 Let's take FieldExpr as an example. assume we are emitting f ().y += 1;
72                 
73                 Here is the call tree again. This time, each call is annotated with the IL
74                 it produces:
75                 
76                 this.EmitAssign (ec, source, false, true)
77                         call f
78                         dup
79                         
80                         Binary.Emit ()
81                                 this.Emit (ec, false);
82                                 ldfld y
83                                 end this.Emit (ec, false);
84                                 
85                                 IntConstant.Emit ()
86                                 ldc.i4.1
87                                 end IntConstant.Emit
88                                 
89                                 add
90                         end Binary.Emit ()
91                         
92                         stfld
93                 end this.EmitAssign (ec, source, false, true)
94                 
95                 Observe two things:
96                         1) EmitAssign left a token on the stack. It was the result of f ().
97                         2) This token was used by Emit
98                 
99                 leave_copy (in both EmitAssign and Emit) tells the compiler to leave a copy
100                 of the expression at that point in evaluation. This is used for pre/post inc/dec
101                 and for a = x += y. Let's do the above example with leave_copy true in EmitAssign
102                 
103                 this.EmitAssign (ec, source, true, true)
104                         call f
105                         dup
106                         
107                         Binary.Emit ()
108                                 this.Emit (ec, false);
109                                 ldfld y
110                                 end this.Emit (ec, false);
111                                 
112                                 IntConstant.Emit ()
113                                 ldc.i4.1
114                                 end IntConstant.Emit
115                                 
116                                 add
117                         end Binary.Emit ()
118                         
119                         dup
120                         stloc temp
121                         stfld
122                         ldloc temp
123                 end this.EmitAssign (ec, source, true, true)
124                 
125                 And with it true in Emit
126                 
127                 this.EmitAssign (ec, source, false, true)
128                         call f
129                         dup
130                         
131                         Binary.Emit ()
132                                 this.Emit (ec, true);
133                                 ldfld y
134                                 dup
135                                 stloc temp
136                                 end this.Emit (ec, true);
137                                 
138                                 IntConstant.Emit ()
139                                 ldc.i4.1
140                                 end IntConstant.Emit
141                                 
142                                 add
143                         end Binary.Emit ()
144                         
145                         stfld
146                         ldloc temp
147                 end this.EmitAssign (ec, source, false, true)
148                 
149                 Note that these two examples are what happens for ++x and x++, respectively.
150                 */
151         }
152
153         /// <summary>
154         ///   An Expression to hold a temporary value.
155         /// </summary>
156         /// <remarks>
157         ///   The LocalTemporary class is used to hold temporary values of a given
158         ///   type to "simulate" the expression semantics on property and indexer
159         ///   access whose return values are void.
160         ///
161         ///   The local temporary is used to alter the normal flow of code generation
162         ///   basically it creates a local variable, and its emit instruction generates
163         ///   code to access this value, return its address or save its value.
164         ///
165         ///   If `is_address' is true, then the value that we store is the address to the
166         ///   real value, and not the value itself. 
167         ///
168         ///   This is needed for a value type, because otherwise you just end up making a
169         ///   copy of the value on the stack and modifying it. You really need a pointer
170         ///   to the origional value so that you can modify it in that location. This
171         ///   Does not happen with a class because a class is a pointer -- so you always
172         ///   get the indirection.
173         ///
174         ///   The `is_address' stuff is really just a hack. We need to come up with a better
175         ///   way to handle it.
176         /// </remarks>
177         public class LocalTemporary : Expression, IMemoryLocation {
178                 LocalBuilder builder;
179                 bool is_address;
180                 
181                 public LocalTemporary (EmitContext ec, Type t) : this (ec, t, false) {}
182                         
183                 public LocalTemporary (EmitContext ec, Type t, bool is_address) 
184                 {
185                         type = t;
186                         eclass = ExprClass.Value;
187                         loc = Location.Null;
188                         builder = ec.GetTemporaryLocal (is_address ? TypeManager.GetReferenceType (t): t);
189                         this.is_address = is_address;
190                 }
191
192                 public LocalTemporary (LocalBuilder b, Type t)
193                 {
194                         type = t;
195                         eclass = ExprClass.Value;
196                         loc = Location.Null;
197                         builder = b;
198                 }
199
200                 public void Release (EmitContext ec)
201                 {
202                         ec.FreeTemporaryLocal (builder, type);
203                         builder = null;
204                 }
205                 
206                 public override Expression DoResolve (EmitContext ec)
207                 {
208                         return this;
209                 }
210
211                 public override void Emit (EmitContext ec)
212                 {
213                         ILGenerator ig = ec.ig;
214                         
215                         ig.Emit (OpCodes.Ldloc, builder);
216                         // we need to copy from the pointer
217                         if (is_address)
218                                 LoadFromPtr (ig, type);
219                 }
220
221                 // NB: if you have `is_address' on the stack there must
222                 // be a managed pointer. Otherwise, it is the type from
223                 // the ctor.
224                 public void Store (EmitContext ec)
225                 {
226                         ILGenerator ig = ec.ig;
227                         ig.Emit (OpCodes.Stloc, builder);
228                 }
229
230                 public void AddressOf (EmitContext ec, AddressOp mode)
231                 {
232                         // if is_address, than this is just the address anyways,
233                         // so we just return this.
234                         ILGenerator ig = ec.ig;
235                                 
236                         if (is_address)
237                                 ig.Emit (OpCodes.Ldloc, builder);
238                         else
239                                 ig.Emit (OpCodes.Ldloca, builder);
240                 }
241
242                 public bool PointsToAddress {
243                         get {
244                                 return is_address;
245                         }
246                 }
247         }
248
249         /// <summary>
250         ///   The Assign node takes care of assigning the value of source into
251         ///   the expression represented by target. 
252         /// </summary>
253         public class Assign : ExpressionStatement {
254                 protected Expression target, source, real_source;
255                 protected LocalTemporary temp = null, real_temp = null;
256                 protected Assign embedded = null;
257                 protected bool is_embedded = false;
258                 protected bool must_free_temp = false;
259
260                 public Assign (Expression target, Expression source, Location l)
261                 {
262                         this.target = target;
263                         this.source = this.real_source = source;
264                         this.loc = l;
265                 }
266
267                 protected Assign (Assign embedded, Location l)
268                         : this (embedded.target, embedded.source, l)
269                 {
270                         this.is_embedded = true;
271                 }
272
273                 protected virtual Assign GetEmbeddedAssign (Location loc)
274                 {
275                         return new Assign (this, loc);
276                 }
277
278                 public Expression Target {
279                         get {
280                                 return target;
281                         }
282
283                         set {
284                                 target = value;
285                         }
286                 }
287
288                 public Expression Source {
289                         get {
290                                 return source;
291                         }
292
293                         set {
294                                 source = value;
295                         }
296                 }
297
298                 public static void error70 (EventInfo ei, Location l)
299                 {
300                         Report.Error (70, l, "The event '" + ei.Name +
301                                       "' can only appear on the left-side of a += or -= (except when" +
302                                       " used from within the type '" + ei.DeclaringType + "')");
303                 }
304
305                 //
306                 // Will return either `this' or an instance of `New'.
307                 //
308                 public override Expression DoResolve (EmitContext ec)
309                 {
310                         // Create an embedded assignment if our source is an assignment.
311                         if (source is Assign)
312                                 source = embedded = ((Assign) source).GetEmbeddedAssign (loc);
313
314                         real_source = source = source.Resolve (ec);
315                         if (source == null)
316                                 return null;
317
318                         //
319                         // This is used in an embedded assignment.
320                         // As an example, consider the statement "A = X = Y = Z".
321                         //
322                         if (is_embedded && !(source is Constant)) {
323                                 // If this is the innermost assignment (the "Y = Z" in our example),
324                                 // create a new temporary local, otherwise inherit that variable
325                                 // from our child (the "X = (Y = Z)" inherits the local from the
326                                 // "Y = Z" assignment).
327
328                                 if (embedded == null) {
329                                         if (this is CompoundAssign)
330                                                 real_temp = temp = new LocalTemporary (ec, target.Type);
331                                         else
332                                                 real_temp = temp = new LocalTemporary (ec, source.Type);
333                                 } else
334                                         temp = embedded.temp;
335
336                                 // Set the source to the new temporary variable.
337                                 // This means that the following target.ResolveLValue () will tell
338                                 // the target to read it's source value from that variable.
339                                 source = temp;
340                         }
341
342                         // If we have an embedded assignment, use the embedded assignment's temporary
343                         // local variable as source.
344                         if (embedded != null)
345                                 source = (embedded.temp != null) ? embedded.temp : embedded.source;
346
347                         target = target.ResolveLValue (ec, source);
348
349                         if (target == null)
350                                 return null;
351
352                         Type target_type = target.Type;
353                         Type source_type = real_source.Type;
354
355                         // If we're an embedded assignment, our parent will reuse our source as its
356                         // source, it won't read from our target.
357                         if (is_embedded)
358                                 type = source_type;
359                         else
360                                 type = target_type;
361                         eclass = ExprClass.Value;
362
363
364                         if (target is EventExpr) {
365                                 EventInfo ei = ((EventExpr) target).EventInfo;
366
367                                 Expression ml = MemberLookup (
368                                         ec, ec.ContainerType, ei.Name,
369                                         MemberTypes.Event, AllBindingFlags | BindingFlags.DeclaredOnly, loc);
370
371                                 if (ml == null) {
372                                         //
373                                         // If this is the case, then the Event does not belong 
374                                         // to this Type and so, according to the spec
375                                         // is allowed to only appear on the left hand of
376                                         // the += and -= operators
377                                         //
378                                         // Note that target will not appear as an EventExpr
379                                         // in the case it is being referenced within the same type container;
380                                         // it will appear as a FieldExpr in that case.
381                                         //
382                                         
383                                         if (!(source is BinaryDelegate)) {
384                                                 error70 (ei, loc);
385                                                 return null;
386                                         } 
387                                 }
388                         }
389                         
390                         if (!(target is IAssignMethod) && (target.eclass != ExprClass.EventAccess)) {
391                                 Report.Error (131, loc,
392                                               "Left hand of an assignment must be a variable, " +
393                                               "a property or an indexer");
394                                 return null;
395                         }
396
397                         if ((source.eclass == ExprClass.Type) && (source is TypeExpr)) {
398                                 source.Error_UnexpectedKind ("variable or value", loc);
399                                 return null;
400                         } else if ((RootContext.Version == LanguageVersion.ISO_1) &&
401                                    (source is MethodGroupExpr)){
402                                 ((MethodGroupExpr) source).ReportUsageError ();
403                                 return null;
404
405                         }
406                         
407                         if (target_type == source_type){
408                                 if (source is New && target_type.IsValueType &&
409                                     (target.eclass != ExprClass.IndexerAccess) && (target.eclass != ExprClass.PropertyAccess)){
410                                         New n = (New) source;
411                                         
412                                         if (n.SetValueTypeVariable (target))
413                                                 return n;
414                                         else
415                                                 return null;
416                                 }
417                                 
418                                 return this;
419                         }
420                         
421                         //
422                         // If this assignemnt/operator was part of a compound binary
423                         // operator, then we allow an explicit conversion, as detailed
424                         // in the spec. 
425                         //
426
427                         if (this is CompoundAssign){
428                                 CompoundAssign a = (CompoundAssign) this;
429                                 
430                                 Binary b = source as Binary;
431                                 if (b != null){
432                                         //
433                                         // 1. if the source is explicitly convertible to the
434                                         //    target_type
435                                         //
436                                         
437                                         source = Convert.ExplicitConversion (ec, source, target_type, loc);
438                                         if (source == null){
439                                                 Convert.Error_CannotImplicitConversion (loc, source_type, target_type);
440                                                 return null;
441                                         }
442                                 
443                                         //
444                                         // 2. and the original right side is implicitly convertible to
445                                         // the type of target
446                                         //
447                                         if (Convert.ImplicitStandardConversionExists (ec, a.original_source, target_type))
448                                                 return this;
449
450                                         //
451                                         // In the spec 2.4 they added: or if type of the target is int
452                                         // and the operator is a shift operator...
453                                         //
454                                         if (source_type == TypeManager.int32_type &&
455                                             (b.Oper == Binary.Operator.LeftShift || b.Oper == Binary.Operator.RightShift))
456                                                 return this;
457
458                                         Convert.Error_CannotImplicitConversion (loc, a.original_source.Type, target_type);
459                                         return null;
460                                 }
461                         }
462
463                         source = Convert.ImplicitConversionRequired (ec, source, target_type, loc);
464                         if (source == null)
465                                 return null;
466
467                         // If we're an embedded assignment, we need to create a new temporary variable
468                         // for the converted value.  Our parent will use this new variable as its source.
469                         // The same applies when we have an embedded assignment - in this case, we need
470                         // to convert our embedded assignment's temporary local variable to the correct
471                         // type and store it in a new temporary local.
472                         if (is_embedded || embedded != null) {
473                                 type = target_type;
474                                 temp = new LocalTemporary (ec, type);
475                                 must_free_temp = true;
476                         }
477                         
478                         return this;
479                 }
480
481                 Expression EmitEmbedded (EmitContext ec)
482                 {
483                         // Emit an embedded assignment.
484
485                         if (real_temp != null) {
486                                 // If we're the innermost assignment, `real_source' is the right-hand
487                                 // expression which gets assigned to all the variables left of it.
488                                 // Emit this expression and store its result in real_temp.
489                                 real_source.Emit (ec);
490                                 real_temp.Store (ec);
491                         }
492
493                         if (embedded != null)
494                                 embedded.EmitEmbedded (ec);
495
496                         // This happens when we've done a type conversion, in this case source will be
497                         // the expression which does the type conversion from real_temp.
498                         // So emit it and store the result in temp; this is the var which will be read
499                         // by our parent.
500                         if (temp != real_temp) {
501                                 source.Emit (ec);
502                                 temp.Store (ec);
503                         }
504
505                         Expression temp_source = (temp != null) ? temp : source;
506                         ((IAssignMethod) target).EmitAssign (ec, temp_source, false, false);
507                         return temp_source;
508                 }
509
510                 void ReleaseEmbedded (EmitContext ec)
511                 {
512                         if (embedded != null)
513                                 embedded.ReleaseEmbedded (ec);
514
515                         if (real_temp != null)
516                                 real_temp.Release (ec);
517
518                         if (must_free_temp)
519                                 temp.Release (ec);
520                 }
521
522                 void Emit (EmitContext ec, bool is_statement)
523                 {
524                         if (target is EventExpr) {
525                                 ((EventExpr) target).EmitAddOrRemove (ec, source);
526                                 return;
527                         }
528                         
529                         IAssignMethod am = (IAssignMethod) target;
530
531                         Expression temp_source;
532                         if (embedded != null) {
533                                 temp_source = embedded.EmitEmbedded (ec);
534
535                                 if (temp != null) {
536                                         source.Emit (ec);
537                                         temp.Store (ec);
538                                         temp_source = temp;
539                                 }
540                         } else
541                                 temp_source = source;
542                 
543                         am.EmitAssign (ec, temp_source, !is_statement, this is CompoundAssign);
544                                 
545                         if (embedded != null) {
546                                 if (temp != null)
547                                         temp.Release (ec);
548                                 embedded.ReleaseEmbedded (ec);
549                         }
550                 }
551                 
552                 public override void Emit (EmitContext ec)
553                 {
554                         Emit (ec, false);
555                 }
556
557                 public override void EmitStatement (EmitContext ec)
558                 {
559                         Emit (ec, true);
560                 }
561         }
562
563         
564         //
565         // This class is used for compound assignments.  
566         //
567         class CompoundAssign : Assign {
568                 Binary.Operator op;
569                 public Expression original_source;
570                 
571                 public CompoundAssign (Binary.Operator op, Expression target, Expression source, Location l)
572                         : base (target, source, l)
573                 {
574                         original_source = source;
575                         this.op = op;
576                 }
577
578                 protected CompoundAssign (CompoundAssign embedded, Location l)
579                         : this (embedded.op, embedded.target, embedded.source, l)
580                 {
581                         this.is_embedded = true;
582                 }
583
584                 protected override Assign GetEmbeddedAssign (Location loc)
585                 {
586                         return new CompoundAssign (this, loc);
587                 }
588
589                 public Expression ResolveSource (EmitContext ec)
590                 {
591                         return original_source.Resolve (ec);
592                 }
593
594                 public override Expression DoResolve (EmitContext ec)
595                 {
596                         original_source = original_source.Resolve (ec);
597                         if (original_source == null)
598                                 return null;
599
600                         target = target.Resolve (ec);
601                         if (target == null)
602                                 return null;
603                         
604                         //
605                         // Only now we can decouple the original source/target
606                         // into a tree, to guarantee that we do not have side
607                         // effects.
608                         //
609                         source = new Binary (op, target, original_source, loc);
610                         return base.DoResolve (ec);
611                 }
612         }
613 }
614
615
616
617