diff --git a/src/System.Linq.Expressions/src/System/Linq/Expressions/BlockExpression.cs b/src/System.Linq.Expressions/src/System/Linq/Expressions/BlockExpression.cs index b434cd44a205..20d264796331 100644 --- a/src/System.Linq.Expressions/src/System/Linq/Expressions/BlockExpression.cs +++ b/src/System.Linq.Expressions/src/System/Linq/Expressions/BlockExpression.cs @@ -781,12 +781,14 @@ public static BlockExpression Block(params Expression[] expressions) switch (expressions.Length) { + case 0: + expressions = new []{ Empty() }; + goto default; case 2: return Block(expressions[0], expressions[1]); case 3: return Block(expressions[0], expressions[1], expressions[2]); case 4: return Block(expressions[0], expressions[1], expressions[2], expressions[3]); case 5: return Block(expressions[0], expressions[1], expressions[2], expressions[3], expressions[4]); default: - ContractUtils.RequiresNotEmpty(expressions, "expressions"); RequiresCanRead(expressions, "expressions"); return new BlockN(expressions.Copy()); } @@ -857,8 +859,12 @@ public static BlockExpression Block(Type type, IEnumerable public static BlockExpression Block(IEnumerable variables, IEnumerable expressions) { ContractUtils.RequiresNotNull(expressions, "expressions"); + var variableList = variables.ToReadOnly(); var expressionList = expressions.ToReadOnly(); - ContractUtils.RequiresNotEmpty(expressionList, "expressions"); + if (variableList.Count != 0) + ContractUtils.RequiresNotEmpty(expressionList, "expressions"); + else if (expressionList.Count == 0) + expressionList = new ReadOnlyCollection(new []{ Empty() }); RequiresCanRead(expressionList, "expressions"); return Block(expressionList.Last().Type, variables, expressionList); @@ -879,7 +885,10 @@ public static BlockExpression Block(Type type, IEnumerable var expressionList = expressions.ToReadOnly(); var variableList = variables.ToReadOnly(); - ContractUtils.RequiresNotEmpty(expressionList, "expressions"); + if (variableList.Count != 0) + ContractUtils.RequiresNotEmpty(expressionList, "expressions"); + else if (expressionList.Count == 0) + expressionList = new ReadOnlyCollection(new []{ Empty() }); RequiresCanRead(expressionList, "expressions"); ValidateVariables(variableList, "variables"); diff --git a/src/System.Linq.Expressions/src/System/Linq/Expressions/SwitchExpression.cs b/src/System.Linq.Expressions/src/System/Linq/Expressions/SwitchExpression.cs index 6b9e3278b4ce..9ef9dc136b33 100644 --- a/src/System.Linq.Expressions/src/System/Linq/Expressions/SwitchExpression.cs +++ b/src/System.Linq.Expressions/src/System/Linq/Expressions/SwitchExpression.cs @@ -202,8 +202,44 @@ public static SwitchExpression Switch(Type type, Expression switchValue, Express if (switchValue.Type == typeof(void)) throw Error.ArgumentCannotBeOfTypeVoid(); var caseList = cases.ToReadOnly(); - ContractUtils.RequiresNotEmpty(caseList, "cases"); - ContractUtils.RequiresNotNullItems(caseList, "cases"); + if (caseList.Count == 0) + { + // Previous versions did not allow for a switch expression with no SwitchCases. This though can be useful if + // the SwitchCases are determined dynamically, in not having to special-case the case where there are zero + // such SwitchCases. + // However, merely changing this method to allow the zero-cases condition may have compatibility issues with + // code acting on the expression tree assuming that this condition cannot occur. Therefore we produce a + // switch with a case for the default value of the expression matching that of the the default case. + // By analogy to C#, we turn the equivalent to the (valid) C#: + // + // switch(expr) + // { + // default: + // doSomething(); + // break; + // } + // + // into: + // + // switch(expr) + // { + // case 0: + // doSomething(); + // break; + // default: + // doSomething(); + // break; + // } + // + // but where the 0 is typed according to the type of expr + // + // If there is no defaultBody either we create a noop body. This will mean the type of the expression is void + // If there is no defaultBody and the type is explicitly set to something other than void, an ArgumentException + // is thrown. + caseList = new ReadOnlyCollection(new [] { SwitchCase(defaultBody ?? Empty(), Default(switchValue.Type)) }); + } + else + ContractUtils.RequiresNotNullItems(caseList, "cases"); // Type of the result. Either provided, or it is type of the branches. Type resultType = type ?? caseList[0].Body.Type; diff --git a/src/System.Linq.Expressions/tests/SequenceTests/SequenceTests.cs b/src/System.Linq.Expressions/tests/SequenceTests/SequenceTests.cs index 8d0147f24d75..090c536653d3 100644 --- a/src/System.Linq.Expressions/tests/SequenceTests/SequenceTests.cs +++ b/src/System.Linq.Expressions/tests/SequenceTests/SequenceTests.cs @@ -3871,6 +3871,57 @@ public static void ObjectSwitch1() Assert.Equal("default", f("HI")); Assert.Equal("null", f(null)); } + + [Fact] + public static void DefaultOnlySwitch() + { + var p = Expression.Parameter(typeof(int)); + var s = Expression.Switch(p, Expression.Constant(42)); + + var fInt32Int32 = Expression.Lambda>(s, p).Compile(); + + Assert.Equal(42, fInt32Int32(0)); + Assert.Equal(42, fInt32Int32(1)); + Assert.Equal(42, fInt32Int32(-1)); + + s = Expression.Switch(typeof(object), p, Expression.Constant("A test string"), null); + + var fInt32Object = Expression.Lambda>(s, p).Compile(); + + Assert.Equal("A test string", fInt32Object(0)); + Assert.Equal("A test string", fInt32Object(1)); + Assert.Equal("A test string", fInt32Object(-1)); + + p = Expression.Parameter(typeof(string)); + s = Expression.Switch(p, Expression.Constant("foo")); + + var fStringString = Expression.Lambda>(s, p).Compile(); + + Assert.Equal("foo", fStringString("bar")); + Assert.Equal("foo", fStringString(null)); + Assert.Equal("foo", fStringString("foo")); + } + + [Fact] + public static void NoDefaultOrCasesSwitch() + { + var p = Expression.Parameter(typeof(int)); + var s = Expression.Switch(p, (Expression)null); + + var f = Expression.Lambda>(s, p).Compile(); + + f(0); + + Assert.Equal(s.Type, typeof(void)); + } + + [Fact] + public static void TypedNoDefaultOrCasesSwitch() + { + var p = Expression.Parameter(typeof(int)); + // A SwitchExpression with neither a defaultBody nor any cases can not be any type except void. + Assert.Throws(() => Expression.Switch(typeof(int), p, (Expression)null, null)); + } static class System_Linq_Expressions_Expression_TDelegate__1 {