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;
24 namespace Microsoft.Scripting.Ast {
26 namespace System.Linq.Expressions {
29 /// Represents a control expression that handles multiple selections by passing control to a <see cref="SwitchCase"/>.
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;
39 internal SwitchExpression(Type type, Expression switchValue, Expression defaultBody, MethodInfo comparison, ReadOnlyCollection<SwitchCase> cases) {
41 _switchValue = switchValue;
42 _defaultBody = defaultBody;
43 _comparison = comparison;
48 /// Gets the static type of the expression that this <see cref="Expression" /> represents.
50 /// <returns>The <see cref="Type"/> that represents the static type of the expression.</returns>
51 public sealed override Type Type {
56 /// Returns the node type of this Expression. Extension nodes should return
57 /// ExpressionType.Extension when overriding this method.
59 /// <returns>The <see cref="ExpressionType"/> of the expression.</returns>
60 public sealed override ExpressionType NodeType {
61 get { return ExpressionType.Switch; }
65 /// Gets the test for the switch.
67 public Expression SwitchValue {
68 get { return _switchValue; }
72 /// Gets the collection of <see cref="SwitchCase"/> objects for the switch.
74 public ReadOnlyCollection<SwitchCase> Cases {
75 get { return _cases; }
79 /// Gets the test for the switch.
81 public Expression DefaultBody {
82 get { return _defaultBody; }
86 /// Gets the equality comparison method, if any.
88 public MethodInfo Comparison {
89 get { return _comparison; }
93 /// Dispatches to the specific visit method for this node type.
95 protected internal override Expression Accept(ExpressionVisitor visitor) {
96 return visitor.VisitSwitch(this);
99 internal bool IsLifted {
101 if (_switchValue.Type.IsNullableType()) {
102 return (_comparison == null) ||
103 !TypeUtils.AreEquivalent(_switchValue.Type, _comparison.GetParametersCached()[0].ParameterType.GetNonRefType());
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.
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) {
122 return Expression.Switch(Type, switchValue, defaultBody, Comparison, cases);
126 public partial class Expression {
128 /// Creates a <see cref="SwitchExpression"/>.
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);
138 /// Creates a <see cref="SwitchExpression"/>.
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);
149 /// Creates a <see cref="SwitchExpression"/>.
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);
161 /// Creates a <see cref="SwitchExpression"/>.
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);
174 /// Creates a <see cref="SwitchExpression"/>.
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);
186 /// Creates a <see cref="SwitchExpression"/>.
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();
198 var caseList = cases.ToReadOnly();
199 ContractUtils.RequiresNotEmpty(caseList, "cases");
200 ContractUtils.RequiresNotNullItems(caseList, "cases");
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;
206 if (comparison != null) {
207 var pms = comparison.GetParametersCached();
208 if (pms.Length != 2) {
209 throw Error.IncorrectNumberOfMethodCallArguments(comparison);
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());
218 throw Error.SwitchValueTypeDoesNotMatchComparisonMethodParameter(switchValue.Type, leftParam.ParameterType);
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;
231 if (!rightOperandType.IsNullableType()) {
232 throw Error.TestValueTypeDoesNotMatchComparisonMethodParameter(rightOperandType, rightParam.ParameterType);
234 rightOperandType = rightOperandType.GetNonNullableType();
236 if (!ParameterIsAssignable(rightParam, rightOperandType)) {
237 throw Error.TestValueTypeDoesNotMatchComparisonMethodParameter(rightOperandType, rightParam.ParameterType);
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");
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);
261 // Get the comparison function from equals node.
262 comparison = equal.Method;
265 if (defaultBody == null) {
266 if (resultType != typeof(void)) throw Error.DefaultBodyMustBeSupplied();
268 ValidateSwitchCaseType(defaultBody, customType, resultType, "defaultBody");
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);
276 return new SwitchExpression(resultType, switchValue, defaultBody, comparison, caseList);
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.
284 private static void ValidateSwitchCaseType(Expression @case, bool customType, Type resultType, string parameterName) {
286 if (resultType != typeof(void)) {
287 if (!TypeUtils.AreReferenceAssignable(resultType, @case.Type)) {
288 throw new ArgumentException(Strings.ArgumentTypesMustMatch, parameterName);
292 if (!TypeUtils.AreEquivalent(resultType, @case.Type)) {
293 throw new ArgumentException(Strings.AllCaseBodiesMustHaveSameType, parameterName);