File: new-anonymous-design.txt

package info (click to toggle)
mono 1.2.2.1-1etch1
  • links: PTS
  • area: main
  • in suites: etch
  • size: 142,720 kB
  • ctags: 256,408
  • sloc: cs: 1,495,736; ansic: 249,442; sh: 18,327; xml: 12,463; makefile: 5,046; perl: 1,248; asm: 635; yacc: 285; sql: 7
file content (197 lines) | stat: -rw-r--r-- 7,490 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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.