PageRenderTime 38ms CodeModel.GetById 13ms app.highlight 17ms RepoModel.GetById 2ms app.codeStats 0ms

/IronPython_Main/Runtime/Microsoft.Scripting.Core/Ast/SwitchExpression.cs

#
C# | 304 lines | 160 code | 30 blank | 114 comment | 39 complexity | 87db5fd622663a5fda8739e167feb3fe MD5 | raw file
  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
 16using System;
 17using System.Collections.Generic;
 18using System.Collections.ObjectModel;
 19using System.Diagnostics;
 20using System.Dynamic.Utils;
 21using System.Reflection;
 22
 23#if SILVERLIGHT
 24using System.Core;
 25#endif
 26
 27#if CLR2
 28namespace Microsoft.Scripting.Ast {
 29#else
 30namespace 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}