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