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 (-208, loc, "yield statement can not appear in finally clause");
48 if (ec.CurrentBranching.InCatch ()){
49 Report.Error (-209, loc, "yield statement can not appear in the catch clause");
52 if (ec.InAnonymousMethod){
53 Report.Error (-209, loc, "yield statement can not appear inside an anonymoud method");
58 // FIXME: Missing check for Yield inside try block that contains catch clauses
63 public override bool Resolve (EmitContext ec)
65 expr = expr.Resolve (ec);
68 if (!CheckContext (ec, loc))
71 in_exc = ec.CurrentBranching.InTryOrCatch (false);
72 Type iterator_type = IteratorHandler.Current.IteratorType;
73 if (expr.Type != iterator_type){
74 expr = Convert.ImplicitConversionRequired (ec, expr, iterator_type, loc);
81 protected override void DoEmit (EmitContext ec)
83 IteratorHandler.Current.MarkYield (ec, expr, in_exc);
87 public class YieldBreak : Statement {
89 public YieldBreak (Location l)
94 public override bool Resolve (EmitContext ec)
96 if (!Yield.CheckContext (ec, loc))
99 ec.CurrentBranching.CurrentUsageVector.Goto ();
103 protected override void DoEmit (EmitContext ec)
105 IteratorHandler.Current.EmitYieldBreak (ec.ig, true);
109 public class IteratorHandler {
111 // Points to the current iterator handler, will be probed by
112 // Yield and YieldBreak to get their context information
114 public static IteratorHandler Current;
117 // The typebuilder to the proxy class we create
119 TypeBuilder enumerator_proxy_class;
120 TypeBuilder enumerable_proxy_class;
123 // The type of this iterator, object by default.
125 public Type IteratorType;
128 // The members we create on the proxy class
130 MethodBuilder move_next_method;
131 MethodBuilder reset_method;
132 MethodBuilder get_current_method;
133 MethodBuilder dispose_method;
134 MethodBuilder getenumerator_method;
135 PropertyBuilder current_property;
136 ConstructorBuilder enumerator_proxy_constructor;
137 ConstructorBuilder enumerable_proxy_constructor;
140 // The PC for the state machine.
142 FieldBuilder pc_field;
145 // The value computed for Current
147 FieldBuilder current_field;
150 // Used to reference fields on the container class (instance methods)
152 public FieldBuilder this_field;
153 public FieldBuilder enumerable_this_field;
156 // References the parameters
159 public FieldBuilder [] parameter_fields;
160 FieldBuilder [] enumerable_parameter_fields;
163 // The state as we generate the iterator
165 ArrayList resume_labels = new ArrayList ();
169 // Context from the original method
172 TypeContainer container;
175 InternalParameters parameters;
176 Block original_block;
180 static int proxy_count;
182 public void EmitYieldBreak (ILGenerator ig, bool add_return)
184 ig.Emit (OpCodes.Ldarg_0);
185 IntConstant.EmitInt (ig, -1);
186 ig.Emit (OpCodes.Stfld, pc_field);
188 ig.Emit (OpCodes.Ldc_I4_0);
189 ig.Emit (OpCodes.Ret);
193 void EmitThrowInvalidOp (ILGenerator ig)
195 ig.Emit (OpCodes.Newobj, TypeManager.invalid_operation_ctor);
196 ig.Emit (OpCodes.Throw);
199 void Create_MoveNext ()
201 move_next_method = enumerator_proxy_class.DefineMethod (
202 "System.IEnumerator.MoveNext",
203 MethodAttributes.HideBySig | MethodAttributes.NewSlot |
204 MethodAttributes.Virtual,
205 CallingConventions.HasThis, TypeManager.bool_type, TypeManager.NoTypes);
206 enumerator_proxy_class.DefineMethodOverride (move_next_method, TypeManager.bool_movenext_void);
208 ILGenerator ig = move_next_method.GetILGenerator ();
209 EmitContext ec = new EmitContext (
211 TypeManager.void_type, modifiers);
213 Label dispatcher = ig.DefineLabel ();
214 ig.Emit (OpCodes.Br, dispatcher);
215 Label entry_point = ig.DefineLabel ();
216 ig.MarkLabel (entry_point);
217 resume_labels.Add (entry_point);
220 SymbolWriter sw = CodeGen.SymbolWriter;
221 if ((sw != null) && !Location.IsNull (loc) && !Location.IsNull (original_block.EndLocation)) {
222 sw.OpenMethod (container, move_next_method, loc, original_block.EndLocation);
224 ec.EmitTopBlock (original_block, parameters, loc);
228 ec.EmitTopBlock (original_block, parameters, loc);
232 EmitYieldBreak (ig, true);
235 // FIXME: Split the switch in blocks that can be consumed by switch.
237 ig.MarkLabel (dispatcher);
239 Label [] labels = new Label [resume_labels.Count];
240 resume_labels.CopyTo (labels);
241 ig.Emit (OpCodes.Ldarg_0);
242 ig.Emit (OpCodes.Ldfld, pc_field);
243 ig.Emit (OpCodes.Switch, labels);
244 ig.Emit (OpCodes.Ldc_I4_0);
245 ig.Emit (OpCodes.Ret);
249 // Invoked when a local variable declaration needs to be mapped to
250 // a field in our proxy class
252 // Prefixes registered:
253 // v_ for EmitContext.MapVariable
256 public FieldBuilder MapVariable (string pfx, string name, Type t)
258 return enumerator_proxy_class.DefineField (pfx + name, t, FieldAttributes.Public);
263 reset_method = enumerator_proxy_class.DefineMethod (
264 "System.IEnumerator.Reset",
265 MethodAttributes.HideBySig | MethodAttributes.NewSlot |
266 MethodAttributes.Virtual,
267 CallingConventions.HasThis, TypeManager.void_type, TypeManager.NoTypes);
268 enumerator_proxy_class.DefineMethodOverride (reset_method, TypeManager.void_reset_void);
269 ILGenerator ig = reset_method.GetILGenerator ();
270 EmitThrowInvalidOp (ig);
273 void Create_Current ()
275 get_current_method = enumerator_proxy_class.DefineMethod (
276 "System.IEnumerator.get_Current",
277 MethodAttributes.HideBySig | MethodAttributes.SpecialName |
278 MethodAttributes.NewSlot | MethodAttributes.Virtual,
279 CallingConventions.HasThis, TypeManager.object_type, TypeManager.NoTypes);
280 enumerator_proxy_class.DefineMethodOverride (get_current_method, TypeManager.object_getcurrent_void);
282 current_property = enumerator_proxy_class.DefineProperty (
284 PropertyAttributes.RTSpecialName | PropertyAttributes.SpecialName,
285 TypeManager.object_type, null);
287 current_property.SetGetMethod (get_current_method);
289 ILGenerator ig = get_current_method.GetILGenerator ();
291 ig.Emit (OpCodes.Ldarg_0);
292 ig.Emit (OpCodes.Ldfld, pc_field);
293 ig.Emit (OpCodes.Ldc_I4_0);
294 Label return_current = ig.DefineLabel ();
295 ig.Emit (OpCodes.Bgt, return_current);
296 EmitThrowInvalidOp (ig);
298 ig.MarkLabel (return_current);
299 ig.Emit (OpCodes.Ldarg_0);
300 ig.Emit (OpCodes.Ldfld, current_field);
301 ig.Emit (OpCodes.Ret);
304 void Create_Dispose ()
306 dispose_method = enumerator_proxy_class.DefineMethod (
307 "System.IDisposable.Dispose",
308 MethodAttributes.HideBySig | MethodAttributes.SpecialName |
309 MethodAttributes.NewSlot | MethodAttributes.Virtual,
310 CallingConventions.HasThis, TypeManager.void_type, TypeManager.NoTypes);
311 enumerator_proxy_class.DefineMethodOverride (dispose_method, TypeManager.void_dispose_void);
312 ILGenerator ig = dispose_method.GetILGenerator ();
314 EmitYieldBreak (ig, false);
315 ig.Emit (OpCodes.Ret);
318 void Create_GetEnumerator ()
320 getenumerator_method = enumerable_proxy_class.DefineMethod (
321 "IEnumerable.GetEnumerator",
322 MethodAttributes.HideBySig | MethodAttributes.SpecialName |
323 MethodAttributes.NewSlot | MethodAttributes.Virtual,
324 CallingConventions.HasThis, TypeManager.ienumerator_type, TypeManager.NoTypes);
326 enumerable_proxy_class.DefineMethodOverride (getenumerator_method, TypeManager.ienumerable_getenumerator_void);
327 ILGenerator ig = getenumerator_method.GetILGenerator ();
329 ig.Emit (OpCodes.Newobj, (ConstructorInfo) enumerator_proxy_constructor);
330 if (enumerable_this_field != null || parameters.Count > 0){
331 LocalBuilder obj = ig.DeclareLocal (enumerator_proxy_class);
332 ig.Emit (OpCodes.Stloc, obj);
333 if (enumerable_this_field != null){
334 ig.Emit (OpCodes.Ldloc, obj);
335 ig.Emit (OpCodes.Ldarg_0);
336 ig.Emit (OpCodes.Ldfld, enumerable_this_field);
337 ig.Emit (OpCodes.Stfld, this_field);
340 for (int i = 0; i < parameters.Count; i++){
341 ig.Emit (OpCodes.Ldloc, obj);
342 ig.Emit (OpCodes.Ldarg_0);
343 ig.Emit (OpCodes.Ldfld, enumerable_parameter_fields [i]);
344 ig.Emit (OpCodes.Stfld, parameter_fields [i]);
346 ig.Emit (OpCodes.Ldloc, obj);
349 ig.Emit (OpCodes.Ret);
353 // Called back from Yield
355 public void MarkYield (EmitContext ec, Expression expr, bool in_exc)
357 ILGenerator ig = ec.ig;
359 // Store the new current
360 ig.Emit (OpCodes.Ldarg_0);
362 ig.Emit (OpCodes.Stfld, current_field);
366 ig.Emit (OpCodes.Ldarg_0);
367 IntConstant.EmitInt (ig, pc);
368 ig.Emit (OpCodes.Stfld, pc_field);
371 ig.Emit (OpCodes.Ldc_I4_1);
373 // Find out how to "leave"
374 if (in_exc || !ec.IsLastStatement){
375 Type old = ec.ReturnType;
376 ec.ReturnType = TypeManager.int32_type;
377 ig.Emit (OpCodes.Stloc, ec.TemporaryReturn ());
382 ec.NeedReturnLabel ();
383 ig.Emit (OpCodes.Leave, ec.ReturnLabel);
384 } else if (ec.IsLastStatement){
385 ig.Emit (OpCodes.Ret);
387 ec.NeedReturnLabel ();
388 ig.Emit (OpCodes.Br, ec.ReturnLabel);
391 Label resume_point = ig.DefineLabel ();
392 ig.MarkLabel (resume_point);
393 resume_labels.Add (resume_point);
397 // Creates the IEnumerator Proxy class
399 void MakeEnumeratorProxy ()
401 TypeExpr [] proxy_base_interfaces = new TypeExpr [2];
402 proxy_base_interfaces [0] = new TypeExpression (TypeManager.ienumerator_type, loc);
403 proxy_base_interfaces [1] = new TypeExpression (TypeManager.idisposable_type, loc);
404 Type [] proxy_base_itypes = new Type [2];
405 proxy_base_itypes [0] = TypeManager.ienumerator_type;
406 proxy_base_itypes [1] = TypeManager.idisposable_type;
407 TypeBuilder container_builder = container.TypeBuilder;
412 enumerator_proxy_class = container_builder.DefineNestedType (
413 "<Enumerator:" + name + ":" + (proxy_count++) + ">",
414 TypeAttributes.AutoLayout | TypeAttributes.Class |TypeAttributes.NestedPrivate,
415 TypeManager.object_type, proxy_base_itypes);
417 TypeManager.RegisterBuilder (enumerator_proxy_class, proxy_base_interfaces);
422 pc_field = enumerator_proxy_class.DefineField ("PC", TypeManager.int32_type, FieldAttributes.Assembly);
423 current_field = enumerator_proxy_class.DefineField ("current", IteratorType, FieldAttributes.Assembly);
424 if ((modifiers & Modifiers.STATIC) == 0)
425 this_field = enumerator_proxy_class.DefineField ("THIS", container.TypeBuilder, FieldAttributes.Assembly);
427 parameter_fields = new FieldBuilder [parameters.Count];
428 for (int i = 0; i < parameters.Count; i++){
429 parameter_fields [i] = enumerator_proxy_class.DefineField (
430 String.Format ("tor{0}_{1}", i, parameters.ParameterName (i)),
431 parameters.ParameterType (i), FieldAttributes.Assembly);
435 // Define a constructor
438 enumerator_proxy_constructor = enumerator_proxy_class.DefineConstructor (
439 MethodAttributes.Public | MethodAttributes.HideBySig |
440 MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
441 CallingConventions.HasThis, TypeManager.NoTypes);
442 InternalParameters parameter_info = new InternalParameters (
443 TypeManager.NoTypes, Parameters.EmptyReadOnlyParameters);
444 TypeManager.RegisterMethod (enumerator_proxy_constructor, parameter_info, TypeManager.NoTypes);
449 ILGenerator ig = enumerator_proxy_constructor.GetILGenerator ();
450 ig.Emit (OpCodes.Ldarg_0);
451 ig.Emit (OpCodes.Call, TypeManager.object_ctor);
453 ig.Emit (OpCodes.Ret);
457 // Creates the IEnumerable proxy class
459 void MakeEnumerableProxy ()
461 TypeBuilder container_builder = container.TypeBuilder;
462 Type [] proxy_base_interfaces = new Type [1];
463 proxy_base_interfaces [0] = TypeManager.ienumerable_type;
466 // Creates the Enumerable proxy class.
468 enumerable_proxy_class = container_builder.DefineNestedType (
469 "<Enumerable:" + name + ":" + (proxy_count++)+ ">",
470 TypeAttributes.AutoLayout | TypeAttributes.Class |TypeAttributes.NestedPublic,
471 TypeManager.object_type, proxy_base_interfaces);
476 if ((modifiers & Modifiers.STATIC) == 0){
477 enumerable_this_field = enumerable_proxy_class.DefineField (
478 "THIS", container.TypeBuilder, FieldAttributes.Assembly);
480 enumerable_parameter_fields = new FieldBuilder [parameters.Count];
481 for (int i = 0; i < parameters.Count; i++){
482 enumerable_parameter_fields [i] = enumerable_proxy_class.DefineField (
483 String.Format ("able{0}_{1}", i, parameters.ParameterName (i)),
484 parameters.ParameterType (i), FieldAttributes.Assembly);
487 enumerable_proxy_constructor = enumerable_proxy_class.DefineConstructor (
488 MethodAttributes.Public | MethodAttributes.HideBySig |
489 MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
490 CallingConventions.HasThis, TypeManager.NoTypes);
491 InternalParameters parameter_info = new InternalParameters (
492 TypeManager.NoTypes, Parameters.EmptyReadOnlyParameters);
493 TypeManager.RegisterMethod (enumerable_proxy_constructor, parameter_info, TypeManager.NoTypes);
495 ILGenerator ig = enumerable_proxy_constructor.GetILGenerator ();
496 ig.Emit (OpCodes.Ldarg_0);
497 ig.Emit (OpCodes.Call, TypeManager.object_ctor);
499 ig.Emit (OpCodes.Ret);
503 // Populates the Enumerator Proxy class
505 void PopulateProxy ()
507 RootContext.RegisterHelperClass (enumerator_proxy_class);
514 if (IsIEnumerable (return_type)){
515 Create_GetEnumerator ();
516 RootContext.RegisterHelperClass (enumerable_proxy_class);
522 // This is invoked by the EmitCode hook
524 void SetupIterator ()
532 public IteratorHandler (string name, TypeContainer container, Type return_type, Type [] param_types,
533 InternalParameters parameters, int modifiers, Location loc)
536 this.container = container;
537 this.return_type = return_type;
538 this.param_types = param_types;
539 this.parameters = parameters;
540 this.modifiers = modifiers;
543 IteratorType = TypeManager.object_type;
545 RootContext.EmitCodeHook += new RootContext.Hook (SetupIterator);
549 // This class is just an expression that evaluates to a type, and the
550 // type is our internal proxy class. Used in the generated new body
551 // of the original method
553 class NewInnerType : Expression {
554 IteratorHandler handler;
556 public NewInnerType (IteratorHandler handler, Location l)
558 this.handler = handler;
559 eclass = ExprClass.Value;
563 public override Expression DoResolve (EmitContext ec)
565 // Create the proxy class type.
566 handler.MakeEnumeratorProxy ();
568 if (IsIEnumerable (handler.return_type))
569 handler.MakeEnumerableProxy ();
571 type = handler.return_type;
575 public override Expression ResolveAsTypeStep (EmitContext ec)
577 return DoResolve (ec);
580 public override void Emit (EmitContext ec)
582 ILGenerator ig = ec.ig;
583 FieldBuilder this_field = null;
584 bool is_ienumerable = IsIEnumerable (handler.return_type);
588 temp_type = handler.enumerable_proxy_class;
589 ig.Emit (OpCodes.Newobj, (ConstructorInfo) handler.enumerable_proxy_constructor);
590 this_field = handler.enumerable_this_field;
592 temp_type = handler.enumerator_proxy_class;
593 ig.Emit (OpCodes.Newobj, (ConstructorInfo) handler.enumerator_proxy_constructor);
594 this_field = handler.this_field;
597 if (this_field == null && handler.parameters.Count == 0)
600 LocalBuilder temp = ec.GetTemporaryLocal (temp_type);
602 ig.Emit (OpCodes.Stloc, temp);
607 if (this_field != null){
608 ig.Emit (OpCodes.Ldloc, temp);
609 ig.Emit (OpCodes.Ldarg_0);
610 if (handler.container is Struct)
611 ig.Emit (OpCodes.Ldobj, handler.container.TypeBuilder);
612 ig.Emit (OpCodes.Stfld, this_field);
616 for (int i = 0; i < handler.parameters.Count; i++){
617 ig.Emit (OpCodes.Ldloc, temp);
618 ParameterReference.EmitLdArg (ig, i + first);
620 ig.Emit (OpCodes.Stfld, handler.enumerable_parameter_fields [i]);
622 ig.Emit (OpCodes.Stfld, handler.parameter_fields [i]);
628 ig.Emit (OpCodes.Ldloc, temp);
629 ec.FreeTemporaryLocal (temp, handler.container.TypeBuilder);
634 // This return statement tricks return into not flagging an error for being
635 // used in a Yields method
637 class NoCheckReturn : Return {
638 public NoCheckReturn (Expression expr, Location loc) : base (expr, loc)
642 public override bool Resolve (EmitContext ec)
644 ec.InIterator = false;
645 bool ret_val = base.Resolve (ec);
646 ec.InIterator = true;
652 static bool IsIEnumerable (Type t)
654 return t == TypeManager.ienumerable_type || TypeManager.ImplementsInterface (t, TypeManager.ienumerable_type);
657 static bool IsIEnumerator (Type t)
659 return t == TypeManager.ienumerator_type || TypeManager.ImplementsInterface (t, TypeManager.ienumerator_type);
663 // Returns the new block for the method, or null on failure
665 public Block Setup (Block block)
667 if (!(IsIEnumerator (return_type) || IsIEnumerable (return_type))){
669 -205, loc, String.Format (
670 "The method `{0}' contains a yield statement, but has an invalid return type for an iterator `{1}'",
671 name, TypeManager.CSharpName (return_type)));
675 for (int i = 0; i < parameters.Count; i++){
676 Parameter.Modifier mod = parameters.ParameterModifier (i);
677 if ((mod & (Parameter.Modifier.REF | Parameter.Modifier.OUT)) != 0){
678 Report.Error (-207, loc, String.Format (
679 "Parameter {0} of `{1}' is {2} and not allowed for an iterator method",
680 i+1, name, parameters.ParameterDesc (i)));
685 original_block = block;
686 Block b = new Block (null);
688 // return new InnerClass ()
689 b.AddStatement (new NoCheckReturn (new NewInnerType (this, loc), loc));