1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Reflection;
7 using Xunit;
8 
9 namespace System.Linq.Expressions.Tests
10 {
11     public class OpAssign
12     {
13         [Theory]
14         [PerCompilationType(nameof(AssignAndEquivalentMethods))]
AssignmentEquivalents(MethodInfo nonAssign, MethodInfo assign, Type type, bool useInterpreter)15         public void AssignmentEquivalents(MethodInfo nonAssign, MethodInfo assign, Type type, bool useInterpreter)
16         {
17             Func<Expression, Expression, Expression> withoutAssignment = (Func<Expression, Expression, Expression>)nonAssign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
18             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
19 
20             foreach (object x in new[] { 0, -1, 1, 10 }.Select(i => Convert.ChangeType(i, type)))
21                 foreach (object y in new[] { -1, 1, 10 }.Select(i => Convert.ChangeType(i, type)))
22                 {
23                     ConstantExpression xExp = Expression.Constant(x);
24                     ConstantExpression yExp = Expression.Constant(y);
25                     Expression woAssign = withoutAssignment(xExp, yExp);
26                     ParameterExpression variable = Expression.Variable(type);
27                     Expression initAssign = Expression.Assign(variable, xExp);
28                     Expression assignment = withAssignment(variable, yExp);
29                     Expression wAssign = Expression.Block(
30                         new ParameterExpression[] { variable },
31                         initAssign,
32                         assignment
33                         );
34                     Assert.True(Expression.Lambda<Func<bool>>(Expression.Equal(woAssign, wAssign)).Compile(useInterpreter)());
35                     LabelTarget target = Expression.Label(type);
36                     Expression wAssignReturningVariable = Expression.Block(
37                         new ParameterExpression[] { variable },
38                         initAssign,
39                         assignment,
40                         Expression.Return(target, variable),
41                         Expression.Label(target, Expression.Default(type))
42                         );
43                     Assert.True(Expression.Lambda<Func<bool>>(Expression.Equal(woAssign, wAssignReturningVariable)).Compile(useInterpreter)());
44                 }
45         }
46 
47         private class Box<T>
48         {
49             public static T StaticValue { get; set; }
50             public T Value { get; set; }
51             public T this[int index]
52             {
53                 get
54                 {
55                     return Value;
56                 }
57                 set
58                 {
59                     Value = value;
60                 }
61             }
Box(T value)62             public Box(T value)
63             {
64                 Value = value;
65             }
66         }
67 
68         [Theory]
69         [PerCompilationType(nameof(AssignAndEquivalentMethods))]
AssignmentEquivalentsWithMemberAccess(MethodInfo nonAssign, MethodInfo assign, Type type, bool useInterpreter)70         public void AssignmentEquivalentsWithMemberAccess(MethodInfo nonAssign, MethodInfo assign, Type type, bool useInterpreter)
71         {
72             Func<Expression, Expression, Expression> withoutAssignment = (Func<Expression, Expression, Expression>)nonAssign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
73             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
74 
75             foreach (object x in new[] { 0, -1, 1, 10 }.Select(i => Convert.ChangeType(i, type)))
76                 foreach (object y in new[] { -1, 1, 10 }.Select(i => Convert.ChangeType(i, type)))
77                 {
78                     ConstantExpression xExp = Expression.Constant(x);
79                     ConstantExpression yExp = Expression.Constant(y);
80                     Expression woAssign = withoutAssignment(xExp, yExp);
81                     Type boxType = typeof(Box<>).MakeGenericType(type);
82                     object box = boxType.GetConstructor(new[] { type }).Invoke(new object[] { x });
83                     Expression boxExp = Expression.Constant(box);
84                     Expression property = Expression.Property(boxExp, boxType.GetProperty("Value"));
85                     Expression assignment = withAssignment(property, yExp);
86                     Assert.True(Expression.Lambda<Func<bool>>(Expression.Equal(woAssign, assignment)).Compile(useInterpreter)());
87                     LabelTarget target = Expression.Label(type);
88                     box = boxType.GetConstructor(new[] { type }).Invoke(new object[] { x });
89                     boxExp = Expression.Constant(box);
90                     property = Expression.Property(boxExp, boxType.GetProperty("Value"));
91                     assignment = withAssignment(property, yExp);
92                     Expression wAssignReturningVariable = Expression.Block(
93                         assignment,
94                         Expression.Return(target, property),
95                         Expression.Label(target, Expression.Default(type))
96                         );
97                     Assert.True(Expression.Lambda<Func<bool>>(Expression.Equal(woAssign, wAssignReturningVariable)).Compile(useInterpreter)());
98                 }
99         }
100 
101         [Theory, PerCompilationType(nameof(AssignAndEquivalentMethods))]
AssignmentEquivalentsWithStaticMemberAccess(MethodInfo nonAssign, MethodInfo assign, Type type, bool useInterpreter)102         public void AssignmentEquivalentsWithStaticMemberAccess(MethodInfo nonAssign, MethodInfo assign, Type type, bool useInterpreter)
103         {
104             Func<Expression, Expression, Expression> withoutAssignment = (Func<Expression, Expression, Expression>)nonAssign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
105             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
106 
107             foreach (object x in new[] { 0, -1, 1, 10 }.Select(i => Convert.ChangeType(i, type)))
108                 foreach (object y in new[] { -1, 1, 10 }.Select(i => Convert.ChangeType(i, type)))
109                 {
110                     ConstantExpression xExp = Expression.Constant(x);
111                     ConstantExpression yExp = Expression.Constant(y);
112                     Expression woAssign = withoutAssignment(xExp, yExp);
113                     Type boxType = typeof(Box<>).MakeGenericType(type);
114                     PropertyInfo prop = boxType.GetProperty("StaticValue");
115                     prop.SetValue(null, x);
116                     Expression property = Expression.Property(null, prop);
117                     Expression assignment = withAssignment(property, yExp);
118                     Assert.True(Expression.Lambda<Func<bool>>(Expression.Equal(woAssign, assignment)).Compile(useInterpreter)());
119                     prop.SetValue(null, x);
120                     Expression wAssignReturningVariable = Expression.Block(
121                         assignment,
122                         property
123                         );
124                     Assert.True(Expression.Lambda<Func<bool>>(Expression.Equal(woAssign, wAssignReturningVariable)).Compile(useInterpreter)());
125                 }
126         }
127         [Theory]
128         [PerCompilationType(nameof(AssignAndEquivalentMethods))]
AssignmentEquivalentsWithIndexAccess(MethodInfo nonAssign, MethodInfo assign, Type type, bool useInterpreter)129         public void AssignmentEquivalentsWithIndexAccess(MethodInfo nonAssign, MethodInfo assign, Type type, bool useInterpreter)
130         {
131             Func<Expression, Expression, Expression> withoutAssignment = (Func<Expression, Expression, Expression>)nonAssign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
132             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
133 
134             foreach (object x in new[] { 0, -1, 1, 10 }.Select(i => Convert.ChangeType(i, type)))
135                 foreach (object y in new[] { -1, 1, 10 }.Select(i => Convert.ChangeType(i, type)))
136                 {
137                     ConstantExpression xExp = Expression.Constant(x);
138                     ConstantExpression yExp = Expression.Constant(y);
139                     Expression woAssign = withoutAssignment(xExp, yExp);
140                     Type boxType = typeof(Box<>).MakeGenericType(type);
141                     object box = boxType.GetConstructor(new[] { type }).Invoke(new object[] { x });
142                     Expression boxExp = Expression.Constant(box);
143                     Expression property = Expression.Property(boxExp, boxType.GetProperty("Item"), Expression.Constant(0));
144                     Expression assignment = withAssignment(property, yExp);
145                     Assert.True(Expression.Lambda<Func<bool>>(Expression.Equal(woAssign, assignment)).Compile(useInterpreter)());
146                     LabelTarget target = Expression.Label(type);
147                     box = boxType.GetConstructor(new[] { type }).Invoke(new object[] { x });
148                     boxExp = Expression.Constant(box);
149                     property = Expression.Property(boxExp, boxType.GetProperty("Item"), Expression.Constant(0));
150                     assignment = withAssignment(property, yExp);
151                     Expression wAssignReturningVariable = Expression.Block(
152                         assignment,
153                         Expression.Return(target, property),
154                         Expression.Label(target, Expression.Default(type))
155                         );
156                     Assert.True(Expression.Lambda<Func<bool>>(Expression.Equal(woAssign, wAssignReturningVariable)).Compile(useInterpreter)());
157                 }
158         }
159 
160         [Theory]
161         [MemberData(nameof(AssignmentMethods))]
AssignmentReducable(MethodInfo assign, Type type)162         public void AssignmentReducable(MethodInfo assign, Type type)
163         {
164             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
165 
166             ParameterExpression variable = Expression.Variable(type);
167             Expression assignment = withAssignment(variable, Expression.Default(type));
168             Assert.True(assignment.CanReduce);
169             Assert.NotSame(assignment, assignment.ReduceAndCheck());
170         }
171 
172         [Theory]
173         [MemberData(nameof(AssignmentMethods))]
CannotAssignToNonWritable(MethodInfo assign, Type type)174         public void CannotAssignToNonWritable(MethodInfo assign, Type type)
175         {
176             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
177 
178             AssertExtensions.Throws<ArgumentException>("left", () => withAssignment(Expression.Default(type), Expression.Default(type)));
179         }
180 
181         [Theory]
182         [MemberData(nameof(AssignmentMethods))]
AssignmentWithMemberAccessReducable(MethodInfo assign, Type type)183         public void AssignmentWithMemberAccessReducable(MethodInfo assign, Type type)
184         {
185             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
186 
187             Type boxType = typeof(Box<>).MakeGenericType(type);
188             object box = boxType.GetConstructor(new[] { type }).Invoke(new object[] { Convert.ChangeType(0, type) });
189             Expression boxExp = Expression.Constant(box);
190             Expression property = Expression.Property(boxExp, boxType.GetProperty("Value"));
191             Expression assignment = withAssignment(property, Expression.Default(type));
192             Assert.True(assignment.CanReduce);
193             Assert.NotSame(assignment, assignment.ReduceAndCheck());
194         }
195 
196         [Theory]
197         [MemberData(nameof(AssignmentMethods))]
AssignmentWithIndexAccessReducable(MethodInfo assign, Type type)198         public void AssignmentWithIndexAccessReducable(MethodInfo assign, Type type)
199         {
200             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
201 
202             Type boxType = typeof(Box<>).MakeGenericType(type);
203             object box = boxType.GetConstructor(new[] { type }).Invoke(new object[] { Convert.ChangeType(0, type) });
204             Expression boxExp = Expression.Constant(box);
205             Expression property = Expression.Property(boxExp, boxType.GetProperty("Item"), Expression.Constant(0));
206             Expression assignment = withAssignment(property, Expression.Default(type));
207             Assert.True(assignment.CanReduce);
208             Assert.NotSame(assignment, assignment.ReduceAndCheck());
209         }
210 
211 
212         private static class Unreadable<T>
213         {
214             public static T WriteOnly
215             {
216                 set { }
217             }
218         }
219 
220         [Theory]
221         [MemberData(nameof(AssignmentMethods))]
ThrowsOnLeftUnreadable(MethodInfo assign, Type type)222         public static void ThrowsOnLeftUnreadable(MethodInfo assign, Type type)
223         {
224             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
225 
226             Type unreadableType = typeof(Unreadable<>).MakeGenericType(type);
227             Expression property = Expression.Property(null, unreadableType.GetProperty("WriteOnly"));
228             AssertExtensions.Throws<ArgumentException>("left", () => withAssignment(property, Expression.Default(type)));
229         }
230 
231         [Theory]
232         [MemberData(nameof(AssignmentMethods))]
ThrowsOnRightUnreadable(MethodInfo assign, Type type)233         public static void ThrowsOnRightUnreadable(MethodInfo assign, Type type)
234         {
235             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
236 
237             Type unreadableType = typeof(Unreadable<>).MakeGenericType(type);
238             Expression property = Expression.Property(null, unreadableType.GetProperty("WriteOnly"));
239             Expression variable = Expression.Variable(type);
240             AssertExtensions.Throws<ArgumentException>("right", () => withAssignment(variable, property));
241         }
242 
243         [Theory]
244         [MemberData(nameof(AssignmentMethodsWithoutTypes))]
ThrowIfNoSuchBinaryOperation(MethodInfo assign)245         public void ThrowIfNoSuchBinaryOperation(MethodInfo assign)
246         {
247             Func<Expression, Expression, Expression> withAssignment = (Func<Expression, Expression, Expression>)assign.CreateDelegate(typeof(Func<Expression, Expression, Expression>));
248 
249             ParameterExpression variable = Expression.Variable(typeof(string));
250             Expression value = Expression.Default(typeof(string));
251             Assert.Throws<InvalidOperationException>(() => withAssignment(variable, value));
252         }
253 
AssignmentMethods()254         private static IEnumerable<object[]> AssignmentMethods()
255         {
256             MethodInfo[] expressionMethods = typeof(Expression).GetMethods().Where(mi => mi.GetParameters().Length == 2).ToArray();
257             foreach (Tuple<string, string> names in AssignAndEquivalentMethodNames(true))
258                 yield return new object[] { expressionMethods.First(mi => mi.Name == names.Item2), typeof(int) };
259             foreach (Tuple<string, string> names in AssignAndEquivalentMethodNames(false))
260                 yield return new object[] { expressionMethods.First(mi => mi.Name == names.Item2), typeof(double) };
261         }
262 
AssignmentMethodsWithoutTypes()263         private static IEnumerable<object[]> AssignmentMethodsWithoutTypes()
264         {
265             MethodInfo[] expressionMethods = typeof(Expression).GetMethods().Where(mi => mi.GetParameters().Length == 2).ToArray();
266             return AssignAndEquivalentMethodNames(true).Concat(AssignAndEquivalentMethodNames(false))
267                 .Select(i => i.Item2)
268                 .Distinct()
269                 .Select(i => new object[] { expressionMethods.First(mi => mi.Name == i) });
270         }
271 
AssignAndEquivalentMethods()272         private static IEnumerable<object[]> AssignAndEquivalentMethods()
273         {
274             MethodInfo[] expressionMethods = typeof(Expression).GetMethods().Where(mi => mi.GetParameters().Length == 2).ToArray();
275             foreach (Tuple<string, string> names in AssignAndEquivalentMethodNames(true))
276                 yield return new object[] {
277                     expressionMethods.First(mi => mi.Name == names.Item1),
278                     expressionMethods.First(mi => mi.Name == names.Item2),
279                     typeof(int)
280                 };
281             foreach (Tuple<string, string> names in AssignAndEquivalentMethodNames(false))
282                 yield return new object[] {
283                     expressionMethods.First(mi => mi.Name == names.Item1),
284                     expressionMethods.First(mi => mi.Name == names.Item2),
285                     typeof(double)
286                 };
287         }
288 
AssignAndEquivalentMethodNames(bool integral)289         private static IEnumerable<Tuple<string, string>> AssignAndEquivalentMethodNames(bool integral)
290         {
291             yield return Tuple.Create("Add", "AddAssign");
292             yield return Tuple.Create("AddChecked", "AddAssignChecked");
293             yield return Tuple.Create("Divide", "DivideAssign");
294             yield return Tuple.Create("Modulo", "ModuloAssign");
295             yield return Tuple.Create("Multiply", "MultiplyAssign");
296             yield return Tuple.Create("MultiplyChecked", "MultiplyAssignChecked");
297             yield return Tuple.Create("Subtract", "SubtractAssign");
298             yield return Tuple.Create("SubtractChecked", "SubtractAssignChecked");
299             if (integral)
300             {
301                 yield return Tuple.Create("And", "AndAssign");
302                 yield return Tuple.Create("ExclusiveOr", "ExclusiveOrAssign");
303                 yield return Tuple.Create("LeftShift", "LeftShiftAssign");
304                 yield return Tuple.Create("Or", "OrAssign");
305                 yield return Tuple.Create("RightShift", "RightShiftAssign");
306             }
307             else
308                 yield return Tuple.Create("Power", "PowerAssign");
309         }
310 
311         [Theory]
312         [MemberData(nameof(ToStringData))]
ToStringTest(ExpressionType kind, string symbol, Type type)313         public static void ToStringTest(ExpressionType kind, string symbol, Type type)
314         {
315             BinaryExpression e = Expression.MakeBinary(kind, Expression.Parameter(type, "a"), Expression.Parameter(type, "b"));
316             Assert.Equal($"(a {symbol} b)", e.ToString());
317         }
318 
ToStringData()319         private static IEnumerable<object[]> ToStringData()
320         {
321             return ToStringDataImpl().Select(t => new object[] { t.Item1, t.Item2, t.Item3 });
322         }
323 
ToStringDataImpl()324         private static IEnumerable<Tuple<ExpressionType, string, Type>> ToStringDataImpl()
325         {
326             yield return Tuple.Create(ExpressionType.AddAssign, "+=", typeof(int));
327             yield return Tuple.Create(ExpressionType.AddAssignChecked, "+=", typeof(int));
328             yield return Tuple.Create(ExpressionType.SubtractAssign, "-=", typeof(int));
329             yield return Tuple.Create(ExpressionType.SubtractAssignChecked, "-=", typeof(int));
330             yield return Tuple.Create(ExpressionType.MultiplyAssign, "*=", typeof(int));
331             yield return Tuple.Create(ExpressionType.MultiplyAssignChecked, "*=", typeof(int));
332             yield return Tuple.Create(ExpressionType.DivideAssign, "/=", typeof(int));
333             yield return Tuple.Create(ExpressionType.ModuloAssign, "%=", typeof(int));
334             yield return Tuple.Create(ExpressionType.PowerAssign, "**=", typeof(double));
335             yield return Tuple.Create(ExpressionType.LeftShiftAssign, "<<=", typeof(int));
336             yield return Tuple.Create(ExpressionType.RightShiftAssign, ">>=", typeof(int));
337             yield return Tuple.Create(ExpressionType.AndAssign, "&=", typeof(int));
338             yield return Tuple.Create(ExpressionType.AndAssign, "&&=", typeof(bool));
339             yield return Tuple.Create(ExpressionType.OrAssign, "|=", typeof(int));
340             yield return Tuple.Create(ExpressionType.OrAssign, "||=", typeof(bool));
341             yield return Tuple.Create(ExpressionType.ExclusiveOrAssign, "^=", typeof(int));
342             yield return Tuple.Create(ExpressionType.ExclusiveOrAssign, "^=", typeof(bool));
343         }
344 
345         private static IEnumerable<ExpressionType> AssignExpressionTypes
346         {
347             get
348             {
349                 yield return ExpressionType.AddAssign;
350                 yield return ExpressionType.SubtractAssign;
351                 yield return ExpressionType.MultiplyAssign;
352                 yield return ExpressionType.AddAssignChecked;
353                 yield return ExpressionType.SubtractAssignChecked;
354                 yield return ExpressionType.MultiplyAssignChecked;
355                 yield return ExpressionType.DivideAssign;
356                 yield return ExpressionType.ModuloAssign;
357                 yield return ExpressionType.PowerAssign;
358                 yield return ExpressionType.AndAssign;
359                 yield return ExpressionType.OrAssign;
360                 yield return ExpressionType.RightShiftAssign;
361                 yield return ExpressionType.LeftShiftAssign;
362                 yield return ExpressionType.ExclusiveOrAssign;
363             }
364         }
365 
366         private static IEnumerable<Func<Expression, Expression, MethodInfo, BinaryExpression>> AssignExpressionMethodInfoUsingFactories
367         {
368             get
369             {
370                 yield return Expression.AddAssign;
371                 yield return Expression.SubtractAssign;
372                 yield return Expression.MultiplyAssign;
373                 yield return Expression.AddAssignChecked;
374                 yield return Expression.SubtractAssignChecked;
375                 yield return Expression.MultiplyAssignChecked;
376                 yield return Expression.DivideAssign;
377                 yield return Expression.ModuloAssign;
378                 yield return Expression.PowerAssign;
379                 yield return Expression.AndAssign;
380                 yield return Expression.OrAssign;
381                 yield return Expression.RightShiftAssign;
382                 yield return Expression.LeftShiftAssign;
383                 yield return Expression.ExclusiveOrAssign;
384             }
385         }
386 
387         public static IEnumerable<object[]> AssignExpressionTypesArguments
388             => AssignExpressionTypes.Select(t => new object[] {t});
389 
390         public static IEnumerable<object[]> AssignExpressionMethodInfoUsingFactoriesArguments =
391             AssignExpressionMethodInfoUsingFactories.Select(f => new object[] {f});
392 
393         private static IEnumerable<LambdaExpression> NonUnaryLambdas
394         {
395             get
396             {
397                 yield return Expression.Lambda<Action>(Expression.Empty());
398                 Expression<Func<int, int, int>> exp = (x, y) => x + y;
399                 yield return exp;
400             }
401         }
402 
403         public static IEnumerable<object[]> AssignExpressionTypesAndNonUnaryLambdas =>
404             AssignExpressionTypes.SelectMany(t => NonUnaryLambdas, (t, l) => new object[] {t, l});
405 
406         private static IEnumerable<LambdaExpression> NonIntegerReturnUnaryIntegerLambdas
407         {
408             get
409             {
410                 ParameterExpression param = Expression.Parameter(typeof(int));
411                 yield return Expression.Lambda<Action<int>>(Expression.Empty(), param);
412                 Expression<Func<int, long>> convL = x => x;
413                 yield return convL;
414                 Expression<Func<int, string>> toString = x => x.ToString();
415                 yield return toString;
416             }
417         }
418 
419         public static IEnumerable<object[]> AssignExpressionTypesAndNonIntegerReturnUnaryIntegerLambdas
420             => AssignExpressionTypes.SelectMany(t => NonIntegerReturnUnaryIntegerLambdas, (t, l) => new object[] {t, l});
421 
422         private static IEnumerable<LambdaExpression> NonIntegerTakingUnaryIntegerReturningLambda
423         {
424             get
425             {
426                 Expression<Func<long, int>> fromL = x => (int)x;
427                 yield return fromL;
428                 Expression<Func<string, int>> fromS = x => x.Length;
429                 yield return fromS;
430             }
431         }
432 
433         public static IEnumerable<object[]> AssignExpressionTypesAndNonIntegerTakingUnaryIntegerReturningLambda
434             =>
435                 AssignExpressionTypes.SelectMany(
436                     t => NonIntegerTakingUnaryIntegerReturningLambda, (t, l) => new object[] {t, l});
437 
438         [Theory, MemberData(nameof(AssignExpressionTypesArguments))]
CannotHaveConversionOnAssignWithoutMethod(ExpressionType type)439         public void CannotHaveConversionOnAssignWithoutMethod(ExpressionType type)
440         {
441             var lhs = Expression.Variable(typeof(int));
442             var rhs = Expression.Constant(0);
443             Expression<Func<int, int>> identity = x => x;
444             Assert.Throws<InvalidOperationException>(() => Expression.MakeBinary(type, lhs, rhs, false, null, identity));
445             Assert.Throws<InvalidOperationException>(() => Expression.MakeBinary(type, lhs, rhs, true, null, identity));
446         }
447 
FiftyNinthBear(int x, int y)448         public static int FiftyNinthBear(int x, int y)
449         {
450             // Ensure numbers add up to 40. Then ignore that and return 59.
451             if (x + y != 40) throw new ArgumentException();
452             return 59;
453         }
454 
455         [Theory, PerCompilationType(nameof(AssignExpressionTypesArguments))]
ConvertAssignment(ExpressionType type, bool useInterpreter)456         public void ConvertAssignment(ExpressionType type, bool useInterpreter)
457         {
458             var lhs = Expression.Parameter(typeof(int));
459             var rhs = Expression.Constant(25);
460             Expression<Func<int, int>> doubleIt = x => 2 * x;
461             var lambda = Expression.Lambda<Func<int, int>>(
462                 Expression.MakeBinary(type, lhs, rhs, false, GetType().GetMethod(nameof(FiftyNinthBear)), doubleIt),
463                 lhs
464                 );
465             var func = lambda.Compile(useInterpreter);
466             Assert.Equal(118, func(15));
467         }
468 
469         [Theory, MemberData(nameof(AssignExpressionTypesAndNonUnaryLambdas))]
ConversionMustBeUnary(ExpressionType type, LambdaExpression conversion)470         public void ConversionMustBeUnary(ExpressionType type, LambdaExpression conversion)
471         {
472             var lhs = Expression.Parameter(typeof(int));
473             var rhs = Expression.Constant(25);
474             MethodInfo meth = GetType().GetMethod(nameof(FiftyNinthBear));
475             AssertExtensions.Throws<ArgumentException>(
476                 "conversion", () => Expression.MakeBinary(type, lhs, rhs, false, meth, conversion));
477         }
478 
479         [Theory, MemberData(nameof(AssignExpressionTypesAndNonIntegerReturnUnaryIntegerLambdas))]
ConversionMustConvertToLHSType(ExpressionType type, LambdaExpression conversion)480         public void ConversionMustConvertToLHSType(ExpressionType type, LambdaExpression conversion)
481         {
482             var lhs = Expression.Parameter(typeof(int));
483             var rhs = Expression.Constant(25);
484             MethodInfo meth = GetType().GetMethod(nameof(FiftyNinthBear));
485             Assert.Throws<InvalidOperationException>(() => Expression.MakeBinary(type, lhs, rhs, false, meth, conversion));
486         }
487 
488         [Theory, MemberData(nameof(AssignExpressionTypesAndNonIntegerTakingUnaryIntegerReturningLambda))]
ConversionMustConvertFromRHSType(ExpressionType type, LambdaExpression conversion)489         public void ConversionMustConvertFromRHSType(ExpressionType type, LambdaExpression conversion)
490         {
491             var lhs = Expression.Parameter(typeof(int));
492             var rhs = Expression.Constant(25);
493             MethodInfo meth = GetType().GetMethod(nameof(FiftyNinthBear));
494             Assert.Throws<InvalidOperationException>(() => Expression.MakeBinary(type, lhs, rhs, false, meth, conversion));
495         }
496 
497         private class AddsToSomethingElse : IEquatable<AddsToSomethingElse>
498         {
499             public int Value { get; }
500 
AddsToSomethingElse(int value)501             public AddsToSomethingElse(int value)
502             {
503                 Value = value;
504             }
505 
operator +(AddsToSomethingElse x, AddsToSomethingElse y)506             public static int operator +(AddsToSomethingElse x, AddsToSomethingElse y) => x.Value + y.Value;
507 
508             public bool Equals(AddsToSomethingElse other) => Value == other?.Value;
509 
510             public override bool Equals(object obj) => Equals(obj as AddsToSomethingElse);
511 
GetHashCode()512             public override int GetHashCode() => Value;
513         }
514 
StringAddition(int x, int y)515         private static string StringAddition(int x, int y) => (x + y).ToString();
516 
517         [Fact]
CannotAssignOpIfOpReturnNotAssignable()518         public void CannotAssignOpIfOpReturnNotAssignable()
519         {
520             var lhs = Expression.Parameter(typeof(AddsToSomethingElse));
521             var rhs = Expression.Constant(new AddsToSomethingElse(3));
522             AssertExtensions.Throws<ArgumentException>(null, () => Expression.AddAssign(lhs, rhs));
523         }
524 
525         [Theory, ClassData(typeof(CompilationTypes))]
CanAssignOpIfOpReturnNotAssignableButConversionFixes(bool useInterpreter)526         public void CanAssignOpIfOpReturnNotAssignableButConversionFixes(bool useInterpreter)
527         {
528             var lhs = Expression.Parameter(typeof(AddsToSomethingElse));
529             var rhs = Expression.Constant(new AddsToSomethingElse(3));
530             Expression<Func<int, AddsToSomethingElse>> conversion = x => new AddsToSomethingElse(x);
531             var exp = Expression.Lambda<Func<AddsToSomethingElse, AddsToSomethingElse>>(
532                 Expression.AddAssign(lhs, rhs, null, conversion),
533                 lhs
534                 );
535             var func = exp.Compile(useInterpreter);
536             Assert.Equal(new AddsToSomethingElse(10), func(new AddsToSomethingElse(7)));
537         }
538 
539         [Theory, PerCompilationType(nameof(AssignExpressionTypesArguments))]
ConvertOpAssignToMember(ExpressionType type, bool useInterpreter)540         public void ConvertOpAssignToMember(ExpressionType type, bool useInterpreter)
541         {
542             Box<int> box = new Box<int>(25);
543             Expression<Func<int, int>> doubleIt = x => x * 2;
544             var exp = Expression.Lambda<Func<int>>(
545                 Expression.MakeBinary(
546                     type,
547                     Expression.Property(Expression.Constant(box), "Value"),
548                     Expression.Constant(15),
549                     false,
550                     GetType().GetMethod(nameof(FiftyNinthBear)),
551                     doubleIt
552                     )
553                 );
554             var act = exp.Compile(useInterpreter);
555             Assert.Equal(118, act());
556             Assert.Equal(118, box.Value);
557         }
558 
559         [Theory, PerCompilationType(nameof(AssignExpressionTypesArguments))]
ConvertOpAssignToArrayIndex(ExpressionType type, bool useInterpreter)560         public void ConvertOpAssignToArrayIndex(ExpressionType type, bool useInterpreter)
561         {
562             int[] array = {0, 0, 25, 0};
563             Expression<Func<int, int>> doubleIt = x => x * 2;
564             var exp = Expression.Lambda<Func<int>>(
565                 Expression.MakeBinary(
566                     type,
567                     Expression.ArrayAccess(Expression.Constant(array), Expression.Constant(2)),
568                     Expression.Constant(15),
569                     false,
570                     GetType().GetMethod(nameof(FiftyNinthBear)),
571                     doubleIt
572                     )
573                 );
574             var act = exp.Compile(useInterpreter);
575             Assert.Equal(118, act());
576             Assert.Equal(118, array[2]);
577         }
578 
ByRefInts(ref int x, int y)579         private delegate int ByRefInts(ref int x, int y);
580 
BothByRefInts(ref int x, ref int y)581         private delegate int BothByRefInts(ref int x, ref int y);
582 
583         [Theory, PerCompilationType(nameof(AssignExpressionMethodInfoUsingFactoriesArguments))]
MethodNoConvertOpWriteByRefParameter(Func<Expression, Expression, MethodInfo, BinaryExpression> factory, bool useInterpreter)584         public void MethodNoConvertOpWriteByRefParameter(Func<Expression, Expression, MethodInfo, BinaryExpression> factory, bool useInterpreter)
585         {
586             var pX = Expression.Parameter(typeof(int).MakeByRefType());
587             var pY = Expression.Parameter(typeof(int));
588             var exp = Expression.Lambda<ByRefInts>(factory(pX, pY, GetType().GetMethod(nameof(FiftyNinthBear))), pX, pY);
589             var del = exp.Compile(useInterpreter);
590             int arg = 5;
591             Assert.Equal(59, del(ref arg, 35));
592             Assert.Equal(59, arg);
593         }
594 
ByRefSomeElse(ref AddsToSomethingElse x, AddsToSomethingElse y)595         private delegate AddsToSomethingElse ByRefSomeElse(ref AddsToSomethingElse x, AddsToSomethingElse y);
596 
597         [Theory, ClassData(typeof(CompilationTypes))]
ConvertOpWriteByRefParameterOverloadedOperator(bool useInterpreter)598         public void ConvertOpWriteByRefParameterOverloadedOperator(bool useInterpreter)
599         {
600             var pX = Expression.Parameter(typeof(AddsToSomethingElse).MakeByRefType());
601             var pY = Expression.Parameter(typeof(AddsToSomethingElse));
602             Expression<Func<int, AddsToSomethingElse>> conv = x => new AddsToSomethingElse(x);
603             var exp = Expression.Lambda<ByRefSomeElse>(Expression.AddAssign(pX, pY, null, conv), pX, pY);
604             var del = exp.Compile(useInterpreter);
605             AddsToSomethingElse arg = new AddsToSomethingElse(5);
606             AddsToSomethingElse result = del(ref arg, new AddsToSomethingElse(35));
607             Assert.Equal(result, arg);
608         }
609     }
610 }
611