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