A few minor fixes
[mono.git] / mcs / mbas / assign.cs
1 //
2 // assign.cs: Assignments.
3 //
4 // Authors:
5 //   Miguel de Icaza (miguel@ximian.com)
6 //   Martin Baulig (martin@gnome.org)
7 //
8 // (C) 2001, 2002 Ximian, Inc.
9 //
10 // This program is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU General Public License
12 // as published by the Free Software Foundation; either version 2
13 // of the License, or (at your option) any later version.
14 //
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
24 using System;
25 using System.Reflection;
26 using System.Reflection.Emit;
27
28 namespace Mono.CSharp {
29
30         /// <summary>
31         ///   This interface is implemented by expressions that can be assigned to.
32         /// </summary>
33         /// <remarks>
34         ///   This interface is implemented by Expressions whose values can not
35         ///   store the result on the top of the stack.
36         ///
37         ///   Expressions implementing this (Properties, Indexers and Arrays) would
38         ///   perform an assignment of the Expression "source" into its final
39         ///   location.
40         ///
41         ///   No values on the top of the stack are expected to be left by
42         ///   invoking this method.
43         /// </remarks>
44         public interface IAssignMethod {
45                 //
46                 // This method will emit the code for the actual assignment
47                 //
48                 void EmitAssign (EmitContext ec, Expression source);
49
50                 //
51                 // This method is invoked before any code generation takes
52                 // place, and it is a mechanism to inform that the expression
53                 // will be invoked more than once, and that the method should
54                 // use temporary values to avoid having side effects
55                 //
56                 // Example: a [ g () ] ++
57                 //
58                 void CacheTemporaries (EmitContext ec);
59         }
60
61         /// <summary>
62         ///   An Expression to hold a temporary value.
63         /// </summary>
64         /// <remarks>
65         ///   The LocalTemporary class is used to hold temporary values of a given
66         ///   type to "simulate" the expression semantics on property and indexer
67         ///   access whose return values are void.
68         ///
69         ///   The local temporary is used to alter the normal flow of code generation
70         ///   basically it creates a local variable, and its emit instruction generates
71         ///   code to access this value, return its address or save its value.
72         /// </remarks>
73         public class LocalTemporary : Expression, IMemoryLocation {
74                 LocalBuilder builder;
75                 
76                 public LocalTemporary (EmitContext ec, Type t)
77                 {
78                         type = t;
79                         eclass = ExprClass.Value;
80                         loc = Location.Null;
81                         builder = ec.GetTemporaryStorage (t);
82                 }
83
84                 public void Release (EmitContext ec)
85                 {
86                         ec.FreeTemporaryStorage (builder);
87                         builder = null;
88                 }
89                 
90                 public LocalTemporary (LocalBuilder b, Type t)
91                 {
92                         type = t;
93                         eclass = ExprClass.Value;
94                         loc = Location.Null;
95                         builder = b;
96                 }
97                 
98                 public override Expression DoResolve (EmitContext ec)
99                 {
100                         return this;
101                 }
102
103                 public override void Emit (EmitContext ec)
104                 {
105                         ec.ig.Emit (OpCodes.Ldloc, builder); 
106                 }
107
108                 public void Store (EmitContext ec)
109                 {
110                         ec.ig.Emit (OpCodes.Stloc, builder);
111                 }
112
113                 public void AddressOf (EmitContext ec, AddressOp mode)
114                 {
115                         ec.ig.Emit (OpCodes.Ldloca, builder);
116                 }
117         }
118
119         /// <summary>
120         ///   The Assign node takes care of assigning the value of source into
121         ///   the expression represented by target. 
122         /// </summary>
123         public class Assign : ExpressionStatement {
124                 protected Expression target, source, real_source;
125                 protected LocalTemporary temp = null, real_temp = null;
126                 protected Assign embedded = null;
127                 protected bool is_embedded = false;
128                 protected bool must_free_temp = false;
129
130                 public Assign (Expression target, Expression source, Location l)
131                 {
132                         this.target = target;
133                         this.source = this.real_source = source;
134                         this.loc = l;
135                 }
136
137                 protected Assign (Assign embedded, Location l)
138                         : this (embedded.target, embedded.source, l)
139                 {
140                         this.is_embedded = true;
141                 }
142
143                 public Expression Target {
144                         get {
145                                 return target;
146                         }
147
148                         set {
149                                 target = value;
150                         }
151                 }
152
153                 public Expression Source {
154                         get {
155                                 return source;
156                         }
157
158                         set {
159                                 source = value;
160                         }
161                 }
162
163                 public static void error70 (EventInfo ei, Location l)
164                 {
165                         Report.Error (70, l, "The event '" + ei.Name +
166                                       "' can only appear on the left-side of a += or -= (except when" +
167                                       " used from within the type '" + ei.DeclaringType + "')");
168                 }
169
170                 //
171                 // Will return either `this' or an instance of `New'.
172                 //
173                 public override Expression DoResolve (EmitContext ec)
174                 {
175                         // Create an embedded assignment if our source is an assignment.
176                         if (source is Assign)
177                                 source = embedded = new Assign ((Assign) source, loc);
178
179                         real_source = source = source.Resolve (ec);
180                         if (source == null)
181                                 return null;
182
183                         //
184                         // This is used in an embedded assignment.
185                         // As an example, consider the statement "A = X = Y = Z".
186                         //
187                         if (is_embedded && !(source is Constant)) {
188                                 // If this is the innermost assignment (the "Y = Z" in our example),
189                                 // create a new temporary local, otherwise inherit that variable
190                                 // from our child (the "X = (Y = Z)" inherits the local from the
191                                 // "Y = Z" assignment).
192                                 if (embedded == null)
193                                         real_temp = temp = new LocalTemporary (ec, source.Type);
194                                 else
195                                         temp = embedded.temp;
196
197                                 // Set the source to the new temporary variable.
198                                 // This means that the following target.ResolveLValue () will tell
199                                 // the target to read it's source value from that variable.
200                                 source = temp;
201                         }
202
203                         // If we have an embedded assignment, use the embedded assignment's temporary
204                         // local variable as source.
205                         if (embedded != null)
206                                 source = (embedded.temp != null) ? embedded.temp : embedded.source;
207
208                         target = target.ResolveLValue (ec, source);
209
210                         if (target == null)
211                                 return null;
212
213                         Type target_type = target.Type;
214                         Type source_type = real_source.Type;
215
216                         // If we're an embedded assignment, our parent will reuse our source as its
217                         // source, it won't read from our target.
218                         if (is_embedded)
219                                 type = source_type;
220                         else
221                                 type = target_type;
222                         eclass = ExprClass.Value;
223
224                         //
225                         // If we are doing a property assignment, then
226                         // set the `value' field on the property, and Resolve
227                         // it.
228                         //
229                         if (target is PropertyExpr){
230                                 PropertyExpr property_assign = (PropertyExpr) target;
231
232                                 if (source_type != target_type){
233                                         source = ConvertImplicitRequired (ec, source, target_type, loc);
234                                         if (source == null)
235                                                 return null;
236                                 }
237
238                                 //
239                                 // FIXME: Maybe handle this in the LValueResolve
240                                 //
241                                 if (!property_assign.VerifyAssignable ())
242                                         return null;
243
244                                 return this;
245                         }
246
247                         if (target is IndexerAccess) {
248                                 return this;
249                         }
250
251                         if (target is EventExpr) {
252                                 EventInfo ei = ((EventExpr) target).EventInfo;
253
254                                 Expression ml = MemberLookup (
255                                         ec, ec.ContainerType, ei.Name,
256                                         MemberTypes.Event, AllBindingFlags | BindingFlags.DeclaredOnly, loc);
257
258                                 if (ml == null) {
259                                         //
260                                         // If this is the case, then the Event does not belong 
261                                         // to this Type and so, according to the spec
262                                         // is allowed to only appear on the left hand of
263                                         // the += and -= operators
264                                         //
265                                         // Note that target will not appear as an EventExpr
266                                         // in the case it is being referenced within the same type container;
267                                         // it will appear as a FieldExpr in that case.
268                                         //
269                                         
270                                         if (!(source is Binary)) {
271                                                 error70 (ei, loc);
272                                                 return null;
273                                         } else {
274                                                 Binary tmp = ((Binary) source);
275                                                 if (tmp.Oper != Binary.Operator.Addition &&
276                                                     tmp.Oper != Binary.Operator.Subtraction) {
277                                                         error70 (ei, loc);
278                                                         return null;
279                                                 }
280                                         }
281                                 }
282                         }
283                         
284                         if (source is New && target_type.IsValueType){
285                                 New n = (New) source;
286
287                                 n.ValueTypeVariable = target;
288                                 return n;
289                         }
290
291                         if (target.eclass != ExprClass.Variable && target.eclass != ExprClass.EventAccess){
292                                 Report.Error (131, loc,
293                                               "Left hand of an assignment must be a variable, " +
294                                               "a property or an indexer");
295                                 return null;
296                         }
297
298                         if ((source.eclass == ExprClass.Type) && (source is TypeExpr)) {
299                                 source.Error118 ("variable or value");
300                                 return null;
301                         } else if (source is MethodGroupExpr){
302                                 ((MethodGroupExpr) source).ReportUsageError ();
303                                 return null;
304                         }
305
306                         if (target_type == source_type)
307                                 return this;
308                         
309                         //
310                         // If this assignemnt/operator was part of a compound binary
311                         // operator, then we allow an explicit conversion, as detailed
312                         // in the spec. 
313                         //
314
315                         if (this is CompoundAssign){
316                                 CompoundAssign a = (CompoundAssign) this;
317                                 
318                                 Binary b = source as Binary;
319                                 if (b != null && b.IsBuiltinOperator){
320                                         //
321                                         // 1. if the source is explicitly convertible to the
322                                         //    target_type
323                                         //
324                                         
325                                         source = ConvertExplicit (ec, source, target_type, loc);
326                                         if (source == null){
327                                                 Error_CannotConvertImplicit (loc, source_type, target_type);
328                                                 return null;
329                                         }
330                                 
331                                         //
332                                         // 2. and the original right side is implicitly convertible to
333                                         // the type of target_type.
334                                         //
335                                         if (StandardConversionExists (a.original_source, target_type))
336                                                 return this;
337
338                                         Error_CannotConvertImplicit (loc, a.original_source.Type, target_type);
339                                         return null;
340                                 }
341                         }
342                         
343                         source = ConvertImplicitRequired (ec, source, target_type, loc);
344                         if (source == null)
345                                 return null;
346
347                         // If we're an embedded assignment, we need to create a new temporary variable
348                         // for the converted value.  Our parent will use this new variable as its source.
349                         // The same applies when we have an embedded assignment - in this case, we need
350                         // to convert our embedded assignment's temporary local variable to the correct
351                         // type and store it in a new temporary local.
352                         if (is_embedded || embedded != null) {
353                                 type = target_type;
354                                 temp = new LocalTemporary (ec, type);
355                                 must_free_temp = true;
356                         }
357                         
358                         return this;
359                 }
360
361                 Expression EmitEmbedded (EmitContext ec)
362                 {
363                         // Emit an embedded assignment.
364                         
365                         if (real_temp != null) {
366                                 // If we're the innermost assignment, `real_source' is the right-hand
367                                 // expression which gets assigned to all the variables left of it.
368                                 // Emit this expression and store its result in real_temp.
369                                 real_source.Emit (ec);
370                                 real_temp.Store (ec);
371                         }
372
373                         if (embedded != null)
374                                 embedded.EmitEmbedded (ec);
375
376                         // This happens when we've done a type conversion, in this case source will be
377                         // the expression which does the type conversion from real_temp.
378                         // So emit it and store the result in temp; this is the var which will be read
379                         // by our parent.
380                         if (temp != real_temp) {
381                                 source.Emit (ec);
382                                 temp.Store (ec);
383                         }
384
385                         Expression temp_source = (temp != null) ? temp : source;
386                         ((IAssignMethod) target).EmitAssign (ec, temp_source);
387                         return temp_source;
388                 }
389
390                 void ReleaseEmbedded (EmitContext ec)
391                 {
392                         if (embedded != null)
393                                 embedded.ReleaseEmbedded (ec);
394
395                         if (real_temp != null)
396                                 real_temp.Release (ec);
397
398                         if (must_free_temp)
399                                 temp.Release (ec);
400                 }
401
402                 void Emit (EmitContext ec, bool is_statement)
403                 {
404                         if (target is EventExpr) {
405                                 ((EventExpr) target).EmitAddOrRemove (ec, source);
406                                 return;
407                         }
408
409                         //
410                         // FIXME! We need a way to "probe" if the process can
411                         // just use `dup' to propagate the result
412                         // 
413                         IAssignMethod am = (IAssignMethod) target;
414
415                         if (this is CompoundAssign){
416                                 am.CacheTemporaries (ec);
417                         }
418
419                         Expression temp_source;
420                         if (embedded != null) {
421                                 temp_source = embedded.EmitEmbedded (ec);
422
423                                 if (temp != null) {
424                                         source.Emit (ec);
425                                         temp.Store (ec);
426                                         temp_source = temp;
427                                 }
428                         } else
429                                 temp_source = source;
430
431                         if (is_statement)
432                                 am.EmitAssign (ec, temp_source);
433                         else {
434                                 LocalTemporary tempo;
435
436                                 tempo = new LocalTemporary (ec, source.Type);
437
438                                 temp_source.Emit (ec);
439                                 tempo.Store (ec);
440                                 am.EmitAssign (ec, tempo);
441                                 tempo.Emit (ec);
442                                 tempo.Release (ec);
443                         }
444
445                         if (embedded != null) {
446                                 if (temp != null)
447                                         temp.Release (ec);
448                                 embedded.ReleaseEmbedded (ec);
449                         }
450                 }
451                 
452                 public override void Emit (EmitContext ec)
453                 {
454                         Emit (ec, false);
455                 }
456
457                 public override void EmitStatement (EmitContext ec)
458                 {
459                         Emit (ec, true);
460                 }
461         }
462
463         
464         //
465         // This class is used for compound assignments.  
466         //
467         class CompoundAssign : Assign {
468                 Binary.Operator op;
469                 public Expression original_source;
470                 
471                 public CompoundAssign (Binary.Operator op, Expression target, Expression source, Location l)
472                         : base (target, source, l)
473                 {
474                         original_source = source;
475                         this.op = op;
476                 }
477
478                 public Expression ResolveSource (EmitContext ec)
479                 {
480                         return original_source.Resolve (ec);
481                 }
482
483                 public override Expression DoResolve (EmitContext ec)
484                 {
485                         target = target.ResolveLValue (ec, source);
486                         if (target == null)
487                                 return null;
488
489                         original_source = original_source.Resolve (ec);
490                         if (original_source == null)
491                                 return null;
492
493                         //
494                         // Only now we can decouple the original source/target
495                         // into a tree, to guarantee that we do not have side
496                         // effects.
497                         //
498                         source = new Binary (op, target, original_source, loc);
499                         return base.DoResolve (ec);
500                 }
501         }
502 }
503
504
505
506