MethodDescriptor.cs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. using Jint.Native;
  2. using System.Reflection;
  3. using System.Runtime.CompilerServices;
  4. using Jint.Extensions;
  5. #pragma warning disable IL2072
  6. namespace Jint.Runtime.Interop;
  7. internal sealed class MethodDescriptor
  8. {
  9. internal MethodDescriptor(MethodBase method)
  10. {
  11. Method = method;
  12. Parameters = method.GetParameters();
  13. IsExtensionMethod = method.IsDefined(typeof(ExtensionAttribute), true);
  14. foreach (var parameter in Parameters)
  15. {
  16. if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute)))
  17. {
  18. HasParams = true;
  19. break;
  20. }
  21. if (parameter.HasDefaultValue)
  22. {
  23. ParameterDefaultValuesCount++;
  24. }
  25. }
  26. }
  27. public MethodBase Method { get; }
  28. public ParameterInfo[] Parameters { get; }
  29. public bool HasParams { get; }
  30. public int ParameterDefaultValuesCount { get; }
  31. public bool IsExtensionMethod { get; }
  32. public static MethodDescriptor[] Build<T>(List<T> source) where T : MethodBase
  33. {
  34. var descriptors = new MethodDescriptor[source.Count];
  35. for (var i = 0; i < source.Count; i++)
  36. {
  37. descriptors[i] = new MethodDescriptor(source[i]);
  38. }
  39. return Prioritize(descriptors);
  40. }
  41. public static MethodDescriptor[] Build<T>(T[] source) where T : MethodBase
  42. {
  43. var descriptors = new MethodDescriptor[source.Length];
  44. for (var i = 0; i < source.Length; i++)
  45. {
  46. descriptors[i] = new MethodDescriptor(source[i]);
  47. }
  48. return Prioritize(descriptors);
  49. }
  50. private static MethodDescriptor[] Prioritize(MethodDescriptor[] descriptors)
  51. {
  52. static int CreateComparison(MethodDescriptor d1, MethodDescriptor d2)
  53. {
  54. // if its a generic method, put it on the end
  55. if (d1.Method.IsGenericMethod && !d2.Method.IsGenericMethod)
  56. {
  57. return 1;
  58. }
  59. if (d2.Method.IsGenericMethod && !d1.Method.IsGenericMethod)
  60. {
  61. return -1;
  62. }
  63. // put params versions to end, they can be tricky to match and can cause trouble / extra overhead
  64. if (d1.HasParams && !d2.HasParams)
  65. {
  66. return 1;
  67. }
  68. if (d2.HasParams && !d1.HasParams)
  69. {
  70. return -1;
  71. }
  72. // then favor less parameters
  73. if (d1.Parameters.Length > d2.Parameters.Length)
  74. {
  75. return 1;
  76. }
  77. if (d2.Parameters.Length > d1.Parameters.Length)
  78. {
  79. return -1;
  80. }
  81. return 0;
  82. }
  83. Array.Sort(descriptors, CreateComparison);
  84. return descriptors;
  85. }
  86. public JsValue Call(Engine engine, object? instance, JsCallArguments arguments)
  87. {
  88. var parameters = new object?[arguments.Length];
  89. var methodParameters = Parameters;
  90. var valueCoercionType = engine.Options.Interop.ValueCoercion;
  91. try
  92. {
  93. for (var i = 0; i < arguments.Length; i++)
  94. {
  95. var methodParameter = methodParameters[i];
  96. var parameterType = methodParameter.ParameterType;
  97. var value = arguments[i];
  98. object? converted;
  99. if (typeof(JsValue).IsAssignableFrom(parameterType))
  100. {
  101. converted = value;
  102. }
  103. else if (value.IsUndefined() && methodParameter.IsOptional)
  104. {
  105. // undefined is considered missing, null is considered explicit value
  106. converted = methodParameter.DefaultValue;
  107. }
  108. else if (!ReflectionExtensions.TryConvertViaTypeCoercion(parameterType, valueCoercionType, value, out converted))
  109. {
  110. converted = engine.TypeConverter.Convert(
  111. value.ToObject(),
  112. parameterType,
  113. System.Globalization.CultureInfo.InvariantCulture);
  114. }
  115. parameters[i] = converted;
  116. }
  117. var retVal = Method switch
  118. {
  119. MethodInfo m => m.Invoke(instance, parameters),
  120. ConstructorInfo c => c.Invoke(parameters),
  121. _ => throw new NotSupportedException("Method is unknown type"),
  122. };
  123. return JsValue.FromObject(engine, retVal);
  124. }
  125. catch (TargetInvocationException exception)
  126. {
  127. Throw.MeaningfulException(engine, exception);
  128. return null;
  129. }
  130. }
  131. }