2009-08-04 Marek Safar <marek.safar@gmail.com>
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Scripting.Core / Actions / BindingRestrictions.cs
1 /* ****************************************************************************\r
2  *\r
3  * Copyright (c) Microsoft Corporation. \r
4  *\r
5  * This source code is subject to terms and conditions of the Microsoft Public License. A \r
6  * copy of the license can be found in the License.html file at the root of this distribution. If \r
7  * you cannot locate the  Microsoft Public License, please send an email to \r
8  * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound \r
9  * by the terms of the Microsoft Public License.\r
10  *\r
11  * You must not remove this notice, or any other, from this software.\r
12  *\r
13  *\r
14  * ***************************************************************************/\r
15 using System; using Microsoft;\r
16 \r
17 \r
18 using System.Collections.Generic;\r
19 using System.Diagnostics;\r
20 #if CODEPLEX_40\r
21 using System.Dynamic.Utils;\r
22 using System.Linq.Expressions;\r
23 #else\r
24 using Microsoft.Scripting.Utils;\r
25 using Microsoft.Linq.Expressions;\r
26 #endif\r
27 using System.Runtime.CompilerServices;\r
28 #if !CODEPLEX_40
29 using Microsoft.Runtime.CompilerServices;
30 #endif
31 \r
32 \r
33 #if CODEPLEX_40\r
34 namespace System.Dynamic {\r
35 #else\r
36 namespace Microsoft.Scripting {\r
37 #endif\r
38 \r
39     /// <summary>\r
40     /// Represents a set of binding restrictions on the <see cref="DynamicMetaObject"/>under which the dynamic binding is valid.\r
41     /// </summary>\r
42 #if !SILVERLIGHT\r
43     [DebuggerTypeProxy(typeof(BindingRestrictionsProxy)), DebuggerDisplay("{DebugView}")]\r
44 #endif\r
45     public abstract class BindingRestrictions {\r
46         /// <summary>\r
47         /// Represents an empty set of binding restrictions. This field is read only.\r
48         /// </summary>\r
49         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]\r
50         public static readonly BindingRestrictions Empty = new CustomRestriction(Expression.Constant(true));\r
51 \r
52         private const int TypeRestrictionHash = 0x10000000;\r
53         private const int InstanceRestrictionHash = 0x20000000;\r
54         private const int CustomRestrictionHash = 0x40000000;\r
55         \r
56         private BindingRestrictions() {\r
57         }\r
58 \r
59         // Overridden by specialized subclasses\r
60         internal abstract Expression GetExpression();\r
61 \r
62         /// <summary>\r
63         /// Merges the set of binding restrictions with the current binding restrictions.\r
64         /// </summary>\r
65         /// <param name="restrictions">The set of restrictions with which to merge the current binding restrictions.</param>\r
66         /// <returns>The new set of binding restrictions.</returns>\r
67         public BindingRestrictions Merge(BindingRestrictions restrictions) {\r
68             ContractUtils.RequiresNotNull(restrictions, "restrictions");\r
69             if (this == Empty) {\r
70                 return restrictions;\r
71             }\r
72             if (restrictions == Empty) {\r
73                 return this;\r
74             }\r
75             return new MergedRestriction(this, restrictions);\r
76         }\r
77 \r
78         /// <summary>\r
79         /// Creates the binding restriction that check the expression for runtime type identity.\r
80         /// </summary>\r
81         /// <param name="expression">The expression to test.</param>\r
82         /// <param name="type">The exact type to test.</param>\r
83         /// <returns>The new binding restrictions.</returns>\r
84         public static BindingRestrictions GetTypeRestriction(Expression expression, Type type) {\r
85             ContractUtils.RequiresNotNull(expression, "expression");\r
86             ContractUtils.RequiresNotNull(type, "type");\r
87 \r
88             return new TypeRestriction(expression, type);\r
89         }\r
90 \r
91         /// <summary>\r
92         /// The method takes a DynamicMetaObject, and returns an instance restriction for testing null if the object\r
93         /// holds a null value, otherwise returns a type restriction.\r
94         /// </summary>\r
95         internal static BindingRestrictions GetTypeRestriction(DynamicMetaObject obj) {\r
96             if (obj.Value == null && obj.HasValue) {\r
97                 return BindingRestrictions.GetInstanceRestriction(obj.Expression, null);\r
98             } else {\r
99                 return BindingRestrictions.GetTypeRestriction(obj.Expression, obj.LimitType);\r
100             }\r
101         }\r
102 \r
103         /// <summary>\r
104         /// Creates the binding restriction that checks the expression for object instance identity.\r
105         /// </summary>\r
106         /// <param name="expression">The expression to test.</param>\r
107         /// <param name="instance">The exact object instance to test.</param>\r
108         /// <returns>The new binding restrictions.</returns>\r
109         public static BindingRestrictions GetInstanceRestriction(Expression expression, object instance) {\r
110             ContractUtils.RequiresNotNull(expression, "expression");\r
111 \r
112             return new InstanceRestriction(expression, instance);\r
113         }\r
114 \r
115         /// <summary>\r
116         /// Creates the binding restriction that checks the expression for arbitrary immutable properties.\r
117         /// </summary>\r
118         /// <param name="expression">The expression expression the restrictions.</param>\r
119         /// <returns>The new binding restrictions.</returns>\r
120         /// <remarks>\r
121         /// By convention, the general restrictions created by this method must only test\r
122         /// immutable object properties.\r
123         /// </remarks>\r
124         public static BindingRestrictions GetExpressionRestriction(Expression expression) {\r
125             ContractUtils.RequiresNotNull(expression, "expression");\r
126             ContractUtils.Requires(expression.Type == typeof(bool), "expression");\r
127             return new CustomRestriction(expression);\r
128         }\r
129 \r
130         /// <summary>\r
131         /// Combines binding restrictions from the list of <see cref="DynamicMetaObject"/> instances into one set of restrictions.\r
132         /// </summary>\r
133         /// <param name="contributingObjects">The list of <see cref="DynamicMetaObject"/> instances from which to combine restrictions.</param>\r
134         /// <returns>The new set of binding restrictions.</returns>\r
135         public static BindingRestrictions Combine(IList<DynamicMetaObject> contributingObjects) {\r
136             BindingRestrictions res = BindingRestrictions.Empty;\r
137             if (contributingObjects != null) {\r
138                 foreach (DynamicMetaObject mo in contributingObjects) {\r
139                     if (mo != null) {\r
140                         res = res.Merge(mo.Restrictions);\r
141                     }\r
142                 }\r
143             }\r
144             return res;\r
145         }\r
146 \r
147         /// <summary>\r
148         /// Builds a balanced tree of AndAlso nodes.\r
149         /// We do this so the compiler won't stack overflow if we have many\r
150         /// restrictions.\r
151         /// </summary>\r
152         private sealed class TestBuilder {\r
153             private readonly Set<BindingRestrictions> _unique = new Set<BindingRestrictions>();\r
154             private readonly Stack<AndNode> _tests = new Stack<AndNode>();\r
155 \r
156             private struct AndNode {\r
157                 internal int Depth;\r
158                 internal Expression Node;\r
159             }\r
160 \r
161             internal void Append(BindingRestrictions restrictions) {\r
162                 if (_unique.Contains(restrictions)) {\r
163                     return;\r
164                 }\r
165                 _unique.Add(restrictions);\r
166 \r
167                 Push(restrictions.GetExpression(), 0);\r
168             }\r
169 \r
170             internal Expression ToExpression() {\r
171                 Expression result = _tests.Pop().Node;\r
172                 while (_tests.Count > 0) {\r
173                     result = Expression.AndAlso(_tests.Pop().Node, result);\r
174                 }\r
175                 return result;\r
176             }\r
177 \r
178             private void Push(Expression node, int depth) {\r
179                 while (_tests.Count > 0 && _tests.Peek().Depth == depth) {\r
180                     node = Expression.AndAlso(_tests.Pop().Node, node);\r
181                     depth++;\r
182                 }\r
183                 _tests.Push(new AndNode { Node = node, Depth = depth });\r
184             }\r
185         }\r
186 \r
187         /// <summary>\r
188         /// Creates the <see cref="Expression"/> representing the binding restrictions.\r
189         /// </summary>\r
190         /// <returns>The expression tree representing the restrictions.</returns>\r
191         public Expression ToExpression() {\r
192             // We could optimize this better, e.g. common subexpression elimination\r
193             // But for now, it's good enough.\r
194 \r
195             if (this == Empty) {\r
196                 return Expression.Constant(true);\r
197             }\r
198 \r
199             var testBuilder = new TestBuilder();\r
200 \r
201             // Visit the tree, left to right.\r
202             // Use an explicit stack so we don't stack overflow.\r
203             //\r
204             // Left-most node is on top of the stack, so we always expand the\r
205             // left most node each iteration.\r
206             var stack = new Stack<BindingRestrictions>();\r
207             stack.Push(this);\r
208             do {\r
209                 var top = stack.Pop();\r
210                 var m = top as MergedRestriction;\r
211                 if (m != null) {\r
212                     stack.Push(m.Right);\r
213                     stack.Push(m.Left);\r
214                 } else {\r
215                     testBuilder.Append(top);\r
216                 }\r
217             } while (stack.Count > 0);\r
218 \r
219             return testBuilder.ToExpression();\r
220         }\r
221 \r
222         private sealed class MergedRestriction : BindingRestrictions {\r
223             internal readonly BindingRestrictions Left;\r
224             internal readonly BindingRestrictions Right;\r
225 \r
226             internal MergedRestriction(BindingRestrictions left, BindingRestrictions right) {\r
227                 Left = left;\r
228                 Right = right;\r
229             }\r
230             internal override Expression GetExpression() {\r
231                 throw ContractUtils.Unreachable;\r
232             }\r
233         }\r
234 \r
235         private sealed class CustomRestriction : BindingRestrictions {\r
236             private readonly Expression _expression;\r
237 \r
238             internal CustomRestriction(Expression expression) {\r
239                 _expression = expression;\r
240             }\r
241 \r
242             public override bool Equals(object obj) {\r
243                 var other = obj as CustomRestriction;\r
244                 return other != null && other._expression == _expression;\r
245             }\r
246 \r
247             public override int GetHashCode() {\r
248                 return CustomRestrictionHash ^ _expression.GetHashCode();\r
249             }\r
250 \r
251             internal override Expression GetExpression() {\r
252                 return _expression;\r
253             }\r
254         }\r
255 \r
256         private sealed class TypeRestriction : BindingRestrictions {\r
257             private readonly Expression _expression;\r
258             private readonly Type _type;\r
259 \r
260             internal TypeRestriction(Expression parameter, Type type) {\r
261                 _expression = parameter;\r
262                 _type = type;\r
263             }\r
264 \r
265             public override bool Equals(object obj) {\r
266                 var other = obj as TypeRestriction;\r
267                 return other != null && TypeUtils.AreEquivalent(other._type, _type) && other._expression == _expression;\r
268             }\r
269 \r
270             public override int GetHashCode() {\r
271                 return TypeRestrictionHash ^ _expression.GetHashCode() ^ _type.GetHashCode();\r
272             }\r
273 \r
274             internal override Expression GetExpression() {\r
275                 return Expression.TypeEqual(_expression, _type);\r
276             }\r
277         }\r
278 \r
279         private sealed class InstanceRestriction : BindingRestrictions {\r
280             private readonly Expression _expression;\r
281             private readonly object _instance;\r
282 \r
283             internal InstanceRestriction(Expression parameter, object instance) {\r
284                 _expression = parameter;\r
285                 _instance = instance;\r
286             }\r
287 \r
288             public override bool Equals(object obj) {\r
289                 var other = obj as InstanceRestriction;\r
290                 return other != null && other._instance == _instance && other._expression == _expression;\r
291             }\r
292 \r
293             public override int GetHashCode() {\r
294                 return InstanceRestrictionHash ^ RuntimeHelpers.GetHashCode(_instance) ^ _expression.GetHashCode();\r
295             }\r
296 \r
297             internal override Expression GetExpression() {\r
298                 if (_instance == null) {\r
299                     return Expression.Equal(\r
300                         Expression.Convert(_expression, typeof(object)),\r
301                         Expression.Constant(null)\r
302                     );\r
303                 }\r
304 \r
305                 ParameterExpression temp = Expression.Parameter(typeof(object), null);\r
306                 return Expression.Block(\r
307                     new[] { temp },\r
308                     Expression.Assign(\r
309                         temp,\r
310                         Expression.Property(\r
311                             Expression.Constant(new WeakReference(_instance)),\r
312                             typeof(WeakReference).GetProperty("Target")\r
313                         )\r
314                     ),\r
315                     Expression.AndAlso(\r
316                     //check that WeekReference was not collected.\r
317                         Expression.NotEqual(temp, Expression.Constant(null)),\r
318                         Expression.Equal(\r
319                             Expression.Convert(_expression, typeof(object)),\r
320                             temp\r
321                         )\r
322                     )\r
323                 );\r
324             }\r
325         }\r
326 \r
327         private string DebugView {\r
328             get { return ToExpression().ToString(); }\r
329         }\r
330 \r
331         private sealed class BindingRestrictionsProxy {\r
332             private readonly BindingRestrictions _node;\r
333 \r
334             public BindingRestrictionsProxy(BindingRestrictions node) {\r
335                 _node = node;\r
336             }\r
337 \r
338             public bool IsEmpty {\r
339                 get { return _node == Empty; }\r
340             }\r
341 \r
342             public Expression Test {\r
343                 get { return _node.ToExpression(); }\r
344             }\r
345 \r
346             public BindingRestrictions[] Restrictions {\r
347                 get {\r
348                     var restrictions = new List<BindingRestrictions>();\r
349 \r
350                     // Visit the tree, left to right\r
351                     //\r
352                     // Left-most node is on top of the stack, so we always expand the\r
353                     // left most node each iteration.\r
354                     var stack = new Stack<BindingRestrictions>();\r
355                     stack.Push(_node);\r
356                     do {\r
357                         var top = stack.Pop();\r
358                         var m = top as MergedRestriction;\r
359                         if (m != null) {\r
360                             stack.Push(m.Right);\r
361                             stack.Push(m.Left);\r
362                         } else {\r
363                             restrictions.Add(top);\r
364                         }\r
365                     } while (stack.Count > 0);\r
366 \r
367                     return restrictions.ToArray();\r
368                 }\r
369             }\r
370 \r
371             public override string ToString() {\r
372                 // To prevent fxcop warning about this field\r
373                 return _node.DebugView;\r
374             }\r
375         }\r
376     }\r
377 }\r