Merge pull request #439 from mono-soc-2012/garyb/iconfix
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Scripting.Core / Ast / SwitchExpression.cs
1 /* ****************************************************************************
2  *
3  * Copyright (c) Microsoft Corporation. 
4  *
5  * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
6  * copy of the license can be found in the License.html file at the root of this distribution. If 
7  * you cannot locate the  Apache License, Version 2.0, please send an email to 
8  * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
9  * by the terms of the Apache License, Version 2.0.
10  *
11  * You must not remove this notice, or any other, from this software.
12  *
13  *
14  * ***************************************************************************/
15
16 using System;
17 using System.Collections.Generic;
18 using System.Collections.ObjectModel;
19 using System.Diagnostics;
20 using System.Dynamic.Utils;
21 using System.Reflection;
22
23 #if !FEATURE_CORE_DLR
24 namespace Microsoft.Scripting.Ast {
25 #else
26 namespace System.Linq.Expressions {
27 #endif
28     /// <summary>
29     /// Represents a control expression that handles multiple selections by passing control to a <see cref="SwitchCase"/>.
30     /// </summary>
31     [DebuggerTypeProxy(typeof(Expression.SwitchExpressionProxy))]
32     public sealed class SwitchExpression : Expression {
33         private readonly Type _type;
34         private readonly Expression _switchValue;
35         private readonly ReadOnlyCollection<SwitchCase> _cases;
36         private readonly Expression _defaultBody;
37         private readonly MethodInfo _comparison;
38
39         internal SwitchExpression(Type type, Expression switchValue, Expression defaultBody, MethodInfo comparison, ReadOnlyCollection<SwitchCase> cases) {
40             _type = type;
41             _switchValue = switchValue;
42             _defaultBody = defaultBody;
43             _comparison = comparison;
44             _cases = cases;
45         }
46
47         /// <summary>
48         /// Gets the static type of the expression that this <see cref="Expression" /> represents.
49         /// </summary>
50         /// <returns>The <see cref="Type"/> that represents the static type of the expression.</returns>
51         public sealed override Type Type {
52             get { return _type; }
53         }
54
55         /// <summary>
56         /// Returns the node type of this Expression. Extension nodes should return
57         /// ExpressionType.Extension when overriding this method.
58         /// </summary>
59         /// <returns>The <see cref="ExpressionType"/> of the expression.</returns>
60         public sealed override ExpressionType NodeType {
61             get { return ExpressionType.Switch; }
62         }
63
64         /// <summary>
65         /// Gets the test for the switch.
66         /// </summary>
67         public Expression SwitchValue {
68             get { return _switchValue; }
69         }
70
71         /// <summary>
72         /// Gets the collection of <see cref="SwitchCase"/> objects for the switch.
73         /// </summary>
74         public ReadOnlyCollection<SwitchCase> Cases {
75             get { return _cases; }
76         }
77
78         /// <summary>
79         /// Gets the test for the switch.
80         /// </summary>
81         public Expression DefaultBody {
82             get { return _defaultBody; }
83         }
84
85         /// <summary>
86         /// Gets the equality comparison method, if any.
87         /// </summary>
88         public MethodInfo Comparison {
89             get { return _comparison; }
90         }
91
92         /// <summary>
93         /// Dispatches to the specific visit method for this node type.
94         /// </summary>
95         protected internal override Expression Accept(ExpressionVisitor visitor) {
96             return visitor.VisitSwitch(this);
97         }
98
99         internal bool IsLifted {
100             get {
101                 if (_switchValue.Type.IsNullableType()) {
102                     return (_comparison == null) ||
103                         !TypeUtils.AreEquivalent(_switchValue.Type, _comparison.GetParametersCached()[0].ParameterType.GetNonRefType());
104                 }
105                 return false;
106             }
107         }
108
109         /// <summary>
110         /// Creates a new expression that is like this one, but using the
111         /// supplied children. If all of the children are the same, it will
112         /// return this expression.
113         /// </summary>
114         /// <param name="switchValue">The <see cref="SwitchValue" /> property of the result.</param>
115         /// <param name="cases">The <see cref="Cases" /> property of the result.</param>
116         /// <param name="defaultBody">The <see cref="DefaultBody" /> property of the result.</param>
117         /// <returns>This expression if no children changed, or an expression with the updated children.</returns>
118         public SwitchExpression Update(Expression switchValue, IEnumerable<SwitchCase> cases, Expression defaultBody) {
119             if (switchValue == SwitchValue && cases == Cases && defaultBody == DefaultBody) {
120                 return this;
121             }
122             return Expression.Switch(Type, switchValue, defaultBody, Comparison, cases);
123         }
124     }
125
126     public partial class Expression {
127         /// <summary>
128         /// Creates a <see cref="SwitchExpression"/>.
129         /// </summary>
130         /// <param name="switchValue">The value to be tested against each case.</param>
131         /// <param name="cases">The valid cases for this switch.</param>
132         /// <returns>The created <see cref="SwitchExpression"/>.</returns>
133         public static SwitchExpression Switch(Expression switchValue, params SwitchCase[] cases) {
134             return Switch(switchValue, null, null, (IEnumerable<SwitchCase>)cases);
135         }
136
137         /// <summary>
138         /// Creates a <see cref="SwitchExpression"/>.
139         /// </summary>
140         /// <param name="switchValue">The value to be tested against each case.</param>
141         /// <param name="defaultBody">The result of the switch if no cases are matched.</param>
142         /// <param name="cases">The valid cases for this switch.</param>
143         /// <returns>The created <see cref="SwitchExpression"/>.</returns>
144         public static SwitchExpression Switch(Expression switchValue, Expression defaultBody, params SwitchCase[] cases) {
145             return Switch(switchValue, defaultBody, null, (IEnumerable<SwitchCase>)cases);
146         }
147
148         /// <summary>
149         /// Creates a <see cref="SwitchExpression"/>.
150         /// </summary>
151         /// <param name="switchValue">The value to be tested against each case.</param>
152         /// <param name="defaultBody">The result of the switch if no cases are matched.</param>
153         /// <param name="comparison">The equality comparison method to use.</param>
154         /// <param name="cases">The valid cases for this switch.</param>
155         /// <returns>The created <see cref="SwitchExpression"/>.</returns>
156         public static SwitchExpression Switch(Expression switchValue, Expression defaultBody, MethodInfo comparison, params SwitchCase[] cases) {
157             return Switch(switchValue, defaultBody, comparison, (IEnumerable<SwitchCase>)cases);
158         }
159
160         /// <summary>
161         /// Creates a <see cref="SwitchExpression"/>.
162         /// </summary>
163         /// <param name="type">The result type of the switch.</param>
164         /// <param name="switchValue">The value to be tested against each case.</param>
165         /// <param name="defaultBody">The result of the switch if no cases are matched.</param>
166         /// <param name="comparison">The equality comparison method to use.</param>
167         /// <param name="cases">The valid cases for this switch.</param>
168         /// <returns>The created <see cref="SwitchExpression"/>.</returns>
169         public static SwitchExpression Switch(Type type, Expression switchValue, Expression defaultBody, MethodInfo comparison, params SwitchCase[] cases) {
170             return Switch(type, switchValue, defaultBody, comparison, (IEnumerable<SwitchCase>)cases);
171         }
172
173         /// <summary>
174         /// Creates a <see cref="SwitchExpression"/>.
175         /// </summary>
176         /// <param name="switchValue">The value to be tested against each case.</param>
177         /// <param name="defaultBody">The result of the switch if no cases are matched.</param>
178         /// <param name="comparison">The equality comparison method to use.</param>
179         /// <param name="cases">The valid cases for this switch.</param>
180         /// <returns>The created <see cref="SwitchExpression"/>.</returns>
181         public static SwitchExpression Switch(Expression switchValue, Expression defaultBody, MethodInfo comparison, IEnumerable<SwitchCase> cases) {
182             return Switch(null, switchValue, defaultBody, comparison, cases);
183         }
184
185         /// <summary>
186         /// Creates a <see cref="SwitchExpression"/>.
187         /// </summary>
188         /// <param name="type">The result type of the switch.</param>
189         /// <param name="switchValue">The value to be tested against each case.</param>
190         /// <param name="defaultBody">The result of the switch if no cases are matched.</param>
191         /// <param name="comparison">The equality comparison method to use.</param>
192         /// <param name="cases">The valid cases for this switch.</param>
193         /// <returns>The created <see cref="SwitchExpression"/>.</returns>
194         public static SwitchExpression Switch(Type type, Expression switchValue, Expression defaultBody, MethodInfo comparison, IEnumerable<SwitchCase> cases) {
195             RequiresCanRead(switchValue, "switchValue");
196             if (switchValue.Type == typeof(void)) throw Error.ArgumentCannotBeOfTypeVoid();
197
198             var caseList = cases.ToReadOnly();
199             ContractUtils.RequiresNotEmpty(caseList, "cases");
200             ContractUtils.RequiresNotNullItems(caseList, "cases");
201
202             // Type of the result. Either provided, or it is type of the branches.
203             Type resultType = type ?? caseList[0].Body.Type;
204             bool customType = type != null;
205
206             if (comparison != null) {
207                 var pms = comparison.GetParametersCached();
208                 if (pms.Length != 2) {
209                     throw Error.IncorrectNumberOfMethodCallArguments(comparison);
210                 }
211                 // Validate that the switch value's type matches the comparison method's 
212                 // left hand side parameter type.
213                 var leftParam = pms[0];
214                 bool liftedCall = false;
215                 if (!ParameterIsAssignable(leftParam, switchValue.Type)) {
216                     liftedCall = ParameterIsAssignable(leftParam, switchValue.Type.GetNonNullableType());
217                     if (!liftedCall) {
218                         throw Error.SwitchValueTypeDoesNotMatchComparisonMethodParameter(switchValue.Type, leftParam.ParameterType);
219                     }
220                 }
221
222                 var rightParam = pms[1];
223                 foreach (var c in caseList) {
224                     ContractUtils.RequiresNotNull(c, "cases");
225                     ValidateSwitchCaseType(c.Body, customType, resultType, "cases");
226                     for (int i = 0; i < c.TestValues.Count; i++) {
227                         // When a comparison method is provided, test values can have different type but have to
228                         // be reference assignable to the right hand side parameter of the method.
229                         Type rightOperandType = c.TestValues[i].Type;
230                         if (liftedCall) {
231                             if (!rightOperandType.IsNullableType()) {
232                                 throw Error.TestValueTypeDoesNotMatchComparisonMethodParameter(rightOperandType, rightParam.ParameterType);
233                             }
234                             rightOperandType = rightOperandType.GetNonNullableType();
235                         }
236                         if (!ParameterIsAssignable(rightParam, rightOperandType)) {
237                             throw Error.TestValueTypeDoesNotMatchComparisonMethodParameter(rightOperandType, rightParam.ParameterType);
238                         }
239                     }
240                 }
241             } else {
242                 // When comparison method is not present, all the test values must have
243                 // the same type. Use the first test value's type as the baseline.
244                 var firstTestValue = caseList[0].TestValues[0];
245                 foreach (var c in caseList) {
246                     ContractUtils.RequiresNotNull(c, "cases");
247                     ValidateSwitchCaseType(c.Body, customType, resultType, "cases");
248                     // When no comparison method is provided, require all test values to have the same type.
249                     for (int i = 0; i < c.TestValues.Count; i++) {
250                         if (!TypeUtils.AreEquivalent(firstTestValue.Type, c.TestValues[i].Type)) {
251                             throw new ArgumentException(Strings.AllTestValuesMustHaveSameType, "cases");
252                         }
253                     }
254                 }
255
256                 // Now we need to validate that switchValue.Type and testValueType
257                 // make sense in an Equal node. Fortunately, Equal throws a
258                 // reasonable error, so just call it.
259                 var equal = Equal(switchValue, firstTestValue, false, comparison);
260
261                 // Get the comparison function from equals node.
262                 comparison = equal.Method;
263             }
264
265             if (defaultBody == null) {
266                 if (resultType != typeof(void)) throw Error.DefaultBodyMustBeSupplied();
267             } else {
268                 ValidateSwitchCaseType(defaultBody, customType, resultType, "defaultBody");
269             }
270
271             // if we have a non-boolean userdefined equals, we don't want it.
272             if (comparison != null && comparison.ReturnType != typeof(bool)) {
273                 throw Error.EqualityMustReturnBoolean(comparison);
274             }
275
276             return new SwitchExpression(resultType, switchValue, defaultBody, comparison, caseList);
277         }
278
279
280         /// <summary>
281         /// If custom type is provided, all branches must be reference assignable to the result type.
282         /// If no custom type is provided, all branches must have the same type - resultType.
283         /// </summary>
284         private static void ValidateSwitchCaseType(Expression @case, bool customType, Type resultType, string parameterName) {
285             if (customType) {
286                 if (resultType != typeof(void)) {
287                     if (!TypeUtils.AreReferenceAssignable(resultType, @case.Type)) {
288                         throw new ArgumentException(Strings.ArgumentTypesMustMatch, parameterName);
289                     }
290                 }
291             } else {
292                 if (!TypeUtils.AreEquivalent(resultType, @case.Type)) {
293                     throw new ArgumentException(Strings.AllCaseBodiesMustHaveSameType, parameterName);
294                 }
295             }
296         }
297     }
298 }