2002-10-19 Miguel de Icaza <miguel@ximian.com>
[mono.git] / mcs / mbas / 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 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.GetTemporaryStorage (t);
68                 }
69
70                 public void Release (EmitContext ec)
71                 {
72                         ec.FreeTemporaryStorage (builder);
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                 public Expression Target {
130                         get {
131                                 return target;
132                         }
133
134                         set {
135                                 target = value;
136                         }
137                 }
138
139                 public Expression Source {
140                         get {
141                                 return source;
142                         }
143
144                         set {
145                                 source = value;
146                         }
147                 }
148
149                 public static void error70 (EventInfo ei, Location l)
150                 {
151                         Report.Error (70, l, "The event '" + ei.Name +
152                                       "' can only appear on the left-side of a += or -= (except when" +
153                                       " used from within the type '" + ei.DeclaringType + "')");
154                 }
155
156                 //
157                 // Will return either `this' or an instance of `New'.
158                 //
159                 public override Expression DoResolve (EmitContext ec)
160                 {
161                         // Create an embedded assignment if our source is an assignment.
162                         if (source is Assign)
163                                 source = embedded = new Assign ((Assign) source, loc);
164
165                         real_source = source = source.Resolve (ec);
166                         if (source == null)
167                                 return null;
168
169                         //
170                         // This is used in an embedded assignment.
171                         // As an example, consider the statement "A = X = Y = Z".
172                         //
173                         if (is_embedded && !(source is Constant)) {
174                                 // If this is the innermost assignment (the "Y = Z" in our example),
175                                 // create a new temporary local, otherwise inherit that variable
176                                 // from our child (the "X = (Y = Z)" inherits the local from the
177                                 // "Y = Z" assignment).
178                                 if (embedded == null)
179                                         real_temp = temp = new LocalTemporary (ec, source.Type);
180                                 else
181                                         temp = embedded.temp;
182
183                                 // Set the source to the new temporary variable.
184                                 // This means that the following target.ResolveLValue () will tell
185                                 // the target to read it's source value from that variable.
186                                 source = temp;
187                         }
188
189                         // If we have an embedded assignment, use the embedded assignment's temporary
190                         // local variable as source.
191                         if (embedded != null)
192                                 source = (embedded.temp != null) ? embedded.temp : embedded.source;
193
194                         target = target.ResolveLValue (ec, source);
195
196                         if (target == null)
197                                 return null;
198
199                         Type target_type = target.Type;
200                         Type source_type = real_source.Type;
201
202                         // If we're an embedded assignment, our parent will reuse our source as its
203                         // source, it won't read from our target.
204                         if (is_embedded)
205                                 type = source_type;
206                         else
207                                 type = target_type;
208                         eclass = ExprClass.Value;
209
210                         //
211                         // If we are doing a property assignment, then
212                         // set the `value' field on the property, and Resolve
213                         // it.
214                         //
215                         if (target is PropertyExpr){
216                                 PropertyExpr property_assign = (PropertyExpr) target;
217
218                                 if (source_type != target_type){
219                                         source = ConvertImplicitRequired (ec, source, target_type, loc);
220                                         if (source == null)
221                                                 return null;
222                                 }
223
224                                 //
225                                 // FIXME: Maybe handle this in the LValueResolve
226                                 //
227                                 if (!property_assign.VerifyAssignable ())
228                                         return null;
229
230                                 return this;
231                         }
232
233                         if (target is IndexerAccess) {
234                                 return this;
235                         }
236
237                         if (target is EventExpr) {
238                                 EventInfo ei = ((EventExpr) target).EventInfo;
239
240                                 Expression ml = MemberLookup (
241                                         ec, ec.ContainerType, ei.Name,
242                                         MemberTypes.Event, AllBindingFlags | BindingFlags.DeclaredOnly, loc);
243
244                                 if (ml == null) {
245                                         //
246                                         // If this is the case, then the Event does not belong 
247                                         // to this Type and so, according to the spec
248                                         // is allowed to only appear on the left hand of
249                                         // the += and -= operators
250                                         //
251                                         // Note that target will not appear as an EventExpr
252                                         // in the case it is being referenced within the same type container;
253                                         // it will appear as a FieldExpr in that case.
254                                         //
255                                         
256                                         if (!(source is Binary)) {
257                                                 error70 (ei, loc);
258                                                 return null;
259                                         } else {
260                                                 Binary tmp = ((Binary) source);
261                                                 if (tmp.Oper != Binary.Operator.Addition &&
262                                                     tmp.Oper != Binary.Operator.Subtraction) {
263                                                         error70 (ei, loc);
264                                                         return null;
265                                                 }
266                                         }
267                                 }
268                         }
269                         
270                         if (source is New && target_type.IsValueType){
271                                 New n = (New) source;
272
273                                 n.ValueTypeVariable = target;
274                                 return n;
275                         }
276
277                         if (target.eclass != ExprClass.Variable && target.eclass != ExprClass.EventAccess){
278                                 Report.Error (131, loc,
279                                               "Left hand of an assignment must be a variable, " +
280                                               "a property or an indexer");
281                                 return null;
282                         }
283
284                         if ((source.eclass == ExprClass.Type) && (source is TypeExpr)) {
285                                 source.Error118 ("variable or value");
286                                 return null;
287                         } else if (source is MethodGroupExpr){
288                                 ((MethodGroupExpr) source).ReportUsageError ();
289                                 return null;
290                         }
291
292                         if (target_type == source_type)
293                                 return this;
294                         
295                         //
296                         // If this assignemnt/operator was part of a compound binary
297                         // operator, then we allow an explicit conversion, as detailed
298                         // in the spec. 
299                         //
300
301                         if (this is CompoundAssign){
302                                 CompoundAssign a = (CompoundAssign) this;
303                                 
304                                 Binary b = source as Binary;
305                                 if (b != null && b.IsBuiltinOperator){
306                                         //
307                                         // 1. if the source is explicitly convertible to the
308                                         //    target_type
309                                         //
310                                         
311                                         source = ConvertExplicit (ec, source, target_type, loc);
312                                         if (source == null){
313                                                 Error_CannotConvertImplicit (loc, source_type, target_type);
314                                                 return null;
315                                         }
316                                 
317                                         //
318                                         // 2. and the original right side is implicitly convertible to
319                                         // the type of target_type.
320                                         //
321                                         if (StandardConversionExists (a.original_source, target_type))
322                                                 return this;
323
324                                         Error_CannotConvertImplicit (loc, a.original_source.Type, target_type);
325                                         return null;
326                                 }
327                         }
328                         
329                         source = ConvertImplicitRequired (ec, source, target_type, loc);
330                         if (source == null)
331                                 return null;
332
333                         // If we're an embedded assignment, we need to create a new temporary variable
334                         // for the converted value.  Our parent will use this new variable as its source.
335                         // The same applies when we have an embedded assignment - in this case, we need
336                         // to convert our embedded assignment's temporary local variable to the correct
337                         // type and store it in a new temporary local.
338                         if (is_embedded || embedded != null) {
339                                 type = target_type;
340                                 temp = new LocalTemporary (ec, type);
341                                 must_free_temp = true;
342                         }
343                         
344                         return this;
345                 }
346
347                 Expression EmitEmbedded (EmitContext ec)
348                 {
349                         // Emit an embedded assignment.
350                         
351                         if (real_temp != null) {
352                                 // If we're the innermost assignment, `real_source' is the right-hand
353                                 // expression which gets assigned to all the variables left of it.
354                                 // Emit this expression and store its result in real_temp.
355                                 real_source.Emit (ec);
356                                 real_temp.Store (ec);
357                         }
358
359                         if (embedded != null)
360                                 embedded.EmitEmbedded (ec);
361
362                         // This happens when we've done a type conversion, in this case source will be
363                         // the expression which does the type conversion from real_temp.
364                         // So emit it and store the result in temp; this is the var which will be read
365                         // by our parent.
366                         if (temp != real_temp) {
367                                 source.Emit (ec);
368                                 temp.Store (ec);
369                         }
370
371                         Expression temp_source = (temp != null) ? temp : source;
372                         ((IAssignMethod) target).EmitAssign (ec, temp_source);
373                         return temp_source;
374                 }
375
376                 void ReleaseEmbedded (EmitContext ec)
377                 {
378                         if (embedded != null)
379                                 embedded.ReleaseEmbedded (ec);
380
381                         if (real_temp != null)
382                                 real_temp.Release (ec);
383
384                         if (must_free_temp)
385                                 temp.Release (ec);
386                 }
387
388                 void Emit (EmitContext ec, bool is_statement)
389                 {
390                         if (target is EventExpr) {
391                                 ((EventExpr) target).EmitAddOrRemove (ec, source);
392                                 return;
393                         }
394
395                         //
396                         // FIXME! We need a way to "probe" if the process can
397                         // just use `dup' to propagate the result
398                         // 
399                         IAssignMethod am = (IAssignMethod) target;
400
401                         if (this is CompoundAssign){
402                                 am.CacheTemporaries (ec);
403                         }
404
405                         Expression temp_source;
406                         if (embedded != null) {
407                                 temp_source = embedded.EmitEmbedded (ec);
408
409                                 if (temp != null) {
410                                         source.Emit (ec);
411                                         temp.Store (ec);
412                                         temp_source = temp;
413                                 }
414                         } else
415                                 temp_source = source;
416
417                         if (is_statement)
418                                 am.EmitAssign (ec, temp_source);
419                         else {
420                                 LocalTemporary tempo;
421
422                                 tempo = new LocalTemporary (ec, source.Type);
423
424                                 temp_source.Emit (ec);
425                                 tempo.Store (ec);
426                                 am.EmitAssign (ec, tempo);
427                                 tempo.Emit (ec);
428                                 tempo.Release (ec);
429                         }
430
431                         if (embedded != null) {
432                                 if (temp != null)
433                                         temp.Release (ec);
434                                 embedded.ReleaseEmbedded (ec);
435                         }
436                 }
437                 
438                 public override void Emit (EmitContext ec)
439                 {
440                         Emit (ec, false);
441                 }
442
443                 public override void EmitStatement (EmitContext ec)
444                 {
445                         Emit (ec, true);
446                 }
447         }
448
449         
450         //
451         // This class is used for compound assignments.  
452         //
453         class CompoundAssign : Assign {
454                 Binary.Operator op;
455                 public Expression original_source;
456                 
457                 public CompoundAssign (Binary.Operator op, Expression target, Expression source, Location l)
458                         : base (target, source, l)
459                 {
460                         original_source = source;
461                         this.op = op;
462                 }
463
464                 public Expression ResolveSource (EmitContext ec)
465                 {
466                         return original_source.Resolve (ec);
467                 }
468
469                 public override Expression DoResolve (EmitContext ec)
470                 {
471                         target = target.ResolveLValue (ec, source);
472                         if (target == null)
473                                 return null;
474
475                         original_source = original_source.Resolve (ec);
476                         if (original_source == null)
477                                 return null;
478
479                         //
480                         // Only now we can decouple the original source/target
481                         // into a tree, to guarantee that we do not have side
482                         // effects.
483                         //
484                         source = new Binary (op, target, original_source, loc);
485                         return base.DoResolve (ec);
486                 }
487         }
488 }
489
490
491
492