new tests + update
[mono.git] / mcs / mcs / assign.cs
1 //
2 // assign.cs: Assignments.
3 //
4 // Author:
5 //   Miguel de Icaza (miguel@ximian.com)
6 //   Martin Baulig (martin@ximian.com)
7 //
8 // (C) 2001, 2002, 2003 Ximian, Inc.
9 // (C) 2004 Novell, Inc
10 //
11 using System;
12 using System.Reflection;
13 using System.Reflection.Emit;
14
15 namespace Mono.CSharp {
16
17         /// <summary>
18         ///   This interface is implemented by expressions that can be assigned to.
19         /// </summary>
20         /// <remarks>
21         ///   This interface is implemented by Expressions whose values can not
22         ///   store the result on the top of the stack.
23         ///
24         ///   Expressions implementing this (Properties, Indexers and Arrays) would
25         ///   perform an assignment of the Expression "source" into its final
26         ///   location.
27         ///
28         ///   No values on the top of the stack are expected to be left by
29         ///   invoking this method.
30         /// </remarks>
31         public interface IAssignMethod {
32                 //
33                 // This is an extra version of Emit. If leave_copy is `true'
34                 // A copy of the expression will be left on the stack at the
35                 // end of the code generated for EmitAssign
36                 //
37                 void Emit (EmitContext ec, bool leave_copy);
38                 
39                 //
40                 // This method does the assignment
41                 // `source' will be stored into the location specified by `this'
42                 // if `leave_copy' is true, a copy of `source' will be left on the stack
43                 // if `prepare_for_load' is true, when `source' is emitted, there will
44                 // be data on the stack that it can use to compuatate its value. This is
45                 // for expressions like a [f ()] ++, where you can't call `f ()' twice.
46                 //
47                 void EmitAssign (EmitContext ec, Expression source, bool leave_copy, bool prepare_for_load);
48                 
49                 /*
50                 For simple assignments, this interface is very simple, EmitAssign is called with source
51                 as the source expression and leave_copy and prepare_for_load false.
52                 
53                 For compound assignments it gets complicated.
54                 
55                 EmitAssign will be called as before, however, prepare_for_load will be
56                 true. The @source expression will contain an expression
57                 which calls Emit. So, the calls look like:
58                 
59                 this.EmitAssign (ec, source, false, true) ->
60                         source.Emit (ec); ->
61                                 [...] ->
62                                         this.Emit (ec, false); ->
63                                         end this.Emit (ec, false); ->
64                                 end [...]
65                         end source.Emit (ec);
66                 end this.EmitAssign (ec, source, false, true)
67                 
68                 
69                 When prepare_for_load is true, EmitAssign emits a `token' on the stack that
70                 Emit will use for its state.
71                 
72                 Let's take FieldExpr as an example. assume we are emitting f ().y += 1;
73                 
74                 Here is the call tree again. This time, each call is annotated with the IL
75                 it produces:
76                 
77                 this.EmitAssign (ec, source, false, true)
78                         call f
79                         dup
80                         
81                         Binary.Emit ()
82                                 this.Emit (ec, false);
83                                 ldfld y
84                                 end this.Emit (ec, false);
85                                 
86                                 IntConstant.Emit ()
87                                 ldc.i4.1
88                                 end IntConstant.Emit
89                                 
90                                 add
91                         end Binary.Emit ()
92                         
93                         stfld
94                 end this.EmitAssign (ec, source, false, true)
95                 
96                 Observe two things:
97                         1) EmitAssign left a token on the stack. It was the result of f ().
98                         2) This token was used by Emit
99                 
100                 leave_copy (in both EmitAssign and Emit) tells the compiler to leave a copy
101                 of the expression at that point in evaluation. This is used for pre/post inc/dec
102                 and for a = x += y. Let's do the above example with leave_copy true in EmitAssign
103                 
104                 this.EmitAssign (ec, source, true, true)
105                         call f
106                         dup
107                         
108                         Binary.Emit ()
109                                 this.Emit (ec, false);
110                                 ldfld y
111                                 end this.Emit (ec, false);
112                                 
113                                 IntConstant.Emit ()
114                                 ldc.i4.1
115                                 end IntConstant.Emit
116                                 
117                                 add
118                         end Binary.Emit ()
119                         
120                         dup
121                         stloc temp
122                         stfld
123                         ldloc temp
124                 end this.EmitAssign (ec, source, true, true)
125                 
126                 And with it true in Emit
127                 
128                 this.EmitAssign (ec, source, false, true)
129                         call f
130                         dup
131                         
132                         Binary.Emit ()
133                                 this.Emit (ec, true);
134                                 ldfld y
135                                 dup
136                                 stloc temp
137                                 end this.Emit (ec, true);
138                                 
139                                 IntConstant.Emit ()
140                                 ldc.i4.1
141                                 end IntConstant.Emit
142                                 
143                                 add
144                         end Binary.Emit ()
145                         
146                         stfld
147                         ldloc temp
148                 end this.EmitAssign (ec, source, false, true)
149                 
150                 Note that these two examples are what happens for ++x and x++, respectively.
151                 */
152         }
153
154         /// <summary>
155         ///   An Expression to hold a temporary value.
156         /// </summary>
157         /// <remarks>
158         ///   The LocalTemporary class is used to hold temporary values of a given
159         ///   type to "simulate" the expression semantics on property and indexer
160         ///   access whose return values are void.
161         ///
162         ///   The local temporary is used to alter the normal flow of code generation
163         ///   basically it creates a local variable, and its emit instruction generates
164         ///   code to access this value, return its address or save its value.
165         ///
166         ///   If `is_address' is true, then the value that we store is the address to the
167         ///   real value, and not the value itself. 
168         ///
169         ///   This is needed for a value type, because otherwise you just end up making a
170         ///   copy of the value on the stack and modifying it. You really need a pointer
171         ///   to the origional value so that you can modify it in that location. This
172         ///   Does not happen with a class because a class is a pointer -- so you always
173         ///   get the indirection.
174         ///
175         ///   The `is_address' stuff is really just a hack. We need to come up with a better
176         ///   way to handle it.
177         /// </remarks>
178         public class LocalTemporary : Expression, IMemoryLocation {
179                 LocalBuilder builder;
180                 bool is_address;
181                 
182                 public LocalTemporary (EmitContext ec, Type t) : this (ec, t, false) {}
183                         
184                 public LocalTemporary (EmitContext ec, Type t, bool is_address) 
185                 {
186                         type = t;
187                         eclass = ExprClass.Value;
188                         loc = Location.Null;
189                         builder = ec.GetTemporaryLocal (is_address ? TypeManager.GetReferenceType (t): t);
190                         this.is_address = is_address;
191                 }
192
193                 public LocalTemporary (LocalBuilder b, Type t)
194                 {
195                         type = t;
196                         eclass = ExprClass.Value;
197                         loc = Location.Null;
198                         builder = b;
199                 }
200
201                 public void Release (EmitContext ec)
202                 {
203                         ec.FreeTemporaryLocal (builder, type);
204                         builder = null;
205                 }
206                 
207                 public override Expression DoResolve (EmitContext ec)
208                 {
209                         return this;
210                 }
211
212                 public override void Emit (EmitContext ec)
213                 {
214                         ILGenerator ig = ec.ig;
215                         
216                         ig.Emit (OpCodes.Ldloc, builder);
217                         // we need to copy from the pointer
218                         if (is_address)
219                                 LoadFromPtr (ig, type);
220                 }
221
222                 // NB: if you have `is_address' on the stack there must
223                 // be a managed pointer. Otherwise, it is the type from
224                 // the ctor.
225                 public void Store (EmitContext ec)
226                 {
227                         ILGenerator ig = ec.ig;
228                         ig.Emit (OpCodes.Stloc, builder);
229                 }
230
231                 public void AddressOf (EmitContext ec, AddressOp mode)
232                 {
233                         // if is_address, than this is just the address anyways,
234                         // so we just return this.
235                         ILGenerator ig = ec.ig;
236                                 
237                         if (is_address)
238                                 ig.Emit (OpCodes.Ldloc, builder);
239                         else
240                                 ig.Emit (OpCodes.Ldloca, builder);
241                 }
242
243                 public bool PointsToAddress {
244                         get {
245                                 return is_address;
246                         }
247                 }
248         }
249
250         /// <summary>
251         ///   The Assign node takes care of assigning the value of source into
252         ///   the expression represented by target. 
253         /// </summary>
254         public class Assign : ExpressionStatement {
255                 protected Expression target, source, real_source;
256                 protected LocalTemporary temp = null, real_temp = null;
257                 protected Assign embedded = null;
258                 protected bool is_embedded = false;
259                 protected bool must_free_temp = false;
260
261                 public Assign (Expression target, Expression source)
262                         : this (target, source, target.Location)
263                 {
264                 }
265
266                 public Assign (Expression target, Expression source, Location l)
267                 {
268                         this.target = target;
269                         this.source = this.real_source = source;
270                         this.loc = l;
271                 }
272
273                 protected Assign (Assign embedded, Location l)
274                         : this (embedded.target, embedded.source, l)
275                 {
276                         this.is_embedded = true;
277                 }
278
279                 protected virtual Assign GetEmbeddedAssign (Location loc)
280                 {
281                         return new Assign (this, loc);
282                 }
283
284                 public Expression Target {
285                         get {
286                                 return target;
287                         }
288
289                         set {
290                                 target = value;
291                         }
292                 }
293
294                 public Expression Source {
295                         get {
296                                 return source;
297                         }
298
299                         set {
300                                 source = value;
301                         }
302                 }
303
304                 public static void error70 (EventInfo ei, Location l)
305                 {
306                         Report.Error (70, l, "The event `" + TypeManager.CSharpSignature (ei) +
307                                       "' can only appear on the left hand side of += or -= (except when" +
308                                       " used from within the type `" + ei.DeclaringType + "')");
309                 }
310
311                 //
312                 // Will return either `this' or an instance of `New'.
313                 //
314                 public override Expression DoResolve (EmitContext ec)
315                 {
316                         // Create an embedded assignment if our source is an assignment.
317                         if (source is Assign)
318                                 source = embedded = ((Assign) source).GetEmbeddedAssign (loc);
319
320                         real_source = source = source.Resolve (ec);
321                         if (source == null) {
322                                 // Ensure that we don't propagate the error as spurious "uninitialized variable" errors.
323                                 target = target.ResolveLValue (ec, EmptyExpression.Null, Location);
324                                 return null;
325                         }
326
327                         //
328                         // This is used in an embedded assignment.
329                         // As an example, consider the statement "A = X = Y = Z".
330                         //
331                         if (is_embedded && !(source is Constant)) {
332                                 // If this is the innermost assignment (the "Y = Z" in our example),
333                                 // create a new temporary local, otherwise inherit that variable
334                                 // from our child (the "X = (Y = Z)" inherits the local from the
335                                 // "Y = Z" assignment).
336
337                                 if (embedded == null) {
338                                         if (this is CompoundAssign)
339                                                 real_temp = temp = new LocalTemporary (ec, target.Type);
340                                         else
341                                                 real_temp = temp = new LocalTemporary (ec, source.Type);
342                                 } else
343                                         temp = embedded.temp;
344
345                                 // Set the source to the new temporary variable.
346                                 // This means that the following target.ResolveLValue () will tell
347                                 // the target to read it's source value from that variable.
348                                 source = temp;
349                         }
350
351                         // If we have an embedded assignment, use the embedded assignment's temporary
352                         // local variable as source.
353                         if (embedded != null)
354                                 source = (embedded.temp != null) ? embedded.temp : embedded.source;
355
356                         target = target.ResolveLValue (ec, source, Location);
357
358                         if (target == null)
359                                 return null;
360
361                         if (source.Equals (target)) {
362                                 Report.Warning (1717, 3, loc, "Assignment made to same variable; did you mean to assign something else?");
363                         }
364
365                         Type target_type = target.Type;
366                         Type source_type = real_source.Type;
367
368                         // If we're an embedded assignment, our parent will reuse our source as its
369                         // source, it won't read from our target.
370                         if (is_embedded)
371                                 type = source_type;
372                         else
373                                 type = target_type;
374                         eclass = ExprClass.Value;
375
376
377                         if (target is EventExpr) {
378                                 EventInfo ei = ((EventExpr) target).EventInfo;
379
380                                 Expression ml = MemberLookup (
381                                         ec, ec.ContainerType, ei.Name,
382                                         MemberTypes.Event, AllBindingFlags | BindingFlags.DeclaredOnly, loc);
383
384                                 if (ml == null) {
385                                         //
386                                         // If this is the case, then the Event does not belong 
387                                         // to this Type and so, according to the spec
388                                         // is allowed to only appear on the left hand of
389                                         // the += and -= operators
390                                         //
391                                         // Note that target will not appear as an EventExpr
392                                         // in the case it is being referenced within the same type container;
393                                         // it will appear as a FieldExpr in that case.
394                                         //
395                                         
396                                         if (!(source is BinaryDelegate)) {
397                                                 error70 (ei, loc);
398                                                 return null;
399                                         } 
400                                 }
401                         }
402                         
403                         FieldExpr field_exp = target as FieldExpr;
404                         if (field_exp != null && field_exp.DeclaringType.IsValueType && !ec.IsConstructor && !ec.IsFieldInitializer) {
405                                 field_exp = field_exp.InstanceExpression as FieldExpr;
406                                 if (field_exp != null && field_exp.FieldInfo.IsInitOnly) {
407                                         if (field_exp.IsStatic) {
408                                                 Report.Error (1650, loc, "Fields of static readonly field `{0}' cannot be assigned to (except in a static constructor or a variable initializer)",
409                                                         field_exp.GetSignatureForError ());
410                                         } else {
411                                                 Report.Error (1648, loc, "Members of readonly field `{0}' cannot be modified (except in a constructor or a variable initializer)",
412                                                         field_exp.GetSignatureForError ());
413                                         }
414                                         return null;
415                                 }
416                         }
417
418                         if (!(target is IAssignMethod) && (target.eclass != ExprClass.EventAccess)) {
419                                 Report.Error (131, loc,
420                                               "Left hand of an assignment must be a variable, " +
421                                               "a property or an indexer");
422                                 return null;
423                         }
424
425                         if ((source.eclass == ExprClass.Type) && (source is TypeExpr)) {
426                                 source.Error_UnexpectedKind (ec, "variable or value", loc);
427                                 return null;
428                         } else if ((RootContext.Version == LanguageVersion.ISO_1) &&
429                                    (source is MethodGroupExpr)){
430                                 ((MethodGroupExpr) source).ReportUsageError ();
431                                 return null;
432
433                         }
434
435                         if (target_type == source_type){
436                                 if (source is New && target_type.IsValueType &&
437                                     (target.eclass != ExprClass.IndexerAccess) && (target.eclass != ExprClass.PropertyAccess)){
438                                         New n = (New) source;
439                                         
440                                         if (n.SetValueTypeVariable (target))
441                                                 return n;
442                                         else
443                                                 return null;
444                                 }
445                                 
446                                 return this;
447                         }
448                         
449                         //
450                         // If this assignment/operator was part of a compound binary
451                         // operator, then we allow an explicit conversion, as detailed
452                         // in the spec. 
453                         //
454
455                         if (this is CompoundAssign){
456                                 CompoundAssign a = (CompoundAssign) this;
457                                 
458                                 Binary b = source as Binary;
459                                 if (b != null){
460                                         //
461                                         // 1. if the source is explicitly convertible to the
462                                         //    target_type
463                                         //
464                                         
465                                         source = Convert.ExplicitConversion (ec, source, target_type, loc);
466                                         if (source == null){
467                                                 Convert.Error_CannotImplicitConversion (loc, source_type, target_type);
468                                                 return null;
469                                         }
470                                 
471                                         //
472                                         // 2. and the original right side is implicitly convertible to
473                                         // the type of target
474                                         //
475                                         if (Convert.ImplicitStandardConversionExists (ec, a.original_source, target_type))
476                                                 return this;
477
478                                         //
479                                         // In the spec 2.4 they added: or if type of the target is int
480                                         // and the operator is a shift operator...
481                                         //
482                                         if (source_type == TypeManager.int32_type &&
483                                             (b.Oper == Binary.Operator.LeftShift || b.Oper == Binary.Operator.RightShift))
484                                                 return this;
485
486                                         Convert.Error_CannotImplicitConversion (loc, a.original_source.Type, target_type);
487                                         return null;
488                                 }
489                         }
490
491                         if (source.eclass == ExprClass.MethodGroup && !TypeManager.IsDelegateType (target_type)) {
492                                 Report.Error (428, source.Location, "Cannot convert method group `{0}' to non-delegate type `{1}'. Did you intend to invoke the method?",
493                                         ((MethodGroupExpr)source).Name, target.GetSignatureForError ());
494                                 return null;
495                         }
496
497                         source = Convert.ImplicitConversionRequired (ec, source, target_type, loc);
498                         
499                         if (source == null)
500                                 return null;
501
502                         // If we're an embedded assignment, we need to create a new temporary variable
503                         // for the converted value.  Our parent will use this new variable as its source.
504                         // The same applies when we have an embedded assignment - in this case, we need
505                         // to convert our embedded assignment's temporary local variable to the correct
506                         // type and store it in a new temporary local.
507                         if (is_embedded || embedded != null) {
508                                 type = target_type;
509                                 temp = new LocalTemporary (ec, type);
510                                 must_free_temp = true;
511                         }
512                         
513                         return this;
514                 }
515
516                 Expression EmitEmbedded (EmitContext ec)
517                 {
518                         // Emit an embedded assignment.
519
520                         if (real_temp != null) {
521                                 // If we're the innermost assignment, `real_source' is the right-hand
522                                 // expression which gets assigned to all the variables left of it.
523                                 // Emit this expression and store its result in real_temp.
524                                 real_source.Emit (ec);
525                                 real_temp.Store (ec);
526                         }
527
528                         if (embedded != null)
529                                 embedded.EmitEmbedded (ec);
530
531                         // This happens when we've done a type conversion, in this case source will be
532                         // the expression which does the type conversion from real_temp.
533                         // So emit it and store the result in temp; this is the var which will be read
534                         // by our parent.
535                         if (temp != real_temp) {
536                                 source.Emit (ec);
537                                 temp.Store (ec);
538                         }
539
540                         Expression temp_source = (temp != null) ? temp : source;
541                         ((IAssignMethod) target).EmitAssign (ec, temp_source, false, false);
542                         return temp_source;
543                 }
544
545                 void ReleaseEmbedded (EmitContext ec)
546                 {
547                         if (embedded != null)
548                                 embedded.ReleaseEmbedded (ec);
549
550                         if (real_temp != null)
551                                 real_temp.Release (ec);
552
553                         if (must_free_temp)
554                                 temp.Release (ec);
555                 }
556
557                 void Emit (EmitContext ec, bool is_statement)
558                 {
559                         if (target is EventExpr) {
560                                 ((EventExpr) target).EmitAddOrRemove (ec, source);
561                                 return;
562                         }
563                         
564                         IAssignMethod am = (IAssignMethod) target;
565
566                         Expression temp_source;
567                         if (embedded != null) {
568                                 temp_source = embedded.EmitEmbedded (ec);
569
570                                 if (temp != null) {
571                                         source.Emit (ec);
572                                         temp.Store (ec);
573                                         temp_source = temp;
574                                 }
575                         } else
576                                 temp_source = source;
577                 
578                         am.EmitAssign (ec, temp_source, !is_statement, this is CompoundAssign);
579                                 
580                         if (embedded != null) {
581                                 if (temp != null)
582                                         temp.Release (ec);
583                                 embedded.ReleaseEmbedded (ec);
584                         }
585                 }
586                 
587                 public override void Emit (EmitContext ec)
588                 {
589                         Emit (ec, false);
590                 }
591
592                 public override void EmitStatement (EmitContext ec)
593                 {
594                         Emit (ec, true);
595                 }
596         }
597
598         
599         //
600         // This class is used for compound assignments.  
601         //
602         class CompoundAssign : Assign {
603                 Binary.Operator op;
604                 public Expression original_source;
605                 
606                 public CompoundAssign (Binary.Operator op, Expression target, Expression source)
607                         : base (target, source, target.Location)
608                 {
609                         original_source = source;
610                         this.op = op;
611                 }
612
613                 protected CompoundAssign (CompoundAssign embedded, Location l)
614                         : this (embedded.op, embedded.target, embedded.source)
615                 {
616                         this.is_embedded = true;
617                 }
618
619                 protected override Assign GetEmbeddedAssign (Location loc)
620                 {
621                         return new CompoundAssign (this, loc);
622                 }
623
624                 public Expression ResolveSource (EmitContext ec)
625                 {
626                         return original_source.Resolve (ec);
627                 }
628
629                 public override Expression DoResolve (EmitContext ec)
630                 {
631                         original_source = original_source.Resolve (ec);
632                         if (original_source == null)
633                                 return null;
634
635                         target = target.Resolve (ec);
636                         if (target == null)
637                                 return null;
638                         
639                         //
640                         // Only now we can decouple the original source/target
641                         // into a tree, to guarantee that we do not have side
642                         // effects.
643                         //
644                         source = new Binary (op, target, original_source);
645                         return base.DoResolve (ec);
646                 }
647         }
648 }
649
650
651
652