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