This commit was manufactured by cvs2svn to create branch 'mono-1-0'.
[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 method will emit the code for the actual assignment
33                 //
34                 void EmitAssign (EmitContext ec, Expression source);
35
36                 //
37                 // This method is invoked before any code generation takes
38                 // place, and it is a mechanism to inform that the expression
39                 // will be invoked more than once, and that the method should
40                 // use temporary values to avoid having side effects
41                 //
42                 // Example: a [ g () ] ++
43                 //
44                 void CacheTemporaries (EmitContext ec);
45         }
46
47         /// <summary>
48         ///   An Expression to hold a temporary value.
49         /// </summary>
50         /// <remarks>
51         ///   The LocalTemporary class is used to hold temporary values of a given
52         ///   type to "simulate" the expression semantics on property and indexer
53         ///   access whose return values are void.
54         ///
55         ///   The local temporary is used to alter the normal flow of code generation
56         ///   basically it creates a local variable, and its emit instruction generates
57         ///   code to access this value, return its address or save its value.
58         ///
59         ///   If `is_address' is true, then the value that we store is the address to the
60         ///   real value, and not the value itself. 
61         ///
62         ///   This is needed for a value type, because otherwise you just end up making a
63         ///   copy of the value on the stack and modifying it. You really need a pointer
64         ///   to the origional value so that you can modify it in that location. This
65         ///   Does not happen with a class because a class is a pointer -- so you always
66         ///   get the indirection.
67         ///
68         ///   The `is_address' stuff is really just a hack. We need to come up with a better
69         ///   way to handle it.
70         /// </remarks>
71         public class LocalTemporary : Expression, IMemoryLocation {
72                 LocalBuilder builder;
73                 bool is_address;
74                 
75                 public LocalTemporary (EmitContext ec, Type t) : this (ec, t, false) {}
76                         
77                 public LocalTemporary (EmitContext ec, Type t, bool is_address) 
78                 {
79                         type = t;
80                         eclass = ExprClass.Value;
81                         loc = Location.Null;
82                         builder = ec.GetTemporaryLocal (is_address ? TypeManager.GetReferenceType (t): t);
83                         this.is_address = is_address;
84                 }
85
86                 public LocalTemporary (LocalBuilder b, Type t)
87                 {
88                         type = t;
89                         eclass = ExprClass.Value;
90                         loc = Location.Null;
91                         builder = b;
92                 }
93
94                 public void Release (EmitContext ec)
95                 {
96                         ec.FreeTemporaryLocal (builder, type);
97                         builder = null;
98                 }
99                 
100                 public override Expression DoResolve (EmitContext ec)
101                 {
102                         return this;
103                 }
104
105                 public override void Emit (EmitContext ec)
106                 {
107                         ILGenerator ig = ec.ig;
108                         
109                         ig.Emit (OpCodes.Ldloc, builder);
110                         // we need to copy from the pointer
111                         if (is_address)
112                                 LoadFromPtr (ig, type);
113                 }
114
115                 // NB: if you have `is_address' on the stack there must
116                 // be a managed pointer. Otherwise, it is the type from
117                 // the ctor.
118                 public void Store (EmitContext ec)
119                 {
120                         ILGenerator ig = ec.ig;
121                         ig.Emit (OpCodes.Stloc, builder);
122                 }
123
124                 public void AddressOf (EmitContext ec, AddressOp mode)
125                 {
126                         // if is_address, than this is just the address anyways,
127                         // so we just return this.
128                         ILGenerator ig = ec.ig;
129                                 
130                         if (is_address)
131                                 ig.Emit (OpCodes.Ldloc, builder);
132                         else
133                                 ig.Emit (OpCodes.Ldloca, builder);
134                 }
135
136                 public bool PointsToAddress {
137                         get {
138                                 return is_address;
139                         }
140                 }
141         }
142
143         /// <summary>
144         ///   The Assign node takes care of assigning the value of source into
145         ///   the expression represented by target. 
146         /// </summary>
147         public class Assign : ExpressionStatement {
148                 protected Expression target, source, real_source;
149                 protected LocalTemporary temp = null, real_temp = null;
150                 protected Assign embedded = null;
151                 protected bool is_embedded = false;
152                 protected bool must_free_temp = false;
153
154                 public Assign (Expression target, Expression source, Location l)
155                 {
156                         this.target = target;
157                         this.source = this.real_source = source;
158                         this.loc = l;
159                 }
160
161                 protected Assign (Assign embedded, Location l)
162                         : this (embedded.target, embedded.source, l)
163                 {
164                         this.is_embedded = true;
165                 }
166
167                 protected virtual Assign GetEmbeddedAssign (Location loc)
168                 {
169                         return new Assign (this, loc);
170                 }
171
172                 public Expression Target {
173                         get {
174                                 return target;
175                         }
176
177                         set {
178                                 target = value;
179                         }
180                 }
181
182                 public Expression Source {
183                         get {
184                                 return source;
185                         }
186
187                         set {
188                                 source = value;
189                         }
190                 }
191
192                 public static void error70 (EventInfo ei, Location l)
193                 {
194                         Report.Error (70, l, "The event '" + ei.Name +
195                                       "' can only appear on the left-side of a += or -= (except when" +
196                                       " used from within the type '" + ei.DeclaringType + "')");
197                 }
198
199                 //
200                 // Will return either `this' or an instance of `New'.
201                 //
202                 public override Expression DoResolve (EmitContext ec)
203                 {
204                         // Create an embedded assignment if our source is an assignment.
205                         if (source is Assign)
206                                 source = embedded = ((Assign) source).GetEmbeddedAssign (loc);
207
208                         real_source = source = source.Resolve (ec);
209                         if (source == null)
210                                 return null;
211
212                         //
213                         // This is used in an embedded assignment.
214                         // As an example, consider the statement "A = X = Y = Z".
215                         //
216                         if (is_embedded && !(source is Constant)) {
217                                 // If this is the innermost assignment (the "Y = Z" in our example),
218                                 // create a new temporary local, otherwise inherit that variable
219                                 // from our child (the "X = (Y = Z)" inherits the local from the
220                                 // "Y = Z" assignment).
221
222                                 if (embedded == null) {
223                                         if (this is CompoundAssign)
224                                                 real_temp = temp = new LocalTemporary (ec, target.Type);
225                                         else
226                                                 real_temp = temp = new LocalTemporary (ec, source.Type);
227                                 } else
228                                         temp = embedded.temp;
229
230                                 // Set the source to the new temporary variable.
231                                 // This means that the following target.ResolveLValue () will tell
232                                 // the target to read it's source value from that variable.
233                                 source = temp;
234                         }
235
236                         // If we have an embedded assignment, use the embedded assignment's temporary
237                         // local variable as source.
238                         if (embedded != null)
239                                 source = (embedded.temp != null) ? embedded.temp : embedded.source;
240
241                         target = target.ResolveLValue (ec, source);
242
243                         if (target == null)
244                                 return null;
245
246                         Type target_type = target.Type;
247                         Type source_type = real_source.Type;
248
249                         // If we're an embedded assignment, our parent will reuse our source as its
250                         // source, it won't read from our target.
251                         if (is_embedded)
252                                 type = source_type;
253                         else
254                                 type = target_type;
255                         eclass = ExprClass.Value;
256
257                         if (target is EventExpr) {
258                                 EventInfo ei = ((EventExpr) target).EventInfo;
259
260                                 Expression ml = MemberLookup (
261                                         ec, ec.ContainerType, ei.Name,
262                                         MemberTypes.Event, AllBindingFlags | BindingFlags.DeclaredOnly, loc);
263
264                                 if (ml == null) {
265                                         //
266                                         // If this is the case, then the Event does not belong 
267                                         // to this Type and so, according to the spec
268                                         // is allowed to only appear on the left hand of
269                                         // the += and -= operators
270                                         //
271                                         // Note that target will not appear as an EventExpr
272                                         // in the case it is being referenced within the same type container;
273                                         // it will appear as a FieldExpr in that case.
274                                         //
275                                         
276                                         if (!(source is BinaryDelegate)) {
277                                                 error70 (ei, loc);
278                                                 return null;
279                                         } 
280                                 }
281                         }
282                         
283                         if (!(target is IAssignMethod) && (target.eclass != ExprClass.EventAccess)) {
284                                 Report.Error (131, loc,
285                                               "Left hand of an assignment must be a variable, " +
286                                               "a property or an indexer");
287                                 return null;
288                         }
289
290                         if ((source.eclass == ExprClass.Type) && (source is TypeExpr)) {
291                                 source.Error_UnexpectedKind ("variable or value");
292                                 return null;
293                         } else if (!RootContext.V2 && (source is MethodGroupExpr)){
294                                 ((MethodGroupExpr) source).ReportUsageError ();
295                                 return null;
296
297                         }
298                         
299                         if (target_type == source_type){
300                                 if (source is New && target_type.IsValueType &&
301                                     (target.eclass != ExprClass.IndexerAccess) && (target.eclass != ExprClass.PropertyAccess)){
302                                         New n = (New) source;
303                                         
304                                         if (n.SetValueTypeVariable (target))
305                                                 return n;
306                                         else
307                                                 return null;
308                                 }
309                                 
310                                 return this;
311                         }
312                         
313                         //
314                         // If this assignemnt/operator was part of a compound binary
315                         // operator, then we allow an explicit conversion, as detailed
316                         // in the spec. 
317                         //
318
319                         if (this is CompoundAssign){
320                                 CompoundAssign a = (CompoundAssign) this;
321                                 
322                                 Binary b = source as Binary;
323                                 if (b != null){
324                                         //
325                                         // 1. if the source is explicitly convertible to the
326                                         //    target_type
327                                         //
328                                         
329                                         source = Convert.ExplicitConversion (ec, source, target_type, loc);
330                                         if (source == null){
331                                                 Convert.Error_CannotImplicitConversion (loc, source_type, target_type);
332                                                 return null;
333                                         }
334                                 
335                                         //
336                                         // 2. and the original right side is implicitly convertible to
337                                         // the type of target
338                                         //
339                                         if (Convert.ImplicitStandardConversionExists (a.original_source, target_type))
340                                                 return this;
341
342                                         //
343                                         // In the spec 2.4 they added: or if type of the target is int
344                                         // and the operator is a shift operator...
345                                         //
346                                         if (source_type == TypeManager.int32_type &&
347                                             (b.Oper == Binary.Operator.LeftShift || b.Oper == Binary.Operator.RightShift))
348                                                 return this;
349
350                                         Convert.Error_CannotImplicitConversion (loc, a.original_source.Type, target_type);
351                                         return null;
352                                 }
353                         }
354
355                         source = Convert.ImplicitConversionRequired (ec, source, target_type, loc);
356                         if (source == null)
357                                 return null;
358
359                         // If we're an embedded assignment, we need to create a new temporary variable
360                         // for the converted value.  Our parent will use this new variable as its source.
361                         // The same applies when we have an embedded assignment - in this case, we need
362                         // to convert our embedded assignment's temporary local variable to the correct
363                         // type and store it in a new temporary local.
364                         if (is_embedded || embedded != null) {
365                                 type = target_type;
366                                 temp = new LocalTemporary (ec, type);
367                                 must_free_temp = true;
368                         }
369                         
370                         return this;
371                 }
372
373                 Expression EmitEmbedded (EmitContext ec)
374                 {
375                         // Emit an embedded assignment.
376
377                         if (real_temp != null) {
378                                 // If we're the innermost assignment, `real_source' is the right-hand
379                                 // expression which gets assigned to all the variables left of it.
380                                 // Emit this expression and store its result in real_temp.
381                                 real_source.Emit (ec);
382                                 real_temp.Store (ec);
383                         }
384
385                         if (embedded != null)
386                                 embedded.EmitEmbedded (ec);
387
388                         // This happens when we've done a type conversion, in this case source will be
389                         // the expression which does the type conversion from real_temp.
390                         // So emit it and store the result in temp; this is the var which will be read
391                         // by our parent.
392                         if (temp != real_temp) {
393                                 source.Emit (ec);
394                                 temp.Store (ec);
395                         }
396
397                         Expression temp_source = (temp != null) ? temp : source;
398                         ((IAssignMethod) target).EmitAssign (ec, temp_source);
399                         return temp_source;
400                 }
401
402                 void ReleaseEmbedded (EmitContext ec)
403                 {
404                         if (embedded != null)
405                                 embedded.ReleaseEmbedded (ec);
406
407                         if (real_temp != null)
408                                 real_temp.Release (ec);
409
410                         if (must_free_temp)
411                                 temp.Release (ec);
412                 }
413
414                 void Emit (EmitContext ec, bool is_statement)
415                 {
416                         if (target is EventExpr) {
417                                 ((EventExpr) target).EmitAddOrRemove (ec, source);
418                                 return;
419                         }
420
421                         bool use_temporaries = false;
422                         
423                         //
424                         // FIXME! We need a way to "probe" if the process can
425                         // just use `dup' to propagate the result
426                         // 
427                         IAssignMethod am = (IAssignMethod) target;
428
429                         if (this is CompoundAssign)
430                                 am.CacheTemporaries (ec);
431
432                         if (!is_statement)
433                                 use_temporaries = true;
434
435                         Expression temp_source;
436                         if (embedded != null) {
437                                 temp_source = embedded.EmitEmbedded (ec);
438
439                                 if (temp != null) {
440                                         source.Emit (ec);
441                                         temp.Store (ec);
442                                         temp_source = temp;
443                                 }
444                         } else
445                                 temp_source = source;
446
447                         if (use_temporaries){
448                                 //
449                                 // Doing this for every path is too expensive
450                                 // I wonder if we can work around this and have a less
451                                 // expensive path
452                                 //
453                                 LocalTemporary tempo;
454                                 
455                                 tempo = new LocalTemporary (ec, source.Type);
456                                 
457                                 temp_source.Emit (ec);
458                                 tempo.Store (ec);
459                                 am.EmitAssign (ec, tempo);
460                                 if (!is_statement)
461                                         tempo.Emit (ec);
462                                 
463                                 tempo.Release (ec);
464                         } else {
465                                 am.EmitAssign (ec, temp_source);
466                         }
467                                 
468                         if (embedded != null) {
469                                 if (temp != null)
470                                         temp.Release (ec);
471                                 embedded.ReleaseEmbedded (ec);
472                         }
473                 }
474                 
475                 public override void Emit (EmitContext ec)
476                 {
477                         Emit (ec, false);
478                 }
479
480                 public override void EmitStatement (EmitContext ec)
481                 {
482                         Emit (ec, true);
483                 }
484         }
485
486         
487         //
488         // This class is used for compound assignments.  
489         //
490         class CompoundAssign : Assign {
491                 Binary.Operator op;
492                 public Expression original_source;
493                 
494                 public CompoundAssign (Binary.Operator op, Expression target, Expression source, Location l)
495                         : base (target, source, l)
496                 {
497                         original_source = source;
498                         this.op = op;
499                 }
500
501                 protected CompoundAssign (CompoundAssign embedded, Location l)
502                         : this (embedded.op, embedded.target, embedded.source, l)
503                 {
504                         this.is_embedded = true;
505                 }
506
507                 protected override Assign GetEmbeddedAssign (Location loc)
508                 {
509                         return new CompoundAssign (this, loc);
510                 }
511
512                 public Expression ResolveSource (EmitContext ec)
513                 {
514                         return original_source.Resolve (ec);
515                 }
516
517                 public override Expression DoResolve (EmitContext ec)
518                 {
519                         original_source = original_source.Resolve (ec);
520                         if (original_source == null)
521                                 return null;
522
523                         target = target.Resolve (ec);
524                         if (target == null)
525                                 return null;
526                         
527                         //
528                         // Only now we can decouple the original source/target
529                         // into a tree, to guarantee that we do not have side
530                         // effects.
531                         //
532                         source = new Binary (op, target, original_source, loc);
533                         return base.DoResolve (ec);
534                 }
535         }
536 }
537
538
539
540