1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
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.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
17 using System.Collections.Generic;
18 using System.Collections.ObjectModel;
19 using System.Diagnostics;
20 using System.Dynamic.Utils;
21 using System.Reflection;
28 namespace Microsoft.Scripting.Ast {
30 namespace System.Linq.Expressions {
33 /// Represents a control expression that handles multiple selections by passing control to a <see cref="SwitchCase"/>.
36 [DebuggerTypeProxy(typeof(Expression.SwitchExpressionProxy))]
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;
45 internal SwitchExpression(Type type, Expression switchValue, Expression defaultBody, MethodInfo comparison, ReadOnlyCollection<SwitchCase> cases) {
47 _switchValue = switchValue;
48 _defaultBody = defaultBody;
49 _comparison = comparison;
54 /// Gets the static type of the expression that this <see cref="Expression" /> represents.
56 /// <returns>The <see cref="Type"/> that represents the static type of the expression.</returns>
57 public sealed override Type Type {
62 /// Returns the node type of this Expression. Extension nodes should return
63 /// ExpressionType.Extension when overriding this method.
65 /// <returns>The <see cref="ExpressionType"/> of the expression.</returns>
66 public sealed override ExpressionType NodeType {
67 get { return ExpressionType.Switch; }
71 /// Gets the test for the switch.
73 public Expression SwitchValue {
74 get { return _switchValue; }
78 /// Gets the collection of <see cref="SwitchCase"/> objects for the switch.
80 public ReadOnlyCollection<SwitchCase> Cases {
81 get { return _cases; }
85 /// Gets the test for the switch.
87 public Expression DefaultBody {
88 get { return _defaultBody; }
92 /// Gets the equality comparison method, if any.
94 public MethodInfo Comparison {
95 get { return _comparison; }
99 /// Dispatches to the specific visit method for this node type.
101 protected internal override Expression Accept(ExpressionVisitor visitor) {
102 return visitor.VisitSwitch(this);
105 internal bool IsLifted {
107 if (_switchValue.Type.IsNullableType()) {
108 return (_comparison == null) ||
109 !TypeUtils.AreEquivalent(_switchValue.Type, _comparison.GetParametersCached()[0].ParameterType.GetNonRefType());
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.
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) {
128 return Expression.Switch(Type, switchValue, defaultBody, Comparison, cases);
132 public partial class Expression {
134 /// Creates a <see cref="SwitchExpression"/>.
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);
144 /// Creates a <see cref="SwitchExpression"/>.
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);
155 /// Creates a <see cref="SwitchExpression"/>.
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);
167 /// Creates a <see cref="SwitchExpression"/>.
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);
180 /// Creates a <see cref="SwitchExpression"/>.
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);
192 /// Creates a <see cref="SwitchExpression"/>.
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();
204 var caseList = cases.ToReadOnly();
205 ContractUtils.RequiresNotEmpty(caseList, "cases");
206 ContractUtils.RequiresNotNullItems(caseList, "cases");
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;
212 if (comparison != null) {
213 var pms = comparison.GetParametersCached();
214 if (pms.Length != 2) {
215 throw Error.IncorrectNumberOfMethodCallArguments(comparison);
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());
224 throw Error.SwitchValueTypeDoesNotMatchComparisonMethodParameter(switchValue.Type, leftParam.ParameterType);
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;
237 if (!rightOperandType.IsNullableType()) {
238 throw Error.TestValueTypeDoesNotMatchComparisonMethodParameter(rightOperandType, rightParam.ParameterType);
240 rightOperandType = rightOperandType.GetNonNullableType();
242 if (!ParameterIsAssignable(rightParam, rightOperandType)) {
243 throw Error.TestValueTypeDoesNotMatchComparisonMethodParameter(rightOperandType, rightParam.ParameterType);
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");
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);
267 // Get the comparison function from equals node.
268 comparison = equal.Method;
271 if (defaultBody == null) {
272 if (resultType != typeof(void)) throw Error.DefaultBodyMustBeSupplied();
274 ValidateSwitchCaseType(defaultBody, customType, resultType, "defaultBody");
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);
282 return new SwitchExpression(resultType, switchValue, defaultBody, comparison, caseList);
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.
290 private static void ValidateSwitchCaseType(Expression @case, bool customType, Type resultType, string parameterName) {
292 if (resultType != typeof(void)) {
293 if (!TypeUtils.AreReferenceAssignable(resultType, @case.Type)) {
294 throw new ArgumentException(Strings.ArgumentTypesMustMatch, parameterName);
298 if (!TypeUtils.AreEquivalent(resultType, @case.Type)) {
299 throw new ArgumentException(Strings.AllCaseBodiesMustHaveSameType, parameterName);