2 // iterators.cs: Support for implementing iterators
5 // Miguel de Icaza (miguel@ximian.com)
7 // (C) 2003 Ximian, Inc.
10 // Flow analysis for Yield.
11 // Emit calls to parent object constructor.
14 // Current should be defined to return T, and IEnumerator.Current returns object
18 using System.Collections;
19 using System.Reflection;
20 using System.Reflection.Emit;
22 namespace Mono.CSharp {
24 public interface IIteratorContainer {
27 // Invoked if a yield statement is found in the body
32 public class Yield : Statement {
33 public Expression expr;
36 public Yield (Expression expr, Location l)
42 public static bool CheckContext (EmitContext ec, Location loc)
44 if (ec.CurrentBranching.InFinally (true)){
45 Report.Error (1625, loc, "Cannot yield in the body of a " +
49 if (ec.CurrentBranching.InCatch ()){
50 Report.Error (1631, loc, "Cannot yield in the body of a " +
54 if (ec.InAnonymousMethod){
55 Report.Error (1621, loc, "yield statement can not appear " +
56 "inside an anonymoud method");
61 // FIXME: Missing check for Yield inside try block that contains catch clauses
66 public override bool Resolve (EmitContext ec)
68 expr = expr.Resolve (ec);
71 if (!CheckContext (ec, loc))
74 in_exc = ec.CurrentBranching.InTryOrCatch (false);
75 Type iterator_type = IteratorHandler.Current.IteratorType;
76 if (expr.Type != iterator_type){
77 expr = Convert.ImplicitConversionRequired (ec, expr, iterator_type, loc);
84 protected override void DoEmit (EmitContext ec)
86 IteratorHandler.Current.MarkYield (ec, expr, in_exc);
90 public class YieldBreak : Statement {
92 public YieldBreak (Location l)
97 public override bool Resolve (EmitContext ec)
99 if (!Yield.CheckContext (ec, loc))
102 ec.CurrentBranching.CurrentUsageVector.Goto ();
106 protected override void DoEmit (EmitContext ec)
108 IteratorHandler.Current.EmitYieldBreak (ec.ig, true);
112 public class IteratorHandler {
114 // Points to the current iterator handler, will be probed by
115 // Yield and YieldBreak to get their context information
117 public static IteratorHandler Current;
120 // The typebuilder to the proxy class we create
122 TypeBuilder enumerator_proxy_class;
123 TypeBuilder enumerable_proxy_class;
126 // The type of this iterator, object by default.
128 public Type IteratorType;
131 // The members we create on the proxy class
133 MethodBuilder move_next_method;
134 MethodBuilder reset_method;
135 MethodBuilder get_current_method;
136 MethodBuilder dispose_method;
137 MethodBuilder getenumerator_method;
138 PropertyBuilder current_property;
139 ConstructorBuilder enumerator_proxy_constructor;
140 ConstructorBuilder enumerable_proxy_constructor;
143 // The PC for the state machine.
145 FieldBuilder pc_field;
148 // The value computed for Current
150 FieldBuilder current_field;
153 // Used to reference fields on the container class (instance methods)
155 public FieldBuilder this_field;
156 public FieldBuilder enumerable_this_field;
159 // References the parameters
162 public FieldBuilder [] parameter_fields;
163 FieldBuilder [] enumerable_parameter_fields;
166 // The state as we generate the iterator
168 ArrayList resume_labels = new ArrayList ();
172 // Context from the original method
175 TypeContainer container;
178 InternalParameters parameters;
179 Block original_block;
183 static int proxy_count;
185 public void EmitYieldBreak (ILGenerator ig, bool add_return)
187 ig.Emit (OpCodes.Ldarg_0);
188 IntConstant.EmitInt (ig, -1);
189 ig.Emit (OpCodes.Stfld, pc_field);
191 ig.Emit (OpCodes.Ldc_I4_0);
192 ig.Emit (OpCodes.Ret);
196 void EmitThrowInvalidOp (ILGenerator ig)
198 ig.Emit (OpCodes.Newobj, TypeManager.invalid_operation_ctor);
199 ig.Emit (OpCodes.Throw);
202 void Create_MoveNext ()
204 move_next_method = enumerator_proxy_class.DefineMethod (
205 "System.IEnumerator.MoveNext",
206 MethodAttributes.HideBySig | MethodAttributes.NewSlot |
207 MethodAttributes.Virtual,
208 CallingConventions.HasThis, TypeManager.bool_type, TypeManager.NoTypes);
209 enumerator_proxy_class.DefineMethodOverride (move_next_method, TypeManager.bool_movenext_void);
211 ILGenerator ig = move_next_method.GetILGenerator ();
212 EmitContext ec = new EmitContext (
214 TypeManager.void_type, modifiers);
216 Label dispatcher = ig.DefineLabel ();
217 ig.Emit (OpCodes.Br, dispatcher);
218 Label entry_point = ig.DefineLabel ();
219 ig.MarkLabel (entry_point);
220 resume_labels.Add (entry_point);
223 SymbolWriter sw = CodeGen.SymbolWriter;
224 if ((sw != null) && !Location.IsNull (loc) && !Location.IsNull (original_block.EndLocation)) {
225 sw.OpenMethod (container, move_next_method, loc, original_block.EndLocation);
227 ec.EmitTopBlock (original_block, parameters, loc);
231 ec.EmitTopBlock (original_block, parameters, loc);
235 EmitYieldBreak (ig, true);
238 // FIXME: Split the switch in blocks that can be consumed by switch.
240 ig.MarkLabel (dispatcher);
242 Label [] labels = new Label [resume_labels.Count];
243 resume_labels.CopyTo (labels);
244 ig.Emit (OpCodes.Ldarg_0);
245 ig.Emit (OpCodes.Ldfld, pc_field);
246 ig.Emit (OpCodes.Switch, labels);
247 ig.Emit (OpCodes.Ldc_I4_0);
248 ig.Emit (OpCodes.Ret);
252 // Invoked when a local variable declaration needs to be mapped to
253 // a field in our proxy class
255 // Prefixes registered:
256 // v_ for EmitContext.MapVariable
259 public FieldBuilder MapVariable (string pfx, string name, Type t)
261 return enumerator_proxy_class.DefineField (pfx + name, t, FieldAttributes.Public);
266 reset_method = enumerator_proxy_class.DefineMethod (
267 "System.IEnumerator.Reset",
268 MethodAttributes.HideBySig | MethodAttributes.NewSlot |
269 MethodAttributes.Virtual,
270 CallingConventions.HasThis, TypeManager.void_type, TypeManager.NoTypes);
271 enumerator_proxy_class.DefineMethodOverride (reset_method, TypeManager.void_reset_void);
272 ILGenerator ig = reset_method.GetILGenerator ();
273 EmitThrowInvalidOp (ig);
276 void Create_Current ()
278 get_current_method = enumerator_proxy_class.DefineMethod (
279 "System.IEnumerator.get_Current",
280 MethodAttributes.HideBySig | MethodAttributes.SpecialName |
281 MethodAttributes.NewSlot | MethodAttributes.Virtual,
282 CallingConventions.HasThis, TypeManager.object_type, TypeManager.NoTypes);
283 enumerator_proxy_class.DefineMethodOverride (get_current_method, TypeManager.object_getcurrent_void);
285 current_property = enumerator_proxy_class.DefineProperty (
287 PropertyAttributes.RTSpecialName | PropertyAttributes.SpecialName,
288 TypeManager.object_type, null);
290 current_property.SetGetMethod (get_current_method);
292 ILGenerator ig = get_current_method.GetILGenerator ();
294 ig.Emit (OpCodes.Ldarg_0);
295 ig.Emit (OpCodes.Ldfld, pc_field);
296 ig.Emit (OpCodes.Ldc_I4_0);
297 Label return_current = ig.DefineLabel ();
298 ig.Emit (OpCodes.Bgt, return_current);
299 EmitThrowInvalidOp (ig);
301 ig.MarkLabel (return_current);
302 ig.Emit (OpCodes.Ldarg_0);
303 ig.Emit (OpCodes.Ldfld, current_field);
304 ig.Emit (OpCodes.Ret);
307 void Create_Dispose ()
309 dispose_method = enumerator_proxy_class.DefineMethod (
310 "System.IDisposable.Dispose",
311 MethodAttributes.HideBySig | MethodAttributes.SpecialName |
312 MethodAttributes.NewSlot | MethodAttributes.Virtual,
313 CallingConventions.HasThis, TypeManager.void_type, TypeManager.NoTypes);
314 enumerator_proxy_class.DefineMethodOverride (dispose_method, TypeManager.void_dispose_void);
315 ILGenerator ig = dispose_method.GetILGenerator ();
317 EmitYieldBreak (ig, false);
318 ig.Emit (OpCodes.Ret);
321 void Create_GetEnumerator ()
323 getenumerator_method = enumerable_proxy_class.DefineMethod (
324 "IEnumerable.GetEnumerator",
325 MethodAttributes.HideBySig | MethodAttributes.SpecialName |
326 MethodAttributes.NewSlot | MethodAttributes.Virtual,
327 CallingConventions.HasThis, TypeManager.ienumerator_type, TypeManager.NoTypes);
329 enumerable_proxy_class.DefineMethodOverride (getenumerator_method, TypeManager.ienumerable_getenumerator_void);
330 ILGenerator ig = getenumerator_method.GetILGenerator ();
332 ig.Emit (OpCodes.Newobj, (ConstructorInfo) enumerator_proxy_constructor);
333 if (enumerable_this_field != null || parameters.Count > 0){
334 LocalBuilder obj = ig.DeclareLocal (enumerator_proxy_class);
335 ig.Emit (OpCodes.Stloc, obj);
336 if (enumerable_this_field != null){
337 ig.Emit (OpCodes.Ldloc, obj);
338 ig.Emit (OpCodes.Ldarg_0);
339 ig.Emit (OpCodes.Ldfld, enumerable_this_field);
340 ig.Emit (OpCodes.Stfld, this_field);
343 for (int i = 0; i < parameters.Count; i++){
344 ig.Emit (OpCodes.Ldloc, obj);
345 ig.Emit (OpCodes.Ldarg_0);
346 ig.Emit (OpCodes.Ldfld, enumerable_parameter_fields [i]);
347 ig.Emit (OpCodes.Stfld, parameter_fields [i]);
349 ig.Emit (OpCodes.Ldloc, obj);
352 ig.Emit (OpCodes.Ret);
356 // Called back from Yield
358 public void MarkYield (EmitContext ec, Expression expr, bool in_exc)
360 ILGenerator ig = ec.ig;
362 // Store the new current
363 ig.Emit (OpCodes.Ldarg_0);
365 ig.Emit (OpCodes.Stfld, current_field);
369 ig.Emit (OpCodes.Ldarg_0);
370 IntConstant.EmitInt (ig, pc);
371 ig.Emit (OpCodes.Stfld, pc_field);
374 ig.Emit (OpCodes.Ldc_I4_1);
376 // Find out how to "leave"
377 if (in_exc || !ec.IsLastStatement){
378 Type old = ec.ReturnType;
379 ec.ReturnType = TypeManager.int32_type;
380 ig.Emit (OpCodes.Stloc, ec.TemporaryReturn ());
385 ec.NeedReturnLabel ();
386 ig.Emit (OpCodes.Leave, ec.ReturnLabel);
387 } else if (ec.IsLastStatement){
388 ig.Emit (OpCodes.Ret);
390 ec.NeedReturnLabel ();
391 ig.Emit (OpCodes.Br, ec.ReturnLabel);
394 Label resume_point = ig.DefineLabel ();
395 ig.MarkLabel (resume_point);
396 resume_labels.Add (resume_point);
400 // Creates the IEnumerator Proxy class
402 void MakeEnumeratorProxy ()
404 TypeExpr [] proxy_base_interfaces = new TypeExpr [2];
405 proxy_base_interfaces [0] = new TypeExpression (TypeManager.ienumerator_type, loc);
406 proxy_base_interfaces [1] = new TypeExpression (TypeManager.idisposable_type, loc);
407 Type [] proxy_base_itypes = new Type [2];
408 proxy_base_itypes [0] = TypeManager.ienumerator_type;
409 proxy_base_itypes [1] = TypeManager.idisposable_type;
410 TypeBuilder container_builder = container.TypeBuilder;
415 enumerator_proxy_class = container_builder.DefineNestedType (
416 "<Enumerator:" + name + ":" + (proxy_count++) + ">",
417 TypeAttributes.AutoLayout | TypeAttributes.Class |TypeAttributes.NestedPrivate,
418 TypeManager.object_type, proxy_base_itypes);
420 TypeManager.RegisterBuilder (enumerator_proxy_class, proxy_base_interfaces);
425 pc_field = enumerator_proxy_class.DefineField ("PC", TypeManager.int32_type, FieldAttributes.Assembly);
426 current_field = enumerator_proxy_class.DefineField ("current", IteratorType, FieldAttributes.Assembly);
427 if ((modifiers & Modifiers.STATIC) == 0)
428 this_field = enumerator_proxy_class.DefineField ("THIS", container.TypeBuilder, FieldAttributes.Assembly);
430 parameter_fields = new FieldBuilder [parameters.Count];
431 for (int i = 0; i < parameters.Count; i++){
432 parameter_fields [i] = enumerator_proxy_class.DefineField (
433 String.Format ("tor{0}_{1}", i, parameters.ParameterName (i)),
434 parameters.ParameterType (i), FieldAttributes.Assembly);
438 // Define a constructor
441 enumerator_proxy_constructor = enumerator_proxy_class.DefineConstructor (
442 MethodAttributes.Public | MethodAttributes.HideBySig |
443 MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
444 CallingConventions.HasThis, TypeManager.NoTypes);
445 InternalParameters parameter_info = new InternalParameters (
446 TypeManager.NoTypes, Parameters.EmptyReadOnlyParameters);
447 TypeManager.RegisterMethod (enumerator_proxy_constructor, parameter_info, TypeManager.NoTypes);
452 ILGenerator ig = enumerator_proxy_constructor.GetILGenerator ();
453 ig.Emit (OpCodes.Ldarg_0);
454 ig.Emit (OpCodes.Call, TypeManager.object_ctor);
456 ig.Emit (OpCodes.Ret);
460 // Creates the IEnumerable proxy class
462 void MakeEnumerableProxy ()
464 TypeBuilder container_builder = container.TypeBuilder;
465 Type [] proxy_base_interfaces = new Type [1];
466 proxy_base_interfaces [0] = TypeManager.ienumerable_type;
469 // Creates the Enumerable proxy class.
471 enumerable_proxy_class = container_builder.DefineNestedType (
472 "<Enumerable:" + name + ":" + (proxy_count++)+ ">",
473 TypeAttributes.AutoLayout | TypeAttributes.Class |TypeAttributes.NestedPublic,
474 TypeManager.object_type, proxy_base_interfaces);
479 if ((modifiers & Modifiers.STATIC) == 0){
480 enumerable_this_field = enumerable_proxy_class.DefineField (
481 "THIS", container.TypeBuilder, FieldAttributes.Assembly);
483 enumerable_parameter_fields = new FieldBuilder [parameters.Count];
484 for (int i = 0; i < parameters.Count; i++){
485 enumerable_parameter_fields [i] = enumerable_proxy_class.DefineField (
486 String.Format ("able{0}_{1}", i, parameters.ParameterName (i)),
487 parameters.ParameterType (i), FieldAttributes.Assembly);
490 enumerable_proxy_constructor = enumerable_proxy_class.DefineConstructor (
491 MethodAttributes.Public | MethodAttributes.HideBySig |
492 MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
493 CallingConventions.HasThis, TypeManager.NoTypes);
494 InternalParameters parameter_info = new InternalParameters (
495 TypeManager.NoTypes, Parameters.EmptyReadOnlyParameters);
496 TypeManager.RegisterMethod (enumerable_proxy_constructor, parameter_info, TypeManager.NoTypes);
498 ILGenerator ig = enumerable_proxy_constructor.GetILGenerator ();
499 ig.Emit (OpCodes.Ldarg_0);
500 ig.Emit (OpCodes.Call, TypeManager.object_ctor);
502 ig.Emit (OpCodes.Ret);
506 // Populates the Enumerator Proxy class
508 void PopulateProxy ()
510 RootContext.RegisterHelperClass (enumerator_proxy_class);
517 if (IsIEnumerable (return_type)){
518 Create_GetEnumerator ();
519 RootContext.RegisterHelperClass (enumerable_proxy_class);
525 // This is invoked by the EmitCode hook
527 void SetupIterator ()
535 public IteratorHandler (string name, TypeContainer container, Type return_type, Type [] param_types,
536 InternalParameters parameters, int modifiers, Location loc)
539 this.container = container;
540 this.return_type = return_type;
541 this.param_types = param_types;
542 this.parameters = parameters;
543 this.modifiers = modifiers;
546 IteratorType = TypeManager.object_type;
548 RootContext.EmitCodeHook += new RootContext.Hook (SetupIterator);
552 // This class is just an expression that evaluates to a type, and the
553 // type is our internal proxy class. Used in the generated new body
554 // of the original method
556 class NewInnerType : Expression {
557 IteratorHandler handler;
559 public NewInnerType (IteratorHandler handler, Location l)
561 this.handler = handler;
562 eclass = ExprClass.Value;
566 public override Expression DoResolve (EmitContext ec)
568 // Create the proxy class type.
569 handler.MakeEnumeratorProxy ();
571 if (IsIEnumerable (handler.return_type))
572 handler.MakeEnumerableProxy ();
574 type = handler.return_type;
578 public override Expression ResolveAsTypeStep (EmitContext ec)
580 return DoResolve (ec);
583 public override void Emit (EmitContext ec)
585 ILGenerator ig = ec.ig;
586 FieldBuilder this_field = null;
587 bool is_ienumerable = IsIEnumerable (handler.return_type);
591 temp_type = handler.enumerable_proxy_class;
592 ig.Emit (OpCodes.Newobj, (ConstructorInfo) handler.enumerable_proxy_constructor);
593 this_field = handler.enumerable_this_field;
595 temp_type = handler.enumerator_proxy_class;
596 ig.Emit (OpCodes.Newobj, (ConstructorInfo) handler.enumerator_proxy_constructor);
597 this_field = handler.this_field;
600 if (this_field == null && handler.parameters.Count == 0)
603 LocalBuilder temp = ec.GetTemporaryLocal (temp_type);
605 ig.Emit (OpCodes.Stloc, temp);
610 if (this_field != null){
611 ig.Emit (OpCodes.Ldloc, temp);
612 ig.Emit (OpCodes.Ldarg_0);
613 if (handler.container is Struct)
614 ig.Emit (OpCodes.Ldobj, handler.container.TypeBuilder);
615 ig.Emit (OpCodes.Stfld, this_field);
619 for (int i = 0; i < handler.parameters.Count; i++){
620 ig.Emit (OpCodes.Ldloc, temp);
621 ParameterReference.EmitLdArg (ig, i + first);
623 ig.Emit (OpCodes.Stfld, handler.enumerable_parameter_fields [i]);
625 ig.Emit (OpCodes.Stfld, handler.parameter_fields [i]);
631 ig.Emit (OpCodes.Ldloc, temp);
632 ec.FreeTemporaryLocal (temp, handler.container.TypeBuilder);
637 // This return statement tricks return into not flagging an error for being
638 // used in a Yields method
640 class NoCheckReturn : Return {
641 public NoCheckReturn (Expression expr, Location loc) : base (expr, loc)
645 public override bool Resolve (EmitContext ec)
647 ec.InIterator = false;
648 bool ret_val = base.Resolve (ec);
649 ec.InIterator = true;
655 static bool IsIEnumerable (Type t)
657 return t == TypeManager.ienumerable_type;
660 static bool IsIEnumerator (Type t)
662 return t == TypeManager.ienumerator_type;
666 // Returns the new block for the method, or null on failure
668 public Block Setup (Block block)
670 if (!(IsIEnumerator (return_type) || IsIEnumerable (return_type))){
672 1624, loc, "The body of `{0}' cannot be an iterator " +
673 "block because '{1}' is not an iterator interface type",
674 name, TypeManager.CSharpName (return_type));
678 for (int i = 0; i < parameters.Count; i++){
679 Parameter.Modifier mod = parameters.ParameterModifier (i);
680 if ((mod & (Parameter.Modifier.REF | Parameter.Modifier.OUT)) != 0){
681 Report.Error (1623, loc, "Iterators cannot have ref " +
682 "or out parameters");
687 original_block = block;
688 Block b = new Block (null);
690 // return new InnerClass ()
691 b.AddStatement (new NoCheckReturn (new NewInnerType (this, loc), loc));