1 Anonymous Methods and the TypeContainer resolve order
2 -----------------------------------------------------
4 Anonymous methods add another resolving pass to the TypeContainer framework.
5 The new code works like this:
7 * Everything which may contain anonymous methods or iterators now
8 implements the `IAnonymousHost' interface. This applies to
9 `Method', `Constructor', `Accessor' and `Operator'.
11 We can already determine whether or not a method contains anonymous
12 methods or iterators at parsing time, but we can't determine their
13 types yet. If we encounter an anonymous method or iterator while
14 parsing, we add the information to the current `IAnonymousHost'.
16 This means that at the end of the parsing stage, we already know
17 about all anonymous methods and iterators, but didn't resolve them
20 * After parsing, RootContext.ResolveTree() calls DefineType() on all
23 * Inside TypeContainer.DefineType(), we do the following:
25 - first we have to create our TypeBuilder via DefineTypeBuilder().
27 - after that, we scan all methods, constructors, operators and
28 property/indexer accessors for anonymous methods and iterators.
30 For each method which either contains anonymous methods or is
31 implemented as iterator, we create a new helper class (the "root
32 scope" of the anonymous method) and add it to the current type as
35 This is done by the new TypeContainer.ResolveMembers() method.
37 - when done, we call DefineNestedTypes() to descend into our nested
40 * RootContext.PopulateTypes() calls TypeContainer.ResolveType() and
41 TypeContainer.DefineMembers() as usual and populates everything.
43 * In TypeContainer.EmitType(), we call DefineMembers() and EmitType()
44 on all our CompilerGeneratedClass'es once we're done emitting the
47 One of the hardest parts of the new anonymous methods implementation
48 was getting this resolve order right. It may sound complicated, but
49 there are reasons why it's done this way.
51 Let's have a look at a small example:
57 public void Hello<U> (U u)
59 public void Test<T> (T t)
71 After parsing this file, we already know that Test() contains an
72 anonymous method, but we don't know its type until resolving it.
74 Because Test() is a generic method, we need to create a generic helper
75 class and then transform all method type parameters into class type
78 One key feature of the new code is using the normal TypeContainer
79 framework to create and use generic classes. For each method
80 containing anonymous methods, we create one "root scope" which deals
81 with generics and also hosts any captured parameter and `this'.
83 In this example, this is done when calling DefineType() on `X's
84 TypeContainer, during the ResolveMembers() pass. After that, we can
85 handle the helper classes just like normal nested classes, so
86 DefineNestedTypes() creates their TypeBuilders.
88 One important thing to keep in mind is that we neither know the type
89 of the anonymous methods nor any captured variables until resolving
90 `Test'. Note that a method's block isn't resolved until
91 TypeContainer.EmitCode(), so we can't call DefineMembers() on our
92 CompilerGeneratedClass'es until we emitted all methods.
95 Anonymous Methods and Scopes:
96 -----------------------------
98 The new code fundamentally changes the concept of CaptureContexts and
99 ScopeInfos. CaptureContext is completely gone while the ScopeInfo has
100 been completely redesigned.
102 Each method containing anonymous methods introduces a "root scope" in
103 which all other scopes are nested. This root scope is also called the
104 anonymous method's host (class `AnonymousMethodHost' in anonymous.cs).
106 The root scope deals with everything related to generics and also
107 hosts the parameters and `this'. All other scopes are nested inside
110 Note that if you have child scopes, they're all nested directly inside
111 the root scope, not inside each other. Because of that, we don't need
112 to link / reparent them.
114 Anonymous Methods and Generics:
115 -------------------------------
117 Creating and consuming generic types is very difficult and you have to
118 follow certain rules to do it right (the most important one is that
119 you may not use the class until it's fully created).
121 GMCS already has working code to do that - and one very important
122 policy in the new anonymous methods code is that it must not interfer
123 with GMCS's way of resolving and defining generic types; ie. everything
124 related to generics is handled during the normal TypeContainer
127 When the anonymous methods code kicks in, all the generic types are
128 already defined and ready for use.
130 Adding a new non-generic class to such a generic type is really easy
131 and not a problem - non-generic means that the new class does not
132 introduce any new type parameters; it may still use its containing
133 class'es type parameters:
139 class IAmNot // must derive from System.Object
141 // using the containing classe's type parameter is ok.
142 public T ButMayStillUseMyParentsT;
147 The new `Variable' abstraction:
148 -------------------------------
150 There is a new `Variable' abstraction which is used for locals and
151 parameters; all the knowledge about how to access a variable and
152 whether it's captured or not is now in that new abstract `Variable'
153 class. The `LocalVariableReference' and `ParameterReference' now
154 share most of their code and have a common `VariableReference' base
155 class, which is also used by `This'.
157 `Variable' also controls whether or not we need to create a temporary
160 Before emitting any method, we scan over all its parameters and local
161 variables again and check whether any of them have been captured.
163 `Parameter' and `LocalInfo' both have a new ResolveVariable() method
164 which creates an instance of the new `Variable' class for each of
167 If we're captured, a `Field' has already been created for the variable
168 and since we're called during the normal TypeContainer resolve / emit
169 process, there' no additional "magic" required; it "just works".
171 CAUTION: Inside the anonymous method, the `Variable's type
172 determines the variable's actual type - outside it
173 is the ParameterReference / LocalVariableReference's
176 To make it more clear:
178 The type of a ParameterReference / LocalVariableReference
179 depends upon whether we're inside our outside the anonymous
180 method - and in case of generic, they are different !!!
182 The normal situation is that outside the anonymous method,
183 we may use the generic method parameters directly (ie.
184 MONO_TYPE_MVAR) - but inside the anonymous method, we're in
185 and generic class, not a generic method - so it's a generic
186 type parameter (MONO_TYPE_VAR).
188 There are several tests for this in my new test suite.
190 This does not only apply to variables; it's the same for types -
191 the same `T' may mean a completely different type depending upon
192 whether we're inside or outside the anonymous method: outside,
193 it's a generic method parameter (MONO_TYPE_MVAR) and inside, it's
194 a generic type parameter (MONO_TYPE_VAR) - so we already need to
195 handle this in the EmitContext to make SimpleNameResolve work.