diff options
author | Martin Baulig <martin@novell.com> | 2006-09-12 20:38:26 +0400 |
---|---|---|
committer | Martin Baulig <martin@novell.com> | 2006-09-12 20:38:26 +0400 |
commit | ec4c7e629828e99f7d0d9b02aecfc97f7bdc5317 (patch) | |
tree | 76285df1704639d5819ae798bc31364738649a0f /mcs/docs | |
parent | 40640866eb802a902747ce3628317366cc2026cf (diff) |
Added a document describing the new anonymous methods design.
svn path=/trunk/mcs/; revision=65328
Diffstat (limited to 'mcs/docs')
-rw-r--r-- | mcs/docs/new-anonymous-design.txt | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/mcs/docs/new-anonymous-design.txt b/mcs/docs/new-anonymous-design.txt new file mode 100644 index 00000000000..402654b5f82 --- /dev/null +++ b/mcs/docs/new-anonymous-design.txt @@ -0,0 +1,197 @@ +Anonymous Methods and the TypeContainer resolve order +----------------------------------------------------- + +Anonymous methods add another resolving pass to the TypeContainer framework. +The new code works like this: + +* Everything which may contain anonymous methods or iterators now + implements the `IAnonymousHost' interface. This applies to + `Method', `Constructor', `Accessor' and `Operator'. + + We can already determine whether or not a method contains anonymous + methods or iterators at parsing time, but we can't determine their + types yet. If we encounter an anonymous method or iterator while + parsing, we add the information to the current `IAnonymousHost'. + + This means that at the end of the parsing stage, we already know + about all anonymous methods and iterators, but didn't resolve them + yet. + +* After parsing, RootContext.ResolveTree() calls DefineType() on all + TypeContainers. + +* Inside TypeContainer.DefineType(), we do the following: + + - first we have to create our TypeBuilder via DefineTypeBuilder(). + + - after that, we scan all methods, constructors, operators and + property/indexer accessors for anonymous methods and iterators. + + For each method which either contains anonymous methods or is + implemented as iterator, we create a new helper class (the "root + scope" of the anonymous method) and add it to the current type as + a nested class. + + This is done by the new TypeContainer.ResolveMembers() method. + + - when done, we call DefineNestedTypes() to descend into our nested + children. + +* RootContext.PopulateTypes() calls TypeContainer.ResolveType() and + TypeContainer.DefineMembers() as usual and populates everything. + +* In TypeContainer.EmitType(), we call DefineMembers() and EmitType() + on all our CompilerGeneratedClass'es once we're done emitting the + current type. + +One of the hardest parts of the new anonymous methods implementation +was getting this resolve order right. It may sound complicated, but +there are reasons why it's done this way. + +Let's have a look at a small example: + + ===== + delegate void Foo (); + + class X { + public void Hello<U> (U u) + + public void Test<T> (T t) + { + T u = t; + Hello (u); + Foo foo = delegate { + Hello (u); + }; + foo (); + } + } + ===== + +After parsing this file, we already know that Test() contains an +anonymous method, but we don't know its type until resolving it. + +Because Test() is a generic method, we need to create a generic helper +class and then transform all method type parameters into class type +parameters. + +One key feature of the new code is using the normal TypeContainer +framework to create and use generic classes. For each method +containing anonymous methods, we create one "root scope" which deals +with generics and also hosts any captured parameter and `this'. + +In this example, this is done when calling DefineType() on `X's +TypeContainer, during the ResolveMembers() pass. After that, we can +handle the helper classes just like normal nested classes, so +DefineNestedTypes() creates their TypeBuilders. + +One important thing to keep in mind is that we neither know the type +of the anonymous methods nor any captured variables until resolving +`Test'. Note that a method's block isn't resolved until +TypeContainer.EmitCode(), so we can't call DefineMembers() on our +CompilerGeneratedClass'es until we emitted all methods. + + +Anonymous Methods and Scopes: +----------------------------- + +The new code fundamentally changes the concept of CaptureContexts and +ScopeInfos. CaptureContext is completely gone while the ScopeInfo has +been completely redesigned. + +Each method containing anonymous methods introduces a "root scope" in +which all other scopes are nested. This root scope is also called the +anonymous method's host (class `AnonymousMethodHost' in anonymous.cs). + +The root scope deals with everything related to generics and also +hosts the parameters and `this'. All other scopes are nested inside +the root scope. + +Note that if you have child scopes, they're all nested directly inside +the root scope, not inside each other. Because of that, we don't need +to link / reparent them. + +Anonymous Methods and Generics: +------------------------------- + +Creating and consuming generic types is very difficult and you have to +follow certain rules to do it right (the most important one is that +you may not use the class until it's fully created). + +GMCS already has working code to do that - and one very important +policy in the new anonymous methods code is that it must not interfer +with GMCS's way of resolving and defining generic types; ie. everything +related to generics is handled during the normal TypeContainer +resolving process. + +When the anonymous methods code kicks in, all the generic types are +already defined and ready for use. + +Adding a new non-generic class to such a generic type is really easy +and not a problem - non-generic means that the new class does not +introduce any new type parameters; it may still use its containing +class'es type parameters: + +Example: + + class IAmGeneric<T> + { + class IAmNot // must derive from System.Object + { + // using the containing classe's type parameter is ok. + public T ButMayStillUseMyParentsT; + } + } + + +The new `Variable' abstraction: +------------------------------- + +There is a new `Variable' abstraction which is used for locals and +parameters; all the knowledge about how to access a variable and +whether it's captured or not is now in that new abstract `Variable' +class. The `LocalVariableReference' and `ParameterReference' now +share most of their code and have a common `VariableReference' base +class, which is also used by `This'. + +`Variable' also controls whether or not we need to create a temporary +copy of a variable. + +Before emitting any method, we scan over all its parameters and local +variables again and check whether any of them have been captured. + +`Parameter' and `LocalInfo' both have a new ResolveVariable() method +which creates an instance of the new `Variable' class for each of +them. + +If we're captured, a `Field' has already been created for the variable +and since we're called during the normal TypeContainer resolve / emit +process, there' no additional "magic" required; it "just works". + + CAUTION: Inside the anonymous method, the `Variable's type + determines the variable's actual type - outside it + is the ParameterReference / LocalVariableReference's + type ! + + To make it more clear: + + The type of a ParameterReference / LocalVariableReference + depends upon whether we're inside our outside the anonymous + method - and in case of generic, they are different !!! + + The normal situation is that outside the anonymous method, + we may use the generic method parameters directly (ie. + MONO_TYPE_MVAR) - but inside the anonymous method, we're in + and generic class, not a generic method - so it's a generic + type parameter (MONO_TYPE_VAR). + + There are several tests for this in my new test suite. + + This does not only apply to variables; it's the same for types - + the same `T' may mean a completely different type depending upon + whether we're inside or outside the anonymous method: outside, + it's a generic method parameter (MONO_TYPE_MVAR) and inside, it's + a generic type parameter (MONO_TYPE_VAR) - so we already need to + handle this in the EmitContext to make SimpleNameResolve work. + + |