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 static class CallTests 12 { 13 private struct Mutable 14 { 15 private int x; 16 17 public int X 18 { 19 get { return x; } 20 set { x = value; } 21 } 22 23 public int this[int i] 24 { 25 get { return x; } 26 set { x = value; } 27 } 28 FooSystem.Linq.Expressions.Tests.CallTests.Mutable29 public int Foo() 30 { 31 return x++; 32 } 33 } 34 35 private class Wrapper<T> 36 { 37 public const int Zero = 0; 38 public T Field; 39 #pragma warning disable 649 // For testing purposes 40 public readonly T ReadOnlyField; 41 #pragma warning restore 42 public T Property 43 { 44 get { return Field; } 45 set { Field = value; } 46 } 47 } 48 49 private static class Methods 50 { ByRef(ref int x)51 public static void ByRef(ref int x) { ++x; } 52 } 53 54 [Theory] 55 [ClassData(typeof(CompilationTypes))] UnboxReturnsReference(bool useInterpreter)56 public static void UnboxReturnsReference(bool useInterpreter) 57 { 58 ParameterExpression p = Expression.Parameter(typeof(object)); 59 UnaryExpression unbox = Expression.Unbox(p, typeof(Mutable)); 60 MethodCallExpression call = Expression.Call(unbox, typeof(Mutable).GetMethod("Foo")); 61 Func<object, int> lambda = Expression.Lambda<Func<object, int>>(call, p).Compile(useInterpreter); 62 63 object boxed = new Mutable(); 64 Assert.Equal(0, lambda(boxed)); 65 Assert.Equal(1, lambda(boxed)); 66 Assert.Equal(2, lambda(boxed)); 67 Assert.Equal(3, lambda(boxed)); 68 } 69 70 [Theory] 71 [ClassData(typeof(CompilationTypes))] ArrayWriteBack(bool useInterpreter)72 public static void ArrayWriteBack(bool useInterpreter) 73 { 74 ParameterExpression p = Expression.Parameter(typeof(Mutable[])); 75 BinaryExpression indexed = Expression.ArrayIndex(p, Expression.Constant(0)); 76 MethodCallExpression call = Expression.Call(indexed, typeof(Mutable).GetMethod("Foo")); 77 Func<Mutable[], int> lambda = Expression.Lambda<Func<Mutable[], int>>(call, p).Compile(useInterpreter); 78 79 var array = new Mutable[1]; 80 Assert.Equal(0, lambda(array)); 81 Assert.Equal(1, lambda(array)); 82 Assert.Equal(2, lambda(array)); 83 } 84 85 [Theory] 86 [ClassData(typeof(CompilationTypes))] MultiRankArrayWriteBack(bool useInterpreter)87 public static void MultiRankArrayWriteBack(bool useInterpreter) 88 { 89 ParameterExpression p = Expression.Parameter(typeof(Mutable[,])); 90 MethodCallExpression indexed = Expression.ArrayIndex(p, Expression.Constant(0), Expression.Constant(0)); 91 MethodCallExpression call = Expression.Call(indexed, typeof(Mutable).GetMethod("Foo")); 92 Func<Mutable[,], int> lambda = Expression.Lambda<Func<Mutable[,], int>>(call, p).Compile(useInterpreter); 93 94 var array = new Mutable[1, 1]; 95 Assert.Equal(0, lambda(array)); 96 Assert.Equal(1, lambda(array)); 97 Assert.Equal(2, lambda(array)); 98 } 99 100 [Theory] 101 [ClassData(typeof(CompilationTypes))] ArrayAccessWriteBack(bool useInterpreter)102 public static void ArrayAccessWriteBack(bool useInterpreter) 103 { 104 ParameterExpression p = Expression.Parameter(typeof(Mutable[])); 105 IndexExpression indexed = Expression.ArrayAccess(p, Expression.Constant(0)); 106 MethodCallExpression call = Expression.Call(indexed, typeof(Mutable).GetMethod("Foo")); 107 Func<Mutable[], int> lambda = Expression.Lambda<Func<Mutable[], int>>(call, p).Compile(useInterpreter); 108 109 var array = new Mutable[1]; 110 Assert.Equal(0, lambda(array)); 111 Assert.Equal(1, lambda(array)); 112 Assert.Equal(2, lambda(array)); 113 } 114 115 [Theory] 116 [ClassData(typeof(CompilationTypes))] MultiRankArrayAccessWriteBack(bool useInterpreter)117 public static void MultiRankArrayAccessWriteBack(bool useInterpreter) 118 { 119 ParameterExpression p = Expression.Parameter(typeof(Mutable[,])); 120 IndexExpression indexed = Expression.ArrayAccess(p, Expression.Constant(0), Expression.Constant(0)); 121 MethodCallExpression call = Expression.Call(indexed, typeof(Mutable).GetMethod("Foo")); 122 Func<Mutable[,], int> lambda = Expression.Lambda<Func<Mutable[,], int>>(call, p).Compile(useInterpreter); 123 124 var array = new Mutable[1, 1]; 125 Assert.Equal(0, lambda(array)); 126 Assert.Equal(1, lambda(array)); 127 Assert.Equal(2, lambda(array)); 128 } 129 130 [Theory] 131 [ClassData(typeof(CompilationTypes))] IndexedPropertyAccessNoWriteBack(bool useInterpreter)132 public static void IndexedPropertyAccessNoWriteBack(bool useInterpreter) 133 { 134 ParameterExpression p = Expression.Parameter(typeof(List<Mutable>)); 135 IndexExpression indexed = Expression.Property(p, typeof(List<Mutable>).GetProperty("Item"), Expression.Constant(0)); 136 MethodCallExpression call = Expression.Call(indexed, typeof(Mutable).GetMethod("Foo")); 137 Func<List<Mutable>, int> lambda = Expression.Lambda<Func<List<Mutable>, int>>(call, p).Compile(useInterpreter); 138 139 var list = new List<Mutable> { new Mutable() }; 140 Assert.Equal(0, lambda(list)); 141 Assert.Equal(0, lambda(list)); 142 } 143 144 [Theory] 145 [ClassData(typeof(CompilationTypes))] FieldAccessWriteBack(bool useInterpreter)146 public static void FieldAccessWriteBack(bool useInterpreter) 147 { 148 ParameterExpression p = Expression.Parameter(typeof(Wrapper<Mutable>)); 149 MemberExpression member = Expression.Field(p, typeof(Wrapper<Mutable>).GetField("Field")); 150 MethodCallExpression call = Expression.Call(member, typeof(Mutable).GetMethod("Foo")); 151 Func<Wrapper<Mutable>, int> lambda = Expression.Lambda<Func<Wrapper<Mutable>, int>>(call, p).Compile(useInterpreter); 152 153 var wrapper = new Wrapper<Mutable>(); 154 Assert.Equal(0, lambda(wrapper)); 155 Assert.Equal(1, lambda(wrapper)); 156 Assert.Equal(2, lambda(wrapper)); 157 } 158 159 [Theory] 160 [ClassData(typeof(CompilationTypes))] PropertyAccessNoWriteBack(bool useInterpreter)161 public static void PropertyAccessNoWriteBack(bool useInterpreter) 162 { 163 ParameterExpression p = Expression.Parameter(typeof(Wrapper<Mutable>)); 164 MemberExpression member = Expression.Property(p, typeof(Wrapper<Mutable>).GetProperty("Property")); 165 MethodCallExpression call = Expression.Call(member, typeof(Mutable).GetMethod("Foo")); 166 Func<Wrapper<Mutable>, int> lambda = Expression.Lambda<Func<Wrapper<Mutable>, int>>(call, p).Compile(useInterpreter); 167 168 var wrapper = new Wrapper<Mutable>(); 169 Assert.Equal(0, lambda(wrapper)); 170 Assert.Equal(0, lambda(wrapper)); 171 } 172 173 [Theory] 174 [ClassData(typeof(CompilationTypes))] ReadonlyFieldAccessWriteBack(bool useInterpreter)175 public static void ReadonlyFieldAccessWriteBack(bool useInterpreter) 176 { 177 ParameterExpression p = Expression.Parameter(typeof(Wrapper<Mutable>)); 178 MemberExpression member = Expression.Field(p, typeof(Wrapper<Mutable>).GetField("ReadOnlyField")); 179 MethodCallExpression call = Expression.Call(member, typeof(Mutable).GetMethod("Foo")); 180 Func<Wrapper<Mutable>, int> lambda = Expression.Lambda<Func<Wrapper<Mutable>, int>>(call, p).Compile(useInterpreter); 181 182 var wrapper = new Wrapper<Mutable>(); 183 Assert.Equal(0, lambda(wrapper)); 184 Assert.Equal(0, lambda(wrapper)); 185 Assert.Equal(0, lambda(wrapper)); 186 } 187 188 [Theory] 189 [ClassData(typeof(CompilationTypes))] ConstFieldAccessWriteBack(bool useInterpreter)190 public static void ConstFieldAccessWriteBack(bool useInterpreter) 191 { 192 MemberExpression member = Expression.Field(null, typeof(Wrapper<Mutable>).GetField("Zero")); 193 MethodCallExpression call = Expression.Call(member, typeof(int).GetMethod("GetType")); 194 Func<Type> lambda = Expression.Lambda<Func<Type>>(call).Compile(useInterpreter); 195 196 var wrapper = new Wrapper<Mutable>(); 197 Assert.Equal(typeof(int), lambda()); 198 } 199 200 [Theory] 201 [ClassData(typeof(CompilationTypes))] CallByRefMutableStructPropertyWriteBack(bool useInterpreter)202 public static void CallByRefMutableStructPropertyWriteBack(bool useInterpreter) 203 { 204 ParameterExpression p = Expression.Parameter(typeof(Mutable)); 205 MemberExpression x = Expression.Property(p, "X"); 206 MethodCallExpression call = Expression.Call(typeof(Methods).GetMethod("ByRef"), x); 207 BlockExpression body = Expression.Block(call, x); 208 Func<Mutable, int> lambda = Expression.Lambda<Func<Mutable, int>>(body, p).Compile(useInterpreter); 209 210 var m = new Mutable() { X = 41 }; 211 Assert.Equal(42, lambda(m)); 212 } 213 214 [Theory] 215 [ClassData(typeof(CompilationTypes))] CallByRefMutableStructIndexWriteBack(bool useInterpreter)216 public static void CallByRefMutableStructIndexWriteBack(bool useInterpreter) 217 { 218 // Should not produce tail-call, but should still succeed 219 ParameterExpression p = Expression.Parameter(typeof(Mutable)); 220 IndexExpression x = Expression.MakeIndex(p, typeof(Mutable).GetProperty("Item"), new[] { Expression.Constant(0) }); 221 MethodCallExpression call = Expression.Call(typeof(Methods).GetMethod("ByRef"), x); 222 Action<Mutable> act = Expression.Lambda<Action<Mutable>>(call, true, p).Compile(useInterpreter); 223 224 Mutable m = new Mutable { X = 41 }; 225 act(m); 226 } 227 228 [Theory] 229 [ClassData(typeof(CompilationTypes))] CallByRefAttemptTailCall(bool useInterpreter)230 public static void CallByRefAttemptTailCall(bool useInterpreter) 231 { 232 ParameterExpression p = Expression.Parameter(typeof(Mutable)); 233 IndexExpression x = Expression.MakeIndex(p, typeof(Mutable).GetProperty("Item"), new[] { Expression.Constant(0) }); 234 MethodCallExpression call = Expression.Call(typeof(Methods).GetMethod("ByRef"), x); 235 BlockExpression body = Expression.Block(call, x); 236 Func<Mutable, int> lambda = Expression.Lambda<Func<Mutable, int>>(body, p).Compile(useInterpreter); 237 238 var m = new Mutable() { X = 41 }; 239 Assert.Equal(42, lambda(m)); 240 } 241 242 [Theory] 243 [ClassData(typeof(CompilationTypes))] Call_InstanceNullInside_ThrowsNullReferenceExceptionOnInvocation(bool useInterpreter)244 public static void Call_InstanceNullInside_ThrowsNullReferenceExceptionOnInvocation(bool useInterpreter) 245 { 246 Expression call = Expression.Call(Expression.Constant(null, typeof(NonGenericClass)), typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.InstanceMethod))); 247 Action compiledDelegate = Expression.Lambda<Action>(call).Compile(useInterpreter); 248 TargetInvocationException exception = Assert.Throws<TargetInvocationException>(() => compiledDelegate.DynamicInvoke()); 249 Assert.IsType<NullReferenceException>(exception.InnerException); 250 } 251 Call_NoParameters_TestData()252 public static IEnumerable<object[]> Call_NoParameters_TestData() 253 { 254 // Basic 255 yield return new object[] { Expression.Constant(new ClassWithInterface1()), typeof(ClassWithInterface1).GetMethod(nameof(ClassWithInterface1.Method)), 2 }; 256 yield return new object[] { Expression.Constant(new StructWithInterface1()), typeof(StructWithInterface1).GetMethod(nameof(StructWithInterface1.Method)), 2 }; 257 258 // Object method 259 yield return new object[] { Expression.Constant(new ClassWithInterface1()), typeof(object).GetMethod(nameof(object.GetType)), typeof(ClassWithInterface1) }; 260 yield return new object[] { Expression.Constant(new StructWithInterface1()), typeof(object).GetMethod(nameof(object.GetType)), typeof(StructWithInterface1) }; 261 yield return new object[] { Expression.Constant(Int32Enum.A), typeof(object).GetMethod(nameof(object.GetType)), typeof(Int32Enum) }; 262 263 // ValueType method from struct 264 yield return new object[] { Expression.Constant(new StructWithInterface1()), typeof(ValueType).GetMethod(nameof(ValueType.ToString)), new StructWithInterface1().ToString() }; 265 266 // Enum method from enum 267 yield return new object[] { Expression.Constant(Int32Enum.A), typeof(Enum).GetMethod(nameof(Enum.ToString), new Type[0]), "A" }; 268 269 // Interface method 270 yield return new object[] { Expression.Constant(new ClassWithInterface1()), typeof(Interface1).GetMethod(nameof(Interface1.InterfaceMethod)), 1 }; 271 yield return new object[] { Expression.Constant(new StructWithInterface1()), typeof(Interface1).GetMethod(nameof(Interface1.InterfaceMethod)), 1 }; 272 yield return new object[] { Expression.Constant(new ClassWithCompoundInterface()), typeof(Interface1).GetMethod(nameof(Interface1.InterfaceMethod)), 1 }; 273 yield return new object[] { Expression.Constant(new StructWithCompoundInterface()), typeof(Interface1).GetMethod(nameof(Interface1.InterfaceMethod)), 1 }; 274 275 // Interface method, interface type 276 yield return new object[] { Expression.Constant(new ClassWithInterface1(), typeof(Interface1)), typeof(Interface1).GetMethod(nameof(Interface1.InterfaceMethod)), 1 }; 277 yield return new object[] { Expression.Constant(new StructWithInterface1(), typeof(Interface1)), typeof(Interface1).GetMethod(nameof(Interface1.InterfaceMethod)), 1 }; 278 } 279 280 [Theory] 281 [PerCompilationType(nameof(Call_NoParameters_TestData))] Call_NoParameters(Expression instance, MethodInfo method, object expected, bool useInterpreter)282 public static void Call_NoParameters(Expression instance, MethodInfo method, object expected, bool useInterpreter) 283 { 284 Expression call = Expression.Call(instance, method); 285 Delegate compiledDelegate = Expression.Lambda(call).Compile(useInterpreter); 286 Assert.Equal(expected, compiledDelegate.DynamicInvoke()); 287 } 288 289 private static Expression s_valid => Expression.Constant(5); 290 291 private static MethodInfo s_method0 = typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.Method0)); 292 private static MethodInfo s_method1 = typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.Method1)); 293 private static MethodInfo s_method2 = typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.Method2)); 294 private static MethodInfo s_method3 = typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.Method3)); 295 private static MethodInfo s_method4 = typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.Method4)); 296 private static MethodInfo s_method5 = typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.Method5)); 297 private static MethodInfo s_method6 = typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.Method6)); 298 private static MethodInfo s_method7 = typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.Method7)); 299 Method_Invalid_TestData()300 public static IEnumerable<object[]> Method_Invalid_TestData() 301 { 302 yield return new object[] { null, typeof(ArgumentNullException) }; 303 yield return new object[] { typeof(GenericClass<>).GetMethod(nameof(GenericClass<string>.NonGenericMethod)), typeof(ArgumentException) }; 304 yield return new object[] { typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.GenericMethod)), typeof(ArgumentException) }; 305 } 306 307 [Theory] 308 [MemberData(nameof(Method_Invalid_TestData))] Method_Invalid_ThrowsArgumentException(MethodInfo method, Type exceptionType)309 public static void Method_Invalid_ThrowsArgumentException(MethodInfo method, Type exceptionType) 310 { 311 AssertArgumentException(() => Expression.Call(null, method), exceptionType, "method"); 312 AssertArgumentException(() => Expression.Call(null, method, s_valid, s_valid), exceptionType, "method"); 313 AssertArgumentException(() => Expression.Call(null, method, s_valid, s_valid, s_valid), exceptionType, "method"); 314 AssertArgumentException(() => Expression.Call(null, method, new Expression[0]), exceptionType, "method"); 315 AssertArgumentException(() => Expression.Call(null, method, (IEnumerable<Expression>)new Expression[0]), exceptionType, "method"); 316 317 AssertArgumentException(() => Expression.Call(method, s_valid), exceptionType, "method"); 318 AssertArgumentException(() => Expression.Call(method, s_valid, s_valid), exceptionType, "method"); 319 AssertArgumentException(() => Expression.Call(method, s_valid, s_valid, s_valid), exceptionType, "method"); 320 AssertArgumentException(() => Expression.Call(method, s_valid, s_valid, s_valid, s_valid), exceptionType, "method"); 321 AssertArgumentException(() => Expression.Call(method, s_valid, s_valid, s_valid, s_valid, s_valid), exceptionType, "method"); 322 AssertArgumentException(() => Expression.Call(method, new Expression[0]), exceptionType, "method"); 323 AssertArgumentException(() => Expression.Call(method, (IEnumerable<Expression>)new Expression[0]), exceptionType, "method"); 324 } 325 326 [Fact] Method_Invalid_Via_Name()327 public static void Method_Invalid_Via_Name() 328 { 329 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(typeof(GenericClass<>), nameof(GenericClass<string>.NonGenericMethod), Type.EmptyTypes)); 330 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(typeof(GenericClass<>).MakeGenericType(typeof(GenericClass<>)), nameof(GenericClass<string>.NonGenericMethod), Type.EmptyTypes)); 331 } 332 Method_DoesntBelongToInstance_TestData()333 public static IEnumerable<object[]> Method_DoesntBelongToInstance_TestData() 334 { 335 // Different declaring type 336 yield return new object[] { Expression.Constant(new ClassWithInterface1()), typeof(OtherClassWithInterface1).GetMethod(nameof(Interface1.InterfaceMethod)) }; 337 yield return new object[] { Expression.Constant(new StructWithInterface1()), typeof(OtherStructWithInterface1).GetMethod(nameof(Interface1.InterfaceMethod)) }; 338 339 // Different interface 340 yield return new object[] { Expression.Constant(new ClassWithInterface1()), typeof(Interface2).GetMethod(nameof(Interface2.InterfaceMethod)) }; 341 yield return new object[] { Expression.Constant(new StructWithInterface1()), typeof(Interface2).GetMethod(nameof(Interface2.InterfaceMethod)) }; 342 343 // Custom type 344 yield return new object[] { Expression.Constant(new ClassWithInterface1(), typeof(object)), typeof(ClassWithInterface1).GetMethod(nameof(ClassWithInterface1.Method)) }; 345 yield return new object[] { Expression.Constant(new StructWithInterface1(), typeof(object)), typeof(ClassWithInterface1).GetMethod(nameof(ClassWithInterface1.Method)) }; 346 } 347 348 [Theory] 349 [MemberData(nameof(Method_DoesntBelongToInstance_TestData))] Method_DoesntBelongToInstance_ThrowsArgumentException(Expression instance, MethodInfo method)350 public static void Method_DoesntBelongToInstance_ThrowsArgumentException(Expression instance, MethodInfo method) 351 { 352 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(instance, method)); 353 } 354 355 [Fact] InstanceMethod_NullInstance_ThrowsArgumentException()356 public static void InstanceMethod_NullInstance_ThrowsArgumentException() 357 { 358 MethodInfo method = typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.InstanceMethod)); 359 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(method, s_valid)); 360 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(method, s_valid, s_valid)); 361 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(method, s_valid, s_valid, s_valid)); 362 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(method, s_valid, s_valid, s_valid, s_valid)); 363 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(method, s_valid, s_valid, s_valid, s_valid, s_valid)); 364 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(method, new Expression[0])); 365 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(method, (IEnumerable<Expression>)new Expression[0])); 366 367 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(null, method, s_valid)); 368 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(null, method, s_valid, s_valid)); 369 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(null, method, s_valid, s_valid, s_valid)); 370 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(null, method, new Expression[0])); 371 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(null, method, (IEnumerable<Expression>)new Expression[0])); 372 } 373 374 [Fact] StaticMethod_NonNullInstance_ThrowsArgumentException()375 public static void StaticMethod_NonNullInstance_ThrowsArgumentException() 376 { 377 Expression instance = Expression.Constant(new NonGenericClass()); 378 MethodInfo method = typeof(NonGenericClass).GetMethod(nameof(NonGenericClass.StaticMethod)); 379 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(instance, method, s_valid)); 380 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(instance, method, s_valid, s_valid)); 381 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(instance, method, s_valid, s_valid, s_valid)); 382 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(instance, method, new Expression[0])); 383 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(instance, method, (IEnumerable<Expression>)new Expression[0])); 384 } 385 InvalidArg_TestData()386 public static IEnumerable<object[]> InvalidArg_TestData() 387 { 388 yield return new object[] { null, typeof(ArgumentNullException) }; 389 yield return new object[] { Expression.Property(null, typeof(Unreadable<string>), nameof(Unreadable<string>.WriteOnly)), typeof(ArgumentException) }; 390 yield return new object[] { Expression.Constant("abc"), typeof(ArgumentException) }; 391 } 392 393 [Theory] 394 [MemberData(nameof(InvalidArg_TestData))] Arg0_Invalid(Expression arg, Type exceptionType)395 public static void Arg0_Invalid(Expression arg, Type exceptionType) 396 { 397 AssertArgumentException(() => Expression.Call(s_method1, arg), exceptionType, "arg0"); 398 AssertArgumentException(() => Expression.Call(s_method2, arg, s_valid), exceptionType, "arg0"); 399 AssertArgumentException(() => Expression.Call(s_method3, arg, s_valid, s_valid), exceptionType, "arg0"); 400 AssertArgumentException(() => Expression.Call(s_method4, arg, s_valid, s_valid, s_valid), exceptionType, "arg0"); 401 AssertArgumentException(() => Expression.Call(s_method5, arg, s_valid, s_valid, s_valid, s_valid), exceptionType, "arg0"); 402 403 AssertArgumentException(() => Expression.Call(null, s_method1, arg), exceptionType, "arg0"); 404 AssertArgumentException(() => Expression.Call(null, s_method2, arg, s_valid), exceptionType, "arg0"); 405 AssertArgumentException(() => Expression.Call(null, s_method3, arg, s_valid, s_valid), exceptionType, "arg0"); 406 } 407 408 [Theory] 409 [MemberData(nameof(InvalidArg_TestData))] Arg1_Invalid(Expression arg, Type exceptionType)410 public static void Arg1_Invalid(Expression arg, Type exceptionType) 411 { 412 AssertArgumentException(() => Expression.Call(s_method2, s_valid, arg), exceptionType, "arg1"); 413 AssertArgumentException(() => Expression.Call(s_method3, s_valid, arg, s_valid), exceptionType, "arg1"); 414 AssertArgumentException(() => Expression.Call(s_method4, s_valid, arg, s_valid, s_valid), exceptionType, "arg1"); 415 AssertArgumentException(() => Expression.Call(s_method5, s_valid, arg, s_valid, s_valid, s_valid), exceptionType, "arg1"); 416 417 AssertArgumentException(() => Expression.Call(null, s_method2, s_valid, arg), exceptionType, "arg1"); 418 AssertArgumentException(() => Expression.Call(null, s_method3, s_valid, arg, s_valid), exceptionType, "arg1"); 419 } 420 421 [Theory] 422 [MemberData(nameof(InvalidArg_TestData))] Arg2_Invalid(Expression arg, Type exceptionType)423 public static void Arg2_Invalid(Expression arg, Type exceptionType) 424 { 425 AssertArgumentException(() => Expression.Call(s_method3, s_valid, s_valid, arg), exceptionType, "arg2"); 426 AssertArgumentException(() => Expression.Call(s_method4, s_valid, s_valid, arg, s_valid), exceptionType, "arg2"); 427 AssertArgumentException(() => Expression.Call(s_method5, s_valid, s_valid, arg, s_valid, s_valid), exceptionType, "arg2"); 428 429 AssertArgumentException(() => Expression.Call(null, s_method3, s_valid, s_valid, arg), exceptionType, "arg2"); 430 } 431 432 [Theory] 433 [MemberData(nameof(InvalidArg_TestData))] Arg3_Invalid(Expression arg, Type exceptionType)434 public static void Arg3_Invalid(Expression arg, Type exceptionType) 435 { 436 AssertArgumentException(() => Expression.Call(s_method4, s_valid, s_valid, s_valid, arg), exceptionType, "arg3"); 437 AssertArgumentException(() => Expression.Call(s_method5, s_valid, s_valid, s_valid, arg, s_valid), exceptionType, "arg3"); 438 } 439 440 [Theory] 441 [MemberData(nameof(InvalidArg_TestData))] Arg4_Invalid(Expression arg, Type exceptionType)442 public static void Arg4_Invalid(Expression arg, Type exceptionType) 443 { 444 AssertArgumentException(() => Expression.Call(s_method5, s_valid, s_valid, s_valid, s_valid, arg), exceptionType, "arg4"); 445 } 446 AssertArgumentException(Action action, Type exceptionType, string paramName)447 private static void AssertArgumentException(Action action, Type exceptionType, string paramName) 448 { 449 ArgumentException ex = (ArgumentException)Assert.Throws(exceptionType, action); 450 if (!PlatformDetection.IsNetNative) // The .NET Native toolchain optimizes away exception ParamNames 451 { 452 Assert.Equal(paramName, ex.ParamName); 453 } 454 } 455 456 [Theory] 457 [InlineData(typeof(NonGenericClass), nameof(NonGenericClass.Method0), 0)] 458 [InlineData(typeof(NonGenericClass), nameof(NonGenericClass.Method1), 1)] 459 [InlineData(typeof(NonGenericClass), nameof(NonGenericClass.Method2), 2)] 460 [InlineData(typeof(NonGenericClass), nameof(NonGenericClass.Method3), 3)] 461 [InlineData(typeof(NonGenericClass), nameof(NonGenericClass.Method4), 4)] 462 [InlineData(typeof(NonGenericClass), nameof(NonGenericClass.Method5), 5)] InvalidArgumentCount_ThrowsArgumentException(Type type, string name, int count)463 public static void InvalidArgumentCount_ThrowsArgumentException(Type type, string name, int count) 464 { 465 MethodInfo method = type.GetMethod(name); 466 Expression arg = Expression.Constant("abc"); 467 if (count != 0) 468 { 469 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(method)); 470 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(null, method)); 471 } 472 if (count != 1) 473 { 474 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(method, arg)); 475 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(null, method, arg)); 476 } 477 if (count != 2) 478 { 479 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(method, arg, arg)); 480 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(null, method, arg, arg)); 481 } 482 if (count != 3) 483 { 484 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(method, arg, arg, arg)); 485 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(null, method, arg, arg, arg)); 486 } 487 if (count != 4) 488 { 489 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(method, arg, arg, arg, arg)); 490 } 491 if (count != 5) 492 { 493 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(method, arg, arg, arg, arg, arg)); 494 } 495 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(method, Enumerable.Repeat(arg, count + 1).ToArray())); 496 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(method, Enumerable.Repeat(arg, count + 1))); 497 498 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(null, method, Enumerable.Repeat(arg, count + 1).ToArray())); 499 AssertExtensions.Throws<ArgumentException>("method", () => Expression.Call(null, method, Enumerable.Repeat(arg, count + 1))); 500 } 501 502 [Fact] MethodName_NullInstance_ThrowsArgumentNullException()503 public static void MethodName_NullInstance_ThrowsArgumentNullException() 504 { 505 AssertExtensions.Throws<ArgumentNullException>("instance", () => Expression.Call((Expression)null, "methodName", new Type[0], new Expression[0])); 506 } 507 508 [Fact] MethodName_NullType_ThrowsArgumentNullException()509 public static void MethodName_NullType_ThrowsArgumentNullException() 510 { 511 AssertExtensions.Throws<ArgumentNullException>("type", () => Expression.Call((Type)null, "methodName", new Type[0], new Expression[0])); 512 } 513 514 [Fact] NullMethodName_ThrowsArgumentNullException()515 public static void NullMethodName_ThrowsArgumentNullException() 516 { 517 AssertExtensions.Throws<ArgumentNullException>("methodName", () => Expression.Call(Expression.Constant(new NonGenericClass()), null, new Type[0], new Expression[0])); 518 AssertExtensions.Throws<ArgumentNullException>("methodName", () => Expression.Call(typeof(NonGenericClass), null, new Type[0], new Expression[0])); 519 } 520 521 [Fact] MethodName_DoesNotExist_ThrowsInvalidOperationException()522 public static void MethodName_DoesNotExist_ThrowsInvalidOperationException() 523 { 524 Assert.Throws<InvalidOperationException>(() => Expression.Call(Expression.Constant(new NonGenericClass()), "NoSuchMethod", null)); 525 Assert.Throws<InvalidOperationException>(() => Expression.Call(typeof(NonGenericClass), "NoSuchMethod", null)); 526 } 527 InvalidTypeArgs_TestData()528 public static IEnumerable<object[]> InvalidTypeArgs_TestData() 529 { 530 yield return new object[] { null }; 531 yield return new object[] { new Type[0] }; 532 yield return new object[] { new Type[2] }; 533 } 534 535 [Theory] 536 [MemberData(nameof(InvalidTypeArgs_TestData))] MethodName_NoSuchGenericMethodWithTypeArgs_ThrowsInvalidOperationException(Type[] typeArgs)537 public static void MethodName_NoSuchGenericMethodWithTypeArgs_ThrowsInvalidOperationException(Type[] typeArgs) 538 { 539 Assert.Throws<InvalidOperationException>(() => Expression.Call(Expression.Constant(new NonGenericClass()), nameof(NonGenericClass.GenericInstanceMethod), typeArgs)); 540 Assert.Throws<InvalidOperationException>(() => Expression.Call(typeof(NonGenericClass), nameof(NonGenericClass.GenericStaticMethod), typeArgs)); 541 } 542 543 [Fact] MethodName_TypeArgsDontMatchConstraints_ThrowsArgumentException()544 public static void MethodName_TypeArgsDontMatchConstraints_ThrowsArgumentException() 545 { 546 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(Expression.Constant(new NonGenericClass()), nameof(NonGenericClass.ConstrainedInstanceMethod), new Type[] { typeof(object) })); 547 AssertExtensions.Throws<ArgumentException>(null, () => Expression.Call(typeof(NonGenericClass), nameof(NonGenericClass.ConstrainedStaticMethod), new Type[] { typeof(object) })); 548 } 549 550 [Fact] MethodName_NonGenericMethodHasTypeArgs_ThrowsInvalidOperationException()551 public static void MethodName_NonGenericMethodHasTypeArgs_ThrowsInvalidOperationException() 552 { 553 Assert.Throws<InvalidOperationException>(() => Expression.Call(Expression.Constant(new NonGenericClass()), nameof(NonGenericClass.InstanceMethod), new Type[1])); 554 Assert.Throws<InvalidOperationException>(() => Expression.Call(typeof(NonGenericClass), nameof(NonGenericClass.StaticMethod), new Type[1])); 555 } 556 557 [Fact] MethodName_TypeArgsHasNullValue_ThrowsArgumentNullException()558 public static void MethodName_TypeArgsHasNullValue_ThrowsArgumentNullException() 559 { 560 AssertExtensions.Throws<ArgumentNullException>(null, () => Expression.Call(Expression.Constant(new NonGenericClass()), nameof(NonGenericClass.GenericInstanceMethod), new Type[] { null })); 561 AssertExtensions.Throws<ArgumentNullException>(null, () => Expression.Call(typeof(NonGenericClass), nameof(NonGenericClass.GenericStaticMethod), new Type[] { null })); 562 } 563 564 [Fact] MethodName_ArgumentsHasNullValue_ThrowsArgumentNullException()565 public static void MethodName_ArgumentsHasNullValue_ThrowsArgumentNullException() 566 { 567 AssertExtensions.Throws<ArgumentNullException>("arguments", () => Expression.Call(Expression.Constant(new NonGenericClass()), nameof(NonGenericClass.InstanceMethod1), new Type[0], new Expression[] { null })); 568 AssertExtensions.Throws<ArgumentNullException>("arguments", () => Expression.Call(typeof(NonGenericClass), nameof(NonGenericClass.StaticMethod1), new Type[0], new Expression[] { null })); 569 } 570 571 [Fact] MethodName_ArgumentsHasNullValueButDifferentCount_ThrowsInvalidOperationException()572 public static void MethodName_ArgumentsHasNullValueButDifferentCount_ThrowsInvalidOperationException() 573 { 574 Assert.Throws<InvalidOperationException>(() => Expression.Call(Expression.Constant(new NonGenericClass()), nameof(NonGenericClass.InstanceMethod1), new Type[0], new Expression[] { null, Expression.Constant("") })); 575 Assert.Throws<InvalidOperationException>(() => Expression.Call(typeof(NonGenericClass), nameof(NonGenericClass.StaticMethod1), new Type[0], new Expression[] { null, Expression.Constant("") })); 576 } 577 578 [Fact] ToStringTest()579 public static void ToStringTest() 580 { 581 // NB: Static methods are inconsistent compared to static members; the declaring type is not included 582 583 MethodCallExpression e1 = Expression.Call(null, typeof(SomeMethods).GetMethod(nameof(SomeMethods.S0), BindingFlags.Static | BindingFlags.Public)); 584 Assert.Equal("S0()", e1.ToString()); 585 586 MethodCallExpression e2 = Expression.Call(null, typeof(SomeMethods).GetMethod(nameof(SomeMethods.S1), BindingFlags.Static | BindingFlags.Public), Expression.Parameter(typeof(int), "x")); 587 Assert.Equal("S1(x)", e2.ToString()); 588 589 MethodCallExpression e3 = Expression.Call(null, typeof(SomeMethods).GetMethod(nameof(SomeMethods.S2), BindingFlags.Static | BindingFlags.Public), Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")); 590 Assert.Equal("S2(x, y)", e3.ToString()); 591 592 MethodCallExpression e4 = Expression.Call(Expression.Parameter(typeof(SomeMethods), "o"), typeof(SomeMethods).GetMethod(nameof(SomeMethods.I0), BindingFlags.Instance | BindingFlags.Public)); 593 Assert.Equal("o.I0()", e4.ToString()); 594 595 MethodCallExpression e5 = Expression.Call(Expression.Parameter(typeof(SomeMethods), "o"), typeof(SomeMethods).GetMethod(nameof(SomeMethods.I1), BindingFlags.Instance | BindingFlags.Public), Expression.Parameter(typeof(int), "x")); 596 Assert.Equal("o.I1(x)", e5.ToString()); 597 598 MethodCallExpression e6 = Expression.Call(Expression.Parameter(typeof(SomeMethods), "o"), typeof(SomeMethods).GetMethod(nameof(SomeMethods.I2), BindingFlags.Instance | BindingFlags.Public), Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")); 599 Assert.Equal("o.I2(x, y)", e6.ToString()); 600 601 MethodCallExpression e7 = Expression.Call(null, typeof(ExtensionMethods).GetMethod(nameof(ExtensionMethods.E0), BindingFlags.Static | BindingFlags.Public), Expression.Parameter(typeof(int), "x")); 602 Assert.Equal("x.E0()", e7.ToString()); 603 604 MethodCallExpression e8 = Expression.Call(null, typeof(ExtensionMethods).GetMethod(nameof(ExtensionMethods.E1), BindingFlags.Static | BindingFlags.Public), Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")); 605 Assert.Equal("x.E1(y)", e8.ToString()); 606 607 MethodCallExpression e9 = Expression.Call(null, typeof(ExtensionMethods).GetMethod(nameof(ExtensionMethods.E2), BindingFlags.Static | BindingFlags.Public), Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y"), Expression.Parameter(typeof(int), "z")); 608 Assert.Equal("x.E2(y, z)", e9.ToString()); 609 } 610 611 [Fact] GetArguments()612 public static void GetArguments() 613 { 614 VerifyGetArguments(Expression.Call(null, s_method0)); 615 VerifyGetArguments(Expression.Call(null, s_method1, Expression.Constant(0))); 616 VerifyGetArguments( 617 Expression.Call(null, s_method2, Enumerable.Range(0, 2).Select(i => Expression.Constant(i)))); 618 VerifyGetArguments( 619 Expression.Call(null, s_method3, Enumerable.Range(0, 3).Select(i => Expression.Constant(i)))); 620 VerifyGetArguments( 621 Expression.Call(null, s_method4, Enumerable.Range(0, 4).Select(i => Expression.Constant(i)))); 622 VerifyGetArguments( 623 Expression.Call(null, s_method5, Enumerable.Range(0, 5).Select(i => Expression.Constant(i)))); 624 VerifyGetArguments( 625 Expression.Call(null, s_method6, Enumerable.Range(0, 6).Select(i => Expression.Constant(i)))); 626 VerifyGetArguments( 627 Expression.Call(null, s_method7, Enumerable.Range(0, 7).Select(i => Expression.Constant(i)))); 628 var site = Expression.Default(typeof(NonGenericClass)); 629 VerifyGetArguments(Expression.Call(site, nameof(NonGenericClass.InstanceMethod0), null)); 630 VerifyGetArguments( 631 Expression.Call(site, nameof(NonGenericClass.InstanceMethod1), null, Expression.Constant(0))); 632 VerifyGetArguments( 633 Expression.Call( 634 site, nameof(NonGenericClass.InstanceMethod2), null, 635 Enumerable.Range(0, 2).Select(i => Expression.Constant(i)).ToArray())); 636 VerifyGetArguments( 637 Expression.Call( 638 site, nameof(NonGenericClass.InstanceMethod3), null, 639 Enumerable.Range(0, 3).Select(i => Expression.Constant(i)).ToArray())); 640 VerifyGetArguments( 641 Expression.Call( 642 site, nameof(NonGenericClass.InstanceMethod4), null, 643 Enumerable.Range(0, 4).Select(i => Expression.Constant(i)).ToArray())); 644 } 645 VerifyGetArguments(MethodCallExpression call)646 private static void VerifyGetArguments(MethodCallExpression call) 647 { 648 var args = call.Arguments; 649 Assert.Equal(args.Count, call.ArgumentCount); 650 AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => call.GetArgument(-1)); 651 AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => call.GetArgument(args.Count)); 652 for (int i = 0; i != args.Count; ++i) 653 { 654 Assert.Same(args[i], call.GetArgument(i)); 655 Assert.Equal(i, ((ConstantExpression)call.GetArgument(i)).Value); 656 } 657 } 658 659 public class GenericClass<T> 660 { NonGenericMethod()661 public static void NonGenericMethod() { } 662 } 663 664 public static class Unreadable<T> 665 { 666 public static T WriteOnly { set { } } 667 } 668 669 public class NonGenericClass 670 { GenericMethod()671 public static void GenericMethod<T>() { } InstanceMethod()672 public void InstanceMethod() { } StaticMethod()673 public static void StaticMethod() { } 674 Method0()675 public static void Method0() { } Method1(int i1)676 public static void Method1(int i1) { } Method2(int i1, int i2)677 public static void Method2(int i1, int i2) { } Method3(int i1, int i2, int i3)678 public static void Method3(int i1, int i2, int i3) { } Method4(int i1, int i2, int i3, int i4)679 public static void Method4(int i1, int i2, int i3, int i4) { } Method5(int i1, int i2, int i3, int i4, int i5)680 public static void Method5(int i1, int i2, int i3, int i4, int i5) { } Method6(int i1, int i2, int i3, int i4, int i5, int i6)681 public static void Method6(int i1, int i2, int i3, int i4, int i5, int i6) { } Method7(int i1, int i2, int i3, int i4, int i5, int i6, int i7)682 public static void Method7(int i1, int i2, int i3, int i4, int i5, int i6, int i7) { } 683 staticSameName(uint i1)684 public void staticSameName(uint i1) { } instanceSameName(int i1)685 public void instanceSameName(int i1) { } 686 StaticSameName(uint i1)687 public static void StaticSameName(uint i1) { } staticSameName(int i1)688 public static void staticSameName(int i1) { } 689 GenericInstanceMethod(T t1)690 public void GenericInstanceMethod<T>(T t1) { } GenericStaticMethod(T t1)691 public static void GenericStaticMethod<T>(T t1) { } 692 693 public void ConstrainedInstanceMethod<T>(T t1) where T : struct { } 694 public static void ConstrainedStaticMethod<T>(T t1) where T : struct { } 695 InstanceMethod0()696 public void InstanceMethod0() { } InstanceMethod1(int i1)697 public void InstanceMethod1(int i1) { } InstanceMethod2(int i1, int i2)698 public void InstanceMethod2(int i1, int i2) { } InstanceMethod3(int i1, int i2, int i3)699 public void InstanceMethod3(int i1, int i2, int i3) { } InstanceMethod4(int i1, int i2, int i3, int i4)700 public void InstanceMethod4(int i1, int i2, int i3, int i4) { } StaticMethod1(int i1)701 public static void StaticMethod1(int i1) { } 702 } 703 704 public interface Interface1 705 { InterfaceMethod()706 int InterfaceMethod(); 707 } 708 709 public interface Interface2 710 { InterfaceMethod()711 int InterfaceMethod(); 712 } 713 714 public interface CompoundInterface : Interface1 { } 715 716 public class ClassWithInterface1 : Interface1 717 { InterfaceMethod()718 public int InterfaceMethod() => 1; Method()719 public int Method() => 2; 720 } 721 722 public class OtherClassWithInterface1 : Interface1 723 { InterfaceMethod()724 public int InterfaceMethod() => 1; 725 } 726 727 public struct StructWithInterface1 : Interface1 728 { InterfaceMethodSystem.Linq.Expressions.Tests.CallTests.StructWithInterface1729 public int InterfaceMethod() => 1; MethodSystem.Linq.Expressions.Tests.CallTests.StructWithInterface1730 public int Method() => 2; 731 } 732 733 public struct OtherStructWithInterface1 : Interface1 734 { InterfaceMethodSystem.Linq.Expressions.Tests.CallTests.OtherStructWithInterface1735 public int InterfaceMethod() => 1; 736 } 737 738 public class ClassWithCompoundInterface : CompoundInterface 739 { InterfaceMethod()740 public int InterfaceMethod() => 1; 741 } 742 743 public struct StructWithCompoundInterface : CompoundInterface 744 { InterfaceMethodSystem.Linq.Expressions.Tests.CallTests.StructWithCompoundInterface745 public int InterfaceMethod() => 1; 746 } 747 } 748 749 class SomeMethods 750 { S0()751 public static void S0() { } S1(int x)752 public static void S1(int x) { } S2(int x, int y)753 public static void S2(int x, int y) { } 754 I0()755 public void I0() { } I1(int x)756 public void I1(int x) { } I2(int x, int y)757 public void I2(int x, int y) { } 758 } 759 760 static class ExtensionMethods 761 { E0(this int x)762 public static void E0(this int x) { } E1(this int x, int y)763 public static void E1(this int x, int y) { } E2(this int x, int y, int z)764 public static void E2(this int x, int y, int z) { } 765 } 766 } 767