|
@@ -1,10 +1,8 @@
|
|
|
using System.Globalization;
|
|
|
using System.Numerics;
|
|
|
-using System.Reflection;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
using Esprima;
|
|
|
using Esprima.Ast;
|
|
|
-using Jint.Extensions;
|
|
|
using Jint.Native;
|
|
|
using Jint.Native.Number;
|
|
|
using Jint.Native.Object;
|
|
@@ -14,56 +12,6 @@ using Jint.Runtime.Interop;
|
|
|
|
|
|
namespace Jint.Runtime
|
|
|
{
|
|
|
- [Flags]
|
|
|
- public enum Types
|
|
|
- {
|
|
|
- Empty = 0,
|
|
|
- Undefined = 1,
|
|
|
- Null = 2,
|
|
|
- Boolean = 4,
|
|
|
- String = 8,
|
|
|
- Number = 16,
|
|
|
- Symbol = 64,
|
|
|
- BigInt = 128,
|
|
|
- Object = 256
|
|
|
- }
|
|
|
-
|
|
|
- [Flags]
|
|
|
- internal enum InternalTypes
|
|
|
- {
|
|
|
- // should not be used, used for empty match
|
|
|
- Empty = 0,
|
|
|
-
|
|
|
- Undefined = 1,
|
|
|
- Null = 2,
|
|
|
-
|
|
|
- // primitive types range start
|
|
|
- Boolean = 4,
|
|
|
- String = 8,
|
|
|
- Number = 16,
|
|
|
- Integer = 32,
|
|
|
- Symbol = 64,
|
|
|
- BigInt = 128,
|
|
|
-
|
|
|
- // primitive types range end
|
|
|
- Object = 256,
|
|
|
-
|
|
|
- PrivateName = 512,
|
|
|
-
|
|
|
- // internal usage
|
|
|
- ObjectEnvironmentRecord = 1024,
|
|
|
- RequiresCloning = 2048,
|
|
|
- Module = 4096,
|
|
|
-
|
|
|
- // the object doesn't override important GetOwnProperty etc which change behavior
|
|
|
- PlainObject = 8192,
|
|
|
- // our native array
|
|
|
- Array = 16384,
|
|
|
-
|
|
|
- Primitive = Boolean | String | Number | Integer | BigInt | Symbol,
|
|
|
- InternalFlags = ObjectEnvironmentRecord | RequiresCloning | PlainObject | Array | Module
|
|
|
- }
|
|
|
-
|
|
|
public static class TypeConverter
|
|
|
{
|
|
|
// how many decimals to check when determining if double is actually an int
|
|
@@ -1066,283 +1014,5 @@ namespace Jint.Runtime
|
|
|
ExceptionHelper.ThrowTypeError(engine.Realm, "Cannot call method on " + o);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- internal readonly record struct MethodMatch(MethodDescriptor Method, JsValue[] Arguments, int Score = 0) : IComparable<MethodMatch>
|
|
|
- {
|
|
|
- public int CompareTo(MethodMatch other) => Score.CompareTo(other.Score);
|
|
|
- }
|
|
|
-
|
|
|
- internal static IEnumerable<MethodMatch> FindBestMatch(
|
|
|
- Engine engine,
|
|
|
- MethodDescriptor[] methods,
|
|
|
- Func<MethodDescriptor, JsValue[]> argumentProvider)
|
|
|
- {
|
|
|
- List<MethodMatch>? matchingByParameterCount = null;
|
|
|
- foreach (var method in methods)
|
|
|
- {
|
|
|
- var parameterInfos = method.Parameters;
|
|
|
- var arguments = argumentProvider(method);
|
|
|
- if (arguments.Length <= parameterInfos.Length
|
|
|
- && arguments.Length >= parameterInfos.Length - method.ParameterDefaultValuesCount)
|
|
|
- {
|
|
|
- var score = CalculateMethodScore(engine, method, arguments);
|
|
|
- if (score == 0)
|
|
|
- {
|
|
|
- // perfect match
|
|
|
- yield return new MethodMatch(method, arguments);
|
|
|
- yield break;
|
|
|
- }
|
|
|
-
|
|
|
- if (score < 0)
|
|
|
- {
|
|
|
- // discard
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- matchingByParameterCount ??= new List<MethodMatch>();
|
|
|
- matchingByParameterCount.Add(new MethodMatch(method, arguments, score));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (matchingByParameterCount == null)
|
|
|
- {
|
|
|
- yield break;
|
|
|
- }
|
|
|
-
|
|
|
- if (matchingByParameterCount.Count > 1)
|
|
|
- {
|
|
|
- matchingByParameterCount.Sort();
|
|
|
- }
|
|
|
-
|
|
|
- foreach (var match in matchingByParameterCount)
|
|
|
- {
|
|
|
- yield return match;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Method's match score tells how far away it's from ideal candidate. 0 = ideal, bigger the the number,
|
|
|
- /// the farther away the candidate is from ideal match. Negative signals impossible match.
|
|
|
- /// </summary>
|
|
|
- private static int CalculateMethodScore(Engine engine, MethodDescriptor method, JsValue[] arguments)
|
|
|
- {
|
|
|
- if (method.Parameters.Length == 0 && arguments.Length == 0)
|
|
|
- {
|
|
|
- // perfect
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- var score = 0;
|
|
|
- for (var i = 0; i < arguments.Length; i++)
|
|
|
- {
|
|
|
- var jsValue = arguments[i];
|
|
|
-
|
|
|
- var parameterScore = CalculateMethodParameterScore(engine, method.Parameters[i], jsValue);
|
|
|
- if (parameterScore < 0)
|
|
|
- {
|
|
|
- return parameterScore;
|
|
|
- }
|
|
|
-
|
|
|
- score += parameterScore;
|
|
|
- }
|
|
|
-
|
|
|
- return score;
|
|
|
- }
|
|
|
-
|
|
|
- internal readonly record struct AssignableResult(int Score, Type MatchingGivenType)
|
|
|
- {
|
|
|
- public bool IsAssignable => Score >= 0;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// resources:
|
|
|
- /// https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/how-to-examine-and-instantiate-generic-types-with-reflection
|
|
|
- /// https://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059
|
|
|
- /// https://docs.microsoft.com/en-us/dotnet/api/system.type.isconstructedgenerictype?view=net-6.0
|
|
|
- /// This can be improved upon - specifically as mentioned in the above MS document:
|
|
|
- /// GetGenericParameterConstraints()
|
|
|
- /// and array handling - i.e.
|
|
|
- /// GetElementType()
|
|
|
- /// </summary>
|
|
|
- internal static AssignableResult IsAssignableToGenericType(Type givenType, Type genericType)
|
|
|
- {
|
|
|
- if (givenType is null)
|
|
|
- {
|
|
|
- return new AssignableResult(-1, typeof(void));
|
|
|
- }
|
|
|
-
|
|
|
- if (!genericType.IsConstructedGenericType)
|
|
|
- {
|
|
|
- // as mentioned here:
|
|
|
- // https://docs.microsoft.com/en-us/dotnet/api/system.type.isconstructedgenerictype?view=net-6.0
|
|
|
- // this effectively means this generic type is open (i.e. not closed) - so any type is "possible" - without looking at the code in the method we don't know
|
|
|
- // whether any operations are being applied that "don't work"
|
|
|
- return new AssignableResult(2, givenType);
|
|
|
- }
|
|
|
-
|
|
|
- var interfaceTypes = givenType.GetInterfaces();
|
|
|
- foreach (var it in interfaceTypes)
|
|
|
- {
|
|
|
- if (it.IsGenericType)
|
|
|
- {
|
|
|
- var givenTypeGenericDef = it.GetGenericTypeDefinition();
|
|
|
- if (givenTypeGenericDef == genericType)
|
|
|
- {
|
|
|
- return new AssignableResult(0, it);
|
|
|
- }
|
|
|
- else if (genericType.IsGenericType && (givenTypeGenericDef == genericType.GetGenericTypeDefinition()))
|
|
|
- {
|
|
|
- return new AssignableResult(0, it);
|
|
|
- }
|
|
|
- // TPC: we could also add a loop to recurse and iterate thru the iterfaces of generic type - because of covariance/contravariance
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
|
|
|
- {
|
|
|
- return new AssignableResult(0, givenType);
|
|
|
- }
|
|
|
-
|
|
|
- var baseType = givenType.BaseType;
|
|
|
- if (baseType == null)
|
|
|
- {
|
|
|
- return new AssignableResult(-1, givenType);
|
|
|
- }
|
|
|
-
|
|
|
- return IsAssignableToGenericType(baseType, genericType);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Determines how well parameter type matches target method's type.
|
|
|
- /// </summary>
|
|
|
- private static int CalculateMethodParameterScore(Engine engine, ParameterInfo parameter, JsValue parameterValue)
|
|
|
- {
|
|
|
- var paramType = parameter.ParameterType;
|
|
|
- var objectValue = parameterValue.ToObject();
|
|
|
- var objectValueType = objectValue?.GetType();
|
|
|
-
|
|
|
- if (objectValueType == paramType)
|
|
|
- {
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (objectValue is null)
|
|
|
- {
|
|
|
- if (!parameter.IsOptional && !TypeIsNullable(paramType))
|
|
|
- {
|
|
|
- // this is bad
|
|
|
- return -1;
|
|
|
- }
|
|
|
-
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (paramType == typeof(JsValue))
|
|
|
- {
|
|
|
- // JsValue is convertible to. But it is still not a perfect match
|
|
|
- return 1;
|
|
|
- }
|
|
|
-
|
|
|
- if (paramType == typeof(object))
|
|
|
- {
|
|
|
- // a catch-all, prefer others over it
|
|
|
- return 5;
|
|
|
- }
|
|
|
-
|
|
|
- if (paramType == typeof(int) && parameterValue.IsInteger())
|
|
|
- {
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (paramType == typeof(float) && objectValueType == typeof(double))
|
|
|
- {
|
|
|
- return parameterValue.IsInteger() ? 1 : 2;
|
|
|
- }
|
|
|
-
|
|
|
- if (paramType.IsEnum &&
|
|
|
- parameterValue is JsNumber jsNumber
|
|
|
- && jsNumber.IsInteger()
|
|
|
- && paramType.GetEnumUnderlyingType() == typeof(int)
|
|
|
- && Enum.IsDefined(paramType, jsNumber.AsInteger()))
|
|
|
- {
|
|
|
- // we can do conversion from int value to enum
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (paramType.IsAssignableFrom(objectValueType))
|
|
|
- {
|
|
|
- // is-a-relation
|
|
|
- return 1;
|
|
|
- }
|
|
|
-
|
|
|
- if (parameterValue.IsArray() && paramType.IsArray)
|
|
|
- {
|
|
|
- // we have potential, TODO if we'd know JS array's internal type we could have exact match
|
|
|
- return 2;
|
|
|
- }
|
|
|
-
|
|
|
- // not sure the best point to start generic type tests
|
|
|
- if (paramType.IsGenericParameter)
|
|
|
- {
|
|
|
- var genericTypeAssignmentScore = IsAssignableToGenericType(objectValueType!, paramType);
|
|
|
- if (genericTypeAssignmentScore.Score != -1)
|
|
|
- {
|
|
|
- return genericTypeAssignmentScore.Score;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (CanChangeType(objectValue, paramType))
|
|
|
- {
|
|
|
- // forcing conversion isn't ideal, but works, especially for int -> double for example
|
|
|
- return 3;
|
|
|
- }
|
|
|
-
|
|
|
- foreach (var m in objectValueType!.GetOperatorOverloadMethods())
|
|
|
- {
|
|
|
- if (paramType.IsAssignableFrom(m.ReturnType) && m.Name is "op_Implicit" or "op_Explicit")
|
|
|
- {
|
|
|
- // implicit/explicit operator conversion is OK, but not ideal
|
|
|
- return 3;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (ReflectionExtensions.TryConvertViaTypeCoercion(paramType, engine.Options.Interop.ValueCoercion, parameterValue, out _))
|
|
|
- {
|
|
|
- // gray JS zone where we start to do odd things
|
|
|
- return 10;
|
|
|
- }
|
|
|
-
|
|
|
- // will rarely succeed
|
|
|
- return 100;
|
|
|
- }
|
|
|
-
|
|
|
- private static bool CanChangeType(object value, Type targetType)
|
|
|
- {
|
|
|
- if (value is null && !targetType.IsValueType)
|
|
|
- {
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- if (value is not IConvertible)
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- try
|
|
|
- {
|
|
|
- Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);
|
|
|
- return true;
|
|
|
- }
|
|
|
- catch
|
|
|
- {
|
|
|
- // nope
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- internal static bool TypeIsNullable(Type type)
|
|
|
- {
|
|
|
- return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
|
|
|
- }
|
|
|
}
|
|
|
}
|