d571d5463786d49a90aba213d1e5855bc6cecf33
[mono.git] / mcs / gmcs / iterators.cs
1 //
2 // iterators.cs: Support for implementing iterators
3 //
4 // Author:
5 //   Miguel de Icaza (miguel@ximian.com)
6 //
7 // (C) 2003 Ximian, Inc.
8 //
9 // TODO:
10 //    Flow analysis for Yield.
11 //    Emit calls to parent object constructor.
12 //
13 // Generics note:
14 //    Current should be defined to return T, and IEnumerator.Current returns object
15 //
16
17 using System;
18 using System.Collections;
19 using System.Reflection;
20 using System.Reflection.Emit;
21
22 namespace Mono.CSharp {
23
24         public interface IIteratorContainer {
25
26                 //
27                 // Invoked if a yield statement is found in the body
28                 //
29                 void SetYields ();
30         }
31         
32         public class Yield : Statement {
33                 public Expression expr;
34                 bool in_exc;
35                 
36                 public Yield (Expression expr, Location l)
37                 {
38                         this.expr = expr;
39                         loc = l;
40                 }
41
42                 public static bool CheckContext (EmitContext ec, Location loc)
43                 {
44                         if (ec.CurrentBranching.InFinally (true)){
45                                 Report.Error (1625, loc, "Cannot yield in the body of a " +
46                                               "finally clause");
47                                 return false;
48                         }
49                         if (ec.CurrentBranching.InCatch ()){
50                                 Report.Error (1631, loc, "Cannot yield in the body of a " +
51                                               "catch clause");
52                                 return false;
53                         }
54                         if (ec.InAnonymousMethod){
55                                 Report.Error (1621, loc, "yield statement can not appear " +
56                                               "inside an anonymoud method");
57                                 return false;
58                         }
59
60                         //
61                         // FIXME: Missing check for Yield inside try block that contains catch clauses
62                         //
63                         return true;
64                 }
65                 
66                 public override bool Resolve (EmitContext ec)
67                 {
68                         expr = expr.Resolve (ec);
69                         if (expr == null)
70                                 return false;
71                         if (!CheckContext (ec, loc))
72                                 return false;
73
74                         in_exc = ec.CurrentBranching.InTryOrCatch (false);
75                         Type iterator_type = ec.CurrentIterator.IteratorType;
76                         if (expr.Type != iterator_type){
77                                 expr = Convert.ImplicitConversionRequired (ec, expr, iterator_type, loc);
78                                 if (expr == null)
79                                         return false;
80                         }
81                         return true;
82                 }
83
84                 protected override void DoEmit (EmitContext ec)
85                 {
86                         ec.CurrentIterator.MarkYield (ec, expr, in_exc);
87                 }
88         }
89
90         public class YieldBreak : Statement {
91
92                 public YieldBreak (Location l)
93                 {
94                         loc = l;
95                 }
96
97                 public override bool Resolve (EmitContext ec)
98                 {
99                         if (!Yield.CheckContext (ec, loc))
100                                 return false;
101
102                         ec.CurrentBranching.CurrentUsageVector.Goto ();
103                         return true;
104                 }
105
106                 protected override void DoEmit (EmitContext ec)
107                 {
108                         ec.CurrentIterator.EmitYieldBreak (ec.ig, true);
109                 }
110         }
111
112         public class Iterator : Class {
113                 Block original_block;
114                 Block block;
115
116                 Type iterator_type;
117                 TypeExpr iterator_type_expr;
118                 bool is_enumerable;
119                 bool is_static;
120
121                 //
122                 // The state as we generate the iterator
123                 //
124                 ArrayList resume_labels = new ArrayList ();
125                 int pc;
126                 
127                 //
128                 // Context from the original method
129                 //
130                 TypeContainer container;
131                 Type return_type;
132                 Type [] param_types;
133                 InternalParameters parameters;
134
135                 static int proxy_count;
136
137                 public void EmitYieldBreak (ILGenerator ig, bool add_return)
138                 {
139                         ig.Emit (OpCodes.Ldarg_0);
140                         IntConstant.EmitInt (ig, -1);
141                         ig.Emit (OpCodes.Stfld, pc_field.FieldBuilder);
142                         if (add_return){
143                                 ig.Emit (OpCodes.Ldc_I4_0);
144                                 ig.Emit (OpCodes.Ret);
145                         }
146                 }
147
148                 public void EmitMoveNext (EmitContext ec)
149                 {
150                         ILGenerator ig = ec.ig;
151
152                         Label dispatcher = ig.DefineLabel ();
153                         ig.Emit (OpCodes.Br, dispatcher);
154                         Label entry_point = ig.DefineLabel ();
155                         ig.MarkLabel (entry_point);
156                         resume_labels.Add (entry_point);
157                         
158                         ec.EmitTopBlock (original_block, parameters, Location);
159
160                         EmitYieldBreak (ig, true);
161
162                         //
163                         // FIXME: Split the switch in blocks that can be consumed
164                         //        by switch.
165                         //
166                         ig.MarkLabel (dispatcher);
167
168                         Label [] labels = new Label [resume_labels.Count];
169                         resume_labels.CopyTo (labels);
170                         ig.Emit (OpCodes.Ldarg_0);
171                         ig.Emit (OpCodes.Ldfld, pc_field.FieldBuilder);
172                         ig.Emit (OpCodes.Switch, labels);
173                         ig.Emit (OpCodes.Ldc_I4_0); 
174                         ig.Emit (OpCodes.Ret);
175                 }
176
177                 // 
178                 // Invoked when a local variable declaration needs to be mapped to
179                 // a field in our proxy class
180                 //
181                 // Prefixes registered:
182                 //   v_   for EmitContext.MapVariable
183                 //   s_   for Storage
184                 //
185                 public FieldBuilder MapVariable (string pfx, string name, Type t)
186                 {
187                         return TypeBuilder.DefineField (pfx + name, t, FieldAttributes.Public);
188                 }
189                 
190                 //
191                 // Called back from Yield
192                 //
193                 public void MarkYield (EmitContext ec, Expression expr, bool in_exc)
194                 {
195                         ILGenerator ig = ec.ig;
196
197                         // Store the new current
198                         ig.Emit (OpCodes.Ldarg_0);
199                         expr.Emit (ec);
200                         ig.Emit (OpCodes.Stfld, current_field.FieldBuilder);
201
202                         // increment pc
203                         pc++;
204                         ig.Emit (OpCodes.Ldarg_0);
205                         IntConstant.EmitInt (ig, pc);
206                         ig.Emit (OpCodes.Stfld, pc_field.FieldBuilder);
207
208                         // Return ok
209                         ig.Emit (OpCodes.Ldc_I4_1);
210                         
211                         // Find out how to "leave"
212                         if (in_exc || !ec.IsLastStatement){
213                                 Type old = ec.ReturnType;
214                                 ec.ReturnType = TypeManager.int32_type;
215                                 ig.Emit (OpCodes.Stloc, ec.TemporaryReturn ());
216                                 ec.ReturnType = old;
217                         }
218
219                         if (in_exc){
220                                 ec.NeedReturnLabel ();
221                                 ig.Emit (OpCodes.Leave, ec.ReturnLabel);
222                         } else if (ec.IsLastStatement){
223                                 ig.Emit (OpCodes.Ret);
224                         } else {
225                                 ec.NeedReturnLabel ();
226                                 ig.Emit (OpCodes.Br, ec.ReturnLabel);
227                         }
228                         
229                         Label resume_point = ig.DefineLabel ();
230                         ig.MarkLabel (resume_point);
231                         resume_labels.Add (resume_point);
232                 }
233
234                 //
235                 // Our constructor
236                 //
237                 public Iterator (TypeContainer container, string name, Type return_type,
238                                  Type [] param_types, InternalParameters parameters,
239                                  int modifiers, Block block, Location loc)
240                         : base (container.NamespaceEntry, container,
241                                 "<Iterator:" + name + (proxy_count++) + ":>",
242                                 Modifiers.PRIVATE, null, loc)
243                 {
244                         this.container = container;
245                         this.return_type = return_type;
246                         this.param_types = param_types;
247                         this.parameters = parameters;
248                         this.original_block = block;
249                         this.block = new Block (null);
250
251                         is_static = (modifiers & Modifiers.STATIC) != 0;
252
253                         container.AddIterator (this);
254                 }
255
256                 public bool Define ()
257                 {
258                         if (!CheckType (return_type)) {
259                                 Report.Error (
260                                         1624, Location,
261                                         "The body of `{0}' cannot be an iterator block " +
262                                         "because '{1}' is not an iterator interface type",
263                                         Name, TypeManager.CSharpName (return_type));
264                                 return false;
265                         }
266
267                         for (int i = 0; i < parameters.Count; i++){
268                                 Parameter.Modifier mod = parameters.ParameterModifier (i);
269                                 if ((mod & (Parameter.Modifier.REF | Parameter.Modifier.OUT)) != 0){
270                                         Report.Error (
271                                                 1623, Location,
272                                                 "Iterators cannot have ref or out parameters");
273                                         return false;
274                                 }
275                         }
276
277                         ArrayList list = new ArrayList ();
278                         if (is_enumerable)
279                                 list.Add (new TypeExpression (
280                                                   TypeManager.ienumerable_type, Location));
281                         list.Add (new TypeExpression (TypeManager.ienumerator_type, Location));
282                         list.Add (new TypeExpression (TypeManager.idisposable_type, Location));
283
284                         iterator_type_expr = new TypeExpression (iterator_type, Location);
285
286                         Bases = list;
287                         return true;
288                 }
289
290                 //
291                 // Returns the new block for the method, or null on failure
292                 //
293                 protected override bool DoDefineType ()
294                 {
295                         Define_Fields ();
296                         Define_Constructor ();
297                         Define_Current ();
298                         Define_MoveNext ();
299                         Define_Reset ();
300                         Define_Dispose ();
301
302                         if (is_enumerable)
303                                 Define_GetEnumerator ();
304
305                         Create_Block ();
306
307                         return true;
308                 }
309
310
311                 Field pc_field;
312                 Field current_field;
313                 public Field this_field;
314                 public Field[] parameter_fields;
315
316                 void Create_Block ()
317                 {
318                         int first = is_static ? 0 : 1;
319
320                         ArrayList args = new ArrayList ();
321                         if (!is_static) {
322                                 Type t = container.TypeBuilder;
323                                 args.Add (new Argument (
324                                         new SimpleParameterReference (t, 0, Location),
325                                         Argument.AType.Expression));
326                         }
327
328                         for (int i = 0; i < parameters.Count; i++) {
329                                 Type t = parameters.ParameterType (i);
330                                 args.Add (new Argument (
331                                         new SimpleParameterReference (t, first + i, Location),
332                                         Argument.AType.Expression));
333                         }
334
335                         Expression new_expr = new New (
336                                 new TypeExpression (TypeBuilder, Location), args, Location);
337
338                         block.AddStatement (new NoCheckReturn (new_expr, Location));
339                 }
340
341                 void Define_Fields ()
342                 {
343                         Location loc = Location.Null;
344
345                         pc_field = new Field (
346                                 TypeManager.system_int32_expr, Modifiers.PRIVATE, "PC",
347                                 null, null, loc);
348                         AddField (pc_field);
349
350                         current_field = new Field (
351                                 iterator_type_expr, Modifiers.PRIVATE, "current",
352                                 null, null, loc);
353                         AddField (current_field);
354
355                         if (!is_static) {
356                                 this_field = new Field (
357                                         new TypeExpression (container.TypeBuilder, Location),
358                                         Modifiers.PRIVATE, "this", null, null, loc);
359                                 AddField (this_field);
360                         }
361
362                         parameter_fields = new Field [parameters.Count];
363                         for (int i = 0; i < parameters.Count; i++) {
364                                 string fname = String.Format (
365                                         "field{0}_{1}", i, parameters.ParameterName (i));
366
367                                 parameter_fields [i] = new Field (
368                                         new TypeExpression (parameters.ParameterType (i), loc),
369                                         Modifiers.PRIVATE, fname, null, null, loc);
370                                 AddField (parameter_fields [i]);
371                         }
372                 }
373
374                 void Define_Constructor ()
375                 {
376                         Parameters ctor_params;
377
378                         if (!is_static) {
379                                 Parameter this_param = new Parameter (
380                                         new TypeExpression (container.TypeBuilder, Location),
381                                         "this", Parameter.Modifier.NONE, null);
382
383                                 Parameter[] old_fixed = parameters.Parameters.FixedParameters;
384                                 Parameter[] fixed_params = new Parameter [old_fixed.Length + 1];
385                                 fixed_params [0] = this_param;
386                                 old_fixed.CopyTo (fixed_params, 1);
387
388                                 ctor_params = new Parameters (
389                                         fixed_params, parameters.Parameters.ArrayParameter,
390                                         Location);
391                         } else
392                                 ctor_params = parameters.Parameters;
393
394                         Constructor ctor = new Constructor (
395                                 this, Name, Modifiers.PUBLIC, ctor_params,
396                                 new ConstructorBaseInitializer (
397                                         null, Parameters.EmptyReadOnlyParameters, Location),
398                                 Location);
399                         AddConstructor (ctor);
400
401                         Block block = ctor.Block = new Block (null);
402
403                         if (!is_static) {
404                                 Type t = container.TypeBuilder;
405
406                                 Assign assign = new Assign (
407                                         new FieldExpression (this_field),
408                                         new SimpleParameterReference (t, 1, Location),
409                                         Location);
410
411                                 block.AddStatement (new StatementExpression (assign, Location));
412                         }
413
414                         int first = is_static ? 1 : 2;
415
416                         for (int i = 0; i < parameters.Count; i++) {
417                                 Type t = parameters.ParameterType (i);
418
419                                 Assign assign = new Assign (
420                                         new FieldExpression (parameter_fields [i]),
421                                         new SimpleParameterReference (t, first + i, Location),
422                                         Location);
423
424                                 block.AddStatement (new StatementExpression (assign, Location));
425                         }
426                 }
427
428                 Statement Create_ThrowInvalidOperation ()
429                 {
430                         TypeExpr ex_type = new TypeExpression (
431                                 TypeManager.invalid_operation_exception_type, Location);
432
433                         return new Throw (new New (ex_type, null, Location), Location);
434                 }
435
436                 Statement Create_ThrowNotSupported ()
437                 {
438                         TypeExpr ex_type = new TypeExpression (
439                                 TypeManager.not_supported_exception_type, Location);
440
441                         return new Throw (new New (ex_type, null, Location), Location);
442                 }
443
444                 void Define_Current ()
445                 {
446                         Block get_block = new Block (null);
447
448                         get_block.AddStatement (new If (
449                                 new Binary (
450                                         Binary.Operator.LessThanOrEqual,
451                                         new FieldExpression (pc_field),
452                                         new IntLiteral (0), Location),
453                                 Create_ThrowInvalidOperation (),
454                                 new Return (
455                                         new FieldExpression (current_field), Location),
456                                 Location));
457
458                         Accessor getter = new Accessor (get_block, null);
459
460                         Property current = new Property (
461                                 this, iterator_type_expr, Modifiers.PUBLIC,
462                                 false, "Current", null, getter, null, Location);
463                         AddProperty (current);
464                 }
465
466                 void Define_MoveNext ()
467                 {
468                         Method move_next = new Method (
469                                 this, TypeManager.system_boolean_expr,
470                                 Modifiers.PUBLIC, false, "MoveNext",
471                                 Parameters.EmptyReadOnlyParameters, null,
472                                 Location.Null);
473                         AddMethod (move_next);
474
475                         Block block = move_next.Block = new Block (null);
476
477                         MoveNextMethod inline = new MoveNextMethod (this, Location);
478                         block.AddStatement (inline);
479                 }
480
481                 void Define_GetEnumerator ()
482                 {
483                         Method get_enumerator = new Method (
484                                 this,
485                                 new TypeExpression (TypeManager.ienumerator_type, Location),
486                                 Modifiers.PUBLIC, false, "GetEnumerator",
487                                 Parameters.EmptyReadOnlyParameters, null,
488                                 Location.Null);
489                         AddMethod (get_enumerator);
490
491                         get_enumerator.Block = new Block (null);
492
493                         This the_this = new This (block, Location);
494                         get_enumerator.Block.AddStatement (new Return (the_this, Location));
495                 }
496
497                 protected class SimpleParameterReference : Expression
498                 {
499                         int idx;
500
501                         public SimpleParameterReference (Type type, int idx, Location loc)
502                         {
503                                 this.idx = idx;
504                                 this.loc = loc;
505                                 this.type = type;
506                                 eclass = ExprClass.Variable;
507                         }
508
509                         public override Expression DoResolve (EmitContext ec)
510                         {
511                                 return this;
512                         }
513
514                         public override void Emit (EmitContext ec)
515                         {
516                                 ParameterReference.EmitLdArg (ec.ig, idx);
517                         }
518                 }
519
520                 protected class FieldExpression : Expression
521                 {
522                         Field field;
523
524                         public FieldExpression (Field field)
525                         {
526                                 this.field = field;
527                         }
528
529                         public override Expression DoResolve (EmitContext ec)
530                         {
531                                 FieldExpr fexpr = new FieldExpr (field.FieldBuilder, loc);
532                                 fexpr.InstanceExpression = ec.GetThis (loc);
533                                 return fexpr.Resolve (ec);
534                         }
535
536                         public override void Emit (EmitContext ec)
537                         {
538                                 throw new InvalidOperationException ();
539                         }
540                 }
541
542                 public class MoveNextMethod : Statement {
543                         Iterator iterator;
544
545                         public MoveNextMethod (Iterator iterator, Location loc)
546                         {
547                                 this.loc = loc;
548                                 this.iterator = iterator;
549                         }
550
551                         public override bool Resolve (EmitContext ec)
552                         {
553                                 ec.CurrentBranching.CurrentUsageVector.Return ();
554                                 return true;
555                         }
556
557                         protected override void DoEmit (EmitContext ec)
558                         {
559                                 int code_flags = Modifiers.METHOD_YIELDS;
560                                 if (iterator.is_static)
561                                         code_flags |= Modifiers.STATIC;
562
563                                 EmitContext new_ec = new EmitContext (
564                                         iterator, loc, ec.ig, iterator.return_type,
565                                         code_flags);
566
567                                 new_ec.CurrentIterator = iterator;
568
569                                 iterator.EmitMoveNext (new_ec);
570                         }
571                 }
572
573                 protected class DoYieldBreak : Statement
574                 {
575                         Iterator iterator;
576                         bool add_return;
577
578                         public DoYieldBreak (Iterator iterator, bool add_return,
579                                              Location loc)
580                         {
581                                 this.iterator = iterator;
582                                 this.add_return = add_return;
583                                 this.loc = loc;
584                         }
585
586                         public override bool Resolve (EmitContext ec)
587                         {
588                                 if (add_return)
589                                         ec.CurrentBranching.CurrentUsageVector.Return ();
590                                 return true;
591                         }
592
593                         protected override void DoEmit (EmitContext ec)
594                         {
595                                iterator.EmitYieldBreak (ec.ig, add_return);
596                         }
597                 }
598
599                 void Define_Reset ()
600                 {
601                         Method reset = new Method (
602                                 this, TypeManager.system_void_expr, Modifiers.PUBLIC,
603                                 false, "Reset", Parameters.EmptyReadOnlyParameters,
604                                 null, Location);
605                         AddMethod (reset);
606
607                         reset.Block = new Block (null);
608                         reset.Block.AddStatement (Create_ThrowNotSupported ());
609                 }
610
611                 void Define_Dispose ()
612                 {
613                         Method dispose = new Method (
614                                 this, TypeManager.system_void_expr, Modifiers.PUBLIC,
615                                 false, "Dispose", Parameters.EmptyReadOnlyParameters,
616                                 null, Location);
617                         AddMethod (dispose);
618
619                         dispose.Block = new Block (null);
620                         dispose.Block.AddStatement (new DoYieldBreak (this, false, Location));
621                         dispose.Block.AddStatement (new Return (null, Location));
622                 }
623
624                 public Block Block {
625                         get { return block; }
626                 }
627
628                 public Type IteratorType {
629                         get { return iterator_type; }
630                 }
631
632                 //
633                 // This return statement tricks return into not flagging an error for being
634                 // used in a Yields method
635                 //
636                 class NoCheckReturn : Return {
637                         public NoCheckReturn (Expression expr, Location loc) : base (expr, loc)
638                         {
639                         }
640
641                         public override bool Resolve (EmitContext ec)
642                         {
643                                 ec.InIterator = false;
644                                 bool ret_val = base.Resolve (ec);
645                                 ec.InIterator = true;
646
647                                 return ret_val;
648                         }
649                 }
650
651                 bool CheckType (Type t)
652                 {
653                         if (t == TypeManager.ienumerable_type) {
654                                 iterator_type = TypeManager.object_type;
655                                 is_enumerable = true;
656                                 return true;
657                         } else if (t == TypeManager.ienumerator_type) {
658                                 iterator_type = TypeManager.object_type;
659                                 is_enumerable = false;
660                                 return true;
661                         }
662
663                         return false;
664                 }
665         }
666 }
667