Przeglądaj źródła

Merge pull request #77 from h15ter/master

Improve performance of overload resolution, fix various aspects of it
Sébastien Ros 11 lat temu
rodzic
commit
7d6bdab1db

+ 18 - 18
Jint.Tests/Runtime/Domain/A.cs

@@ -49,24 +49,24 @@ namespace Jint.Tests.Runtime.Domain
         public bool Call7(string str, Func<string, bool> predicate)
         {
             return predicate(str);
-        }
-
-        public string Call8(Func<string> predicate)
-        {
-            return predicate();
-        }
-
-        public void Call9(Action predicate)
-        {
-            predicate();
-        }
-        public void Call10(string str, Action<string> predicate)
-        {
-            predicate(str);
-        }
-        public void Call11(string str, string str2, Action<string, string> predicate)
-        {
-            predicate(str, str2);
+        }
+
+        public string Call8(Func<string> predicate)
+        {
+            return predicate();
+        }
+
+        public void Call9(Action predicate)
+        {
+            predicate();
+        }
+        public void Call10(string str, Action<string> predicate)
+        {
+            predicate(str);
+        }
+        public void Call11(string str, string str2, Action<string, string> predicate)
+        {
+            predicate(str, str2);
         }
 
     }

+ 41 - 9
Jint/Runtime/Descriptors/Specialized/IndexDescriptor.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Globalization;
+using System.Linq;
 using System.Reflection;
 using Jint.Native;
 
@@ -10,18 +11,39 @@ namespace Jint.Runtime.Descriptors.Specialized
         private readonly Engine _engine;
         private readonly object _key;
         private readonly object _item;
-        private readonly MethodInfo _getter;
-        private readonly MethodInfo _setter;
+        private readonly PropertyInfo _indexer;
 
         public IndexDescriptor(Engine engine, Type targetType, string key, object item)
         {
             _engine = engine;
             _item = item;
 
-            _getter = targetType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public);
-            _setter = targetType.GetMethod("set_Item", BindingFlags.Instance | BindingFlags.Public);
+            // get all instance indexers with exactly 1 argument
+            var indexers = targetType
+                .GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
+                .Where(x => x.GetIndexParameters().Length == 1);
 
-            _key = _engine.ClrTypeConverter.Convert(key, _getter.GetParameters()[0].ParameterType, CultureInfo.InvariantCulture);
+            // try to find first indexer having either public getter or setter with matching argument type
+            foreach (var indexer in indexers)
+            {
+                if (indexer.GetGetMethod() != null || indexer.GetSetMethod() != null)
+                {
+                    var paramType = indexer.GetIndexParameters()[0].ParameterType;
+
+                    if (_engine.ClrTypeConverter.TryConvert(key, paramType, CultureInfo.InvariantCulture, out _key))
+                    {
+                        _indexer = indexer;
+                        break;
+
+                    }
+                }
+            }
+
+            // throw if no indexer found
+            if (_indexer == null)
+            {
+                throw new InvalidOperationException("No matching indexer found.");
+            }
 
             Writable = true;
         }
@@ -30,22 +52,32 @@ namespace Jint.Runtime.Descriptors.Specialized
         public IndexDescriptor(Engine engine, string key, object item)
             : this(engine, item.GetType(), key, item)
         {
-            
         }
 
         public override JsValue? Value
         {
             get
             {
+                var getter = _indexer.GetGetMethod();
+                if (getter == null)
+                {
+                    throw new InvalidOperationException("Indexer has no public getter.");
+                }
+
                 object[] parameters = { _key };
-                return JsValue.FromObject(_engine, _getter.Invoke(_item, parameters));
+                return JsValue.FromObject(_engine, getter.Invoke(_item, parameters));
             }
 
             set
             {
-                var defaultValue = _item.GetType().IsValueType ? System.Activator.CreateInstance(_item.GetType()) : null;
+                var setter = _indexer.GetSetMethod();
+                if (setter == null)
+                {
+                    throw new InvalidOperationException("Indexer has no public setter.");
+                }
+
                 object[] parameters = { _key, value.HasValue ? value.Value.ToObject() : null };
-                _setter.Invoke(_item, parameters);
+                setter.Invoke(_item, parameters);
             }
         }
     }

+ 80 - 48
Jint/Runtime/Interop/DefaultTypeConverter.cs

@@ -3,30 +3,33 @@ using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq.Expressions;
 using Jint.Native;
+using System.Collections.Generic;
 
 namespace Jint.Runtime.Interop
 {
     public class DefaultTypeConverter : ITypeConverter
     {
         private readonly Engine _engine;
+        private static readonly Dictionary<string, bool> _knownConversions = new Dictionary<string, bool>();
+        private static readonly object _lockObject = new object();
 
         public DefaultTypeConverter(Engine engine)
         {
             _engine = engine;
         }
 
-        public object Convert(object value, Type type, IFormatProvider formatProvider)
-        {
-            if (value == null)
-            {
-                if (TypeIsNullable(type))
-                {
-                    return null;
-                }
-
-                throw new NotSupportedException(string.Format("Unable to convert null to '{0}'", type.FullName));
-            }
-
+        public virtual object Convert(object value, Type type, IFormatProvider formatProvider)
+        {
+            if (value == null)
+            {
+                if (TypeConverter.TypeIsNullable(type))
+                {
+                    return null;
+                }
+
+                throw new NotSupportedException(string.Format("Unable to convert null to '{0}'", type.FullName));
+            }
+
             // don't try to convert if value is derived from type
             if (type.IsInstanceOfType(value))
             {
@@ -35,7 +38,7 @@ namespace Jint.Runtime.Interop
 
             if (type.IsEnum)
             {
-                var integer = System.Convert.ChangeType(value, typeof (int), formatProvider);
+                var integer = System.Convert.ChangeType(value, typeof(int), formatProvider);
                 if (integer == null)
                 {
                     throw new ArgumentOutOfRangeException();
@@ -46,9 +49,9 @@ namespace Jint.Runtime.Interop
 
             var valueType = value.GetType();
             // is the javascript value an ICallable instance ?
-            if (valueType == typeof (Func<JsValue, JsValue[], JsValue>))
+            if (valueType == typeof(Func<JsValue, JsValue[], JsValue>))
             {
-                var function = (Func<JsValue, JsValue[], JsValue>) value;
+                var function = (Func<JsValue, JsValue[], JsValue>)value;
 
                 if (type.IsGenericType)
                 {
@@ -79,7 +82,7 @@ namespace Jint.Runtime.Interop
                     {
                         var genericArguments = type.GetGenericArguments();
                         var returnType = genericArguments.Last();
-                        
+
                         var @params = new ParameterExpression[genericArguments.Count() - 1];
                         for (var i = 0; i < @params.Count(); i++)
                         {
@@ -101,45 +104,74 @@ namespace Jint.Runtime.Interop
                 }
                 else
                 {
-                    if (type == typeof (Action))
+                    if (type == typeof(Action))
                     {
                         return (Action)(() => function(JsValue.Undefined, new JsValue[0]));
-                    }
-                    else if (type.IsSubclassOf(typeof(System.MulticastDelegate)))
-                    {
-                        var method = type.GetMethod("Invoke");
-                        var arguments = method.GetParameters();
-
-                        var @params = new ParameterExpression[arguments.Count()];
-                        for (var i = 0; i < @params.Count(); i++)
-                        {
-                            @params[i] = Expression.Parameter(typeof(object), arguments[i].Name);
-                        }
-                        var @vars = Expression.NewArrayInit(typeof(JsValue), @params.Select(p => Expression.Call(null, typeof(JsValue).GetMethod("FromObject"), Expression.Constant(_engine, typeof(Engine)), p)));
-
-                        var callExpression = Expression.Block(
-                                                Expression.Call(
-                                                    Expression.Call(Expression.Constant(function.Target),
-                                                        function.Method,
-                                                        Expression.Constant(JsValue.Undefined, typeof(JsValue)),
-                                                        @vars),
-                                                    typeof(JsValue).GetMethod("ToObject")),
-                                                Expression.Empty());
-
-                        var dynamicExpression = Expression.Invoke(Expression.Lambda(callExpression, new ReadOnlyCollection<ParameterExpression>(@params)), new ReadOnlyCollection<ParameterExpression>(@params));
-
-                        return Expression.Lambda(type, dynamicExpression, new ReadOnlyCollection<ParameterExpression>(@params));
+                    }
+                    else if (type.IsSubclassOf(typeof(System.MulticastDelegate)))
+                    {
+                        var method = type.GetMethod("Invoke");
+                        var arguments = method.GetParameters();
+
+                        var @params = new ParameterExpression[arguments.Count()];
+                        for (var i = 0; i < @params.Count(); i++)
+                        {
+                            @params[i] = Expression.Parameter(typeof(object), arguments[i].Name);
+                        }
+                        var @vars = Expression.NewArrayInit(typeof(JsValue), @params.Select(p => Expression.Call(null, typeof(JsValue).GetMethod("FromObject"), Expression.Constant(_engine, typeof(Engine)), p)));
+
+                        var callExpression = Expression.Block(
+                                                Expression.Call(
+                                                    Expression.Call(Expression.Constant(function.Target),
+                                                        function.Method,
+                                                        Expression.Constant(JsValue.Undefined, typeof(JsValue)),
+                                                        @vars),
+                                                    typeof(JsValue).GetMethod("ToObject")),
+                                                Expression.Empty());
+
+                        var dynamicExpression = Expression.Invoke(Expression.Lambda(callExpression, new ReadOnlyCollection<ParameterExpression>(@params)), new ReadOnlyCollection<ParameterExpression>(@params));
+
+                        return Expression.Lambda(type, dynamicExpression, new ReadOnlyCollection<ParameterExpression>(@params));
                     }
                 }
 
             }
 
             return System.Convert.ChangeType(value, type, formatProvider);
-        }
-
-        private static bool TypeIsNullable(Type type)
-        {
-            return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
-        }
+        }
+
+        public virtual bool TryConvert(object value, Type type, IFormatProvider formatProvider, out object converted)
+        {
+            bool canConvert;
+            var key = value == null ? String.Format("Null->{0}", type) : String.Format("{0}->{1}", value.GetType(), type);
+
+            lock (_lockObject)
+            {
+                if (!_knownConversions.TryGetValue(key, out canConvert))
+                {
+                    try
+                    {
+                        converted = Convert(value, type, formatProvider);
+                        _knownConversions.Add(key, true);
+                        return true;
+                    }
+                    catch
+                    {
+                        converted = null;
+                        _knownConversions.Add(key, false);
+                        return false;
+                    }
+                }
+            }
+
+            if (canConvert)
+            {
+                converted = Convert(value, type, formatProvider);
+                return true;
+            }
+
+            converted = null;
+            return false;
+        }
     }
 }

+ 1 - 0
Jint/Runtime/Interop/ITypeConverter.cs

@@ -5,5 +5,6 @@ namespace Jint.Runtime.Interop
     public interface ITypeConverter
     {
         object Convert(object value, Type type, IFormatProvider formatProvider);
+        bool TryConvert(object value, Type type, IFormatProvider formatProvider, out object converted);
     }
 }

+ 13 - 16
Jint/Runtime/Interop/MethodInfoFunctionInstance.cs

@@ -6,8 +6,6 @@ using Jint.Native.Function;
 
 namespace Jint.Runtime.Interop
 {
-    using System;
-
     public sealed class MethodInfoFunctionInstance : FunctionInstance
     {
         private readonly MethodInfo[] _methods;
@@ -27,12 +25,13 @@ namespace Jint.Runtime.Interop
         public JsValue Invoke(MethodInfo[] methodInfos, JsValue thisObject, JsValue[] arguments)
         {
             var methods = TypeConverter.FindBestMatch(Engine, methodInfos, arguments).ToList();
+            var converter = Engine.ClrTypeConverter;
 
             foreach (var method in methods)
             {
                 var parameters = new object[arguments.Length];
-                try
-                {
+                var argumentsMatch = true;
+
                     for (var i = 0; i < arguments.Length; i++)
                     {
                         var parameterType = method.GetParameters()[i].ParameterType;
@@ -43,10 +42,11 @@ namespace Jint.Runtime.Interop
                         }
                         else
                         {
-                            parameters[i] = Engine.ClrTypeConverter.Convert(
-                                arguments[i].ToObject(),
-                                parameterType,
-                                CultureInfo.InvariantCulture);
+                        if (!converter.TryConvert(arguments[i].ToObject(), parameterType, CultureInfo.InvariantCulture, out parameters[i]))
+                        {
+                            argumentsMatch = false;
+                            break;
+                        }
 
                             if (typeof(System.Linq.Expressions.LambdaExpression).IsAssignableFrom(parameters[i].GetType()))
                             {
@@ -55,16 +55,13 @@ namespace Jint.Runtime.Interop
                         }
                     }
 
-                    var result = JsValue.FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters.ToArray()));
-
-                    // todo: cache method info
-
-                    return result;
-                }
-                catch 
+                if (!argumentsMatch)
                 {
-                    // ignore method
+                    continue;
                 }
+
+                // todo: cache method info
+                return JsValue.FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters.ToArray()));
             }
 
             throw new JavaScriptException(Engine.TypeError, "No public methods with the specified arguments were found.");

+ 18 - 24
Jint/Runtime/TypeConverter.cs

@@ -350,8 +350,7 @@ namespace Jint.Runtime
         {
             methods = methods
                 .Where(m => m.GetParameters().Count() == arguments.Length)
-                .ToArray()
-                ;
+                .ToArray();
 
             if (methods.Length == 1 && !methods[0].GetParameters().Any())
             {
@@ -366,7 +365,18 @@ namespace Jint.Runtime
                 var parameters = method.GetParameters();
                 for (var i = 0; i < arguments.Length; i++)
                 {
-                    if (objectArguments[i].GetType() != parameters[i].ParameterType)
+                    var arg = objectArguments[i];
+                    var paramType = parameters[i].ParameterType;
+                    
+                    if (arg == null)
+                    {
+                        if (!TypeIsNullable(paramType))
+                        {
+                            perfectMatch = false;
+                            break;
+                        }
+                    }
+                    else if (arg.GetType() != paramType)
                     {
                         perfectMatch = false;
                         break;
@@ -380,31 +390,15 @@ namespace Jint.Runtime
                 }
             }
 
-            var candidates = new List<MethodBase>();
             foreach (var method in methods)
             {
-                var parameters = new object[arguments.Length];
-                try
-                {
-                    for (var i = 0; i < arguments.Length; i++)
-                    {
-                        parameters[i] = engine.ClrTypeConverter.Convert(
-                            objectArguments[i],
-                            method.GetParameters()[i].ParameterType,
-                            CultureInfo.InvariantCulture);
-                    }
-                }
-                catch
-                {
-                    // ignore method
-                }
-
-                candidates.Add(method);
+                yield return method;
             }
-
-            foreach (var candidate in candidates)
-                yield return candidate;
         }
 
+        public static bool TypeIsNullable(Type type)
+        {
+            return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
+        }
     }
 }