Ver código fonte

Optimize interop, use NET5 to run tests (#803)

Marko Lahma 4 anos atrás
pai
commit
cccd41e73a
40 arquivos alterados com 984 adições e 535 exclusões
  1. 25 0
      Jint.Benchmark/InteropBenchmark.cs
  2. 1 1
      Jint.Benchmark/Jint.Benchmark.csproj
  3. 1 1
      Jint.Repl/Jint.Repl.csproj
  4. 2 2
      Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj
  5. 2 2
      Jint.Tests.Ecma/Jint.Tests.Ecma.csproj
  6. 2 2
      Jint.Tests.Test262/Jint.Tests.Test262.csproj
  7. 3 2
      Jint.Tests/Jint.Tests.csproj
  8. 9 7
      Jint.Tests/Runtime/EngineTests.cs
  9. 17 0
      Jint.Tests/Runtime/FunctionTests.cs
  10. 31 0
      Jint.Tests/Runtime/InteropTests.cs
  11. 4 5
      Jint/Engine.cs
  12. 0 1
      Jint/Native/Array/ArrayConstructor.cs
  13. 15 5
      Jint/Native/Function/BindFunctionInstance.cs
  14. 0 1
      Jint/Native/Map/MapConstructor.cs
  15. 0 1
      Jint/Native/Map/MapPrototype.cs
  16. 0 1
      Jint/Native/Object/ObjectInstance.cs
  17. 0 1
      Jint/Native/RegExp/RegExpConstructor.cs
  18. 0 1
      Jint/Native/RegExp/RegExpPrototype.cs
  19. 0 1
      Jint/Native/Set/SetConstructor.cs
  20. 0 1
      Jint/Native/Set/SetPrototype.cs
  21. 0 1
      Jint/Native/Symbol/SymbolPrototype.cs
  22. 2 2
      Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs
  23. 0 1
      Jint/Runtime/Descriptors/PropertyDescriptor.cs
  24. 0 47
      Jint/Runtime/Descriptors/Specialized/FieldInfoDescriptor.cs
  25. 0 165
      Jint/Runtime/Descriptors/Specialized/IndexDescriptor.cs
  26. 0 68
      Jint/Runtime/Descriptors/Specialized/PropertyInfoDescriptor.cs
  27. 46 0
      Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs
  28. 89 0
      Jint/Runtime/Interop/MethodDescriptor.cs
  29. 20 17
      Jint/Runtime/Interop/MethodInfoFunctionInstance.cs
  30. 175 122
      Jint/Runtime/Interop/ObjectWrapper.cs
  31. 37 0
      Jint/Runtime/Interop/Reflection/ConstantValueAccessor.cs
  32. 27 0
      Jint/Runtime/Interop/Reflection/FieldAccessor.cs
  33. 134 0
      Jint/Runtime/Interop/Reflection/IndexerAccessor.cs
  34. 30 0
      Jint/Runtime/Interop/Reflection/MethodAccessor.cs
  35. 30 0
      Jint/Runtime/Interop/Reflection/PropertyAccessor.cs
  36. 123 0
      Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs
  37. 52 0
      Jint/Runtime/Interop/TypeDescriptor.cs
  38. 61 25
      Jint/Runtime/Interop/TypeReference.cs
  39. 0 1
      Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs
  40. 46 51
      Jint/Runtime/TypeConverter.cs

+ 25 - 0
Jint.Benchmark/InteropBenchmark.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Reflection;
 using BenchmarkDotNet.Attributes;
 using Jint.Native;
@@ -15,6 +16,7 @@ namespace Jint.Benchmark
         public class Person
         {
             public string Name { get; set; }
+            public int Age { get; set; }
         }
 
         private Engine _engine;
@@ -249,6 +251,29 @@ namespace Jint.Benchmark
                 _engine.Execute("'[email protected]'.split('@');");
             }
         }
+        
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void ResolvingConsoleWriteLine()
+        {
+            var originalOut = Console.Out;
+            Console.SetOut(TextWriter.Null);
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.Execute("System.Console.WriteLine('value to write');");
+            }
+            Console.SetOut(originalOut);
+        }
+        
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void Setter()
+        {
+            var p = new Person();
+            _engine.SetValue("p", p);
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.Execute("p.Age = 42;");
+            }
+        }
 
         [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
         public void LoopWithNativeEnumerator()

+ 1 - 1
Jint.Benchmark/Jint.Benchmark.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <AssemblyName>Jint.Benchmark</AssemblyName>
     <OutputType>Exe</OutputType>
     <GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>

+ 1 - 1
Jint.Repl/Jint.Repl.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <OutputType>Exe</OutputType>
     <IsPackable>false</IsPackable>
   </PropertyGroup>

+ 2 - 2
Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>
+    <TargetFrameworks>net461;net5.0</TargetFrameworks>
   </PropertyGroup>
   <ItemGroup>
     <EmbeddedResource Include="Scripts\*.*" />
@@ -9,7 +9,7 @@
     <ProjectReference Include="..\Jint\Jint.csproj" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.analyzers" Version="0.10.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />

+ 2 - 2
Jint.Tests.Ecma/Jint.Tests.Ecma.csproj

@@ -1,12 +1,12 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
   </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\Jint\Jint.csproj" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.analyzers" Version="0.10.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />

+ 2 - 2
Jint.Tests.Test262/Jint.Tests.Test262.csproj

@@ -1,12 +1,12 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>
+    <TargetFrameworks>net461;net5.0</TargetFrameworks>
   </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\Jint\Jint.csproj" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.analyzers" Version="0.10.0" />

+ 3 - 2
Jint.Tests/Jint.Tests.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>
+    <TargetFrameworks>net461;net5.0</TargetFrameworks>
     <AssemblyOriginatorKeyFile>..\Jint\Jint.snk</AssemblyOriginatorKeyFile>
     <SignAssembly>true</SignAssembly>
   </PropertyGroup>
@@ -14,7 +14,8 @@
     <Reference Include="Microsoft.CSharp" Condition=" '$(TargetFramework)' == 'net461' " />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
+    <PackageReference Include="MongoDB.Bson.signed" Version="2.11.2" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.analyzers" Version="0.10.0" />

+ 9 - 7
Jint.Tests/Runtime/EngineTests.cs

@@ -2019,15 +2019,17 @@ var prep = function (fn) { fn(); };
             var PDT = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
             var FR = new CultureInfo("fr-FR");
 
-            new Engine(options => options.LocalTimeZone(PDT).Culture(FR))
+            var engine = new Engine(options => options.LocalTimeZone(PDT).Culture(FR))
                 .SetValue("log", new Action<object>(Console.WriteLine))
                 .SetValue("assert", new Action<bool>(Assert.True))
-                .SetValue("equal", new Action<object, object>(Assert.Equal))
-                .Execute(@"
-                    var d = new Number(-1.23);
-                    equal('-1.23', d.toString());
-                    equal('-1,23', d.toLocaleString());
-            ");
+                .SetValue("equal", new Action<object, object>(Assert.Equal));
+
+            engine.Execute("var d = new Number(-1.23);");
+            engine.Execute("equal('-1.23', d.toString());");
+            
+            // NET 5 globalization APIs use ICU libraries on newer Windows 10 giving different result
+            // build server is older Windows...
+            engine.Execute("assert('-1,230' === d.toLocaleString() || '-1,23' === d.toLocaleString());");
         }
 
         [Fact]

+ 17 - 0
Jint.Tests/Runtime/FunctionTests.cs

@@ -0,0 +1,17 @@
+using Xunit;
+
+namespace Jint.Tests.Runtime
+{
+    public class FunctionTests
+    {
+        [Fact]
+        public void BindCombinesBoundArgumentsToCallArgumentsCorrectly()
+        {
+            var e = new Engine();
+            e.Execute("var testFunc = function (a, b, c) { return a + ', ' + b + ', ' + c + ', ' + JSON.stringify(arguments); }");
+            
+            Assert.Equal("a, 1, a, {\"0\":\"a\",\"1\":1,\"2\":\"a\"}", e.Execute("testFunc('a', 1, 'a');").GetCompletionValue().AsString());
+            Assert.Equal("a, 1, a, {\"0\":\"a\",\"1\":1,\"2\":\"a\"}", e.Execute("testFunc.bind('anything')('a', 1, 'a');").GetCompletionValue().AsString());
+        }
+    }
+}

+ 31 - 0
Jint.Tests/Runtime/InteropTests.cs

@@ -12,6 +12,7 @@ using Jint.Runtime;
 using Jint.Runtime.Interop;
 using Jint.Tests.Runtime.Converters;
 using Jint.Tests.Runtime.Domain;
+using MongoDB.Bson;
 using Newtonsoft.Json.Linq;
 using Shapes;
 using Xunit;
@@ -2287,5 +2288,35 @@ namespace Jint.Tests.Runtime
             Assert.True(_engine.Execute("return o[0] == 'item1'").GetCompletionValue().AsBoolean());
             Assert.True(_engine.Execute("return o[1] == 'item2'").GetCompletionValue().AsBoolean());
         }
+        
+        [Fact]
+        public void DictionaryLikeShouldCheckIndexerAndFallBackToProperty()
+        {
+            const string json = @"{ ""Type"": ""Cat"" }";
+            var jObjectWithTypeProperty = JObject.Parse(json);
+        
+            _engine.SetValue("o", jObjectWithTypeProperty);
+        
+            var typeResult = _engine.Execute("o.Type").GetCompletionValue();
+            
+            // JToken requires conversion
+            Assert.Equal("Cat", TypeConverter.ToString(typeResult));
+
+            // weak equality does conversions from native types
+            Assert.True(_engine.Execute("o.Type == 'Cat'").GetCompletionValue().AsBoolean());
+        }        
+
+        [Fact]
+        public void IndexingBsonProperties()
+        {
+            const string jsonAnimals = @" { ""Animals"": [ { ""Id"": 1, ""Type"": ""Cat"" } ] }";
+            var bsonAnimals = BsonDocument.Parse(jsonAnimals);
+            
+            _engine.SetValue("animals", bsonAnimals["Animals"]);
+
+            // weak equality does conversions from native types
+            Assert.True(_engine.Execute("animals[0].Type == 'Cat'").GetCompletionValue().AsBoolean());
+            Assert.True(_engine.Execute("animals[0].Id == 1").GetCompletionValue().AsBoolean());
+        }
     }
 }

+ 4 - 5
Jint/Engine.cs

@@ -28,9 +28,9 @@ using Jint.Runtime;
 using Jint.Runtime.CallStack;
 using Jint.Runtime.Debugger;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Environments;
 using Jint.Runtime.Interop;
+using Jint.Runtime.Interop.Reflection;
 using Jint.Runtime.Interpreter;
 using Jint.Runtime.References;
 
@@ -81,9 +81,9 @@ namespace Jint
         public ITypeConverter ClrTypeConverter { get; internal set; }
 
         // cache of types used when resolving CLR type names
-        internal readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>();
+        internal readonly Dictionary<string, Type> TypeCache = new();
 
-        internal static Dictionary<Type, Func<Engine, object, JsValue>> TypeMappers = new Dictionary<Type, Func<Engine, object, JsValue>>
+        internal static Dictionary<Type, Func<Engine, object, JsValue>> TypeMappers = new()
         {
             { typeof(bool), (engine, v) => (bool) v ? JsBoolean.True : JsBoolean.False },
             { typeof(byte), (engine, v) => JsNumber.Create((byte)v) },
@@ -108,8 +108,7 @@ namespace Jint
         internal readonly PropertyDescriptor _callerCalleeArgumentsThrowerConfigurable;
         internal readonly PropertyDescriptor _callerCalleeArgumentsThrowerNonConfigurable;
 
-        internal readonly Dictionary<ClrPropertyDescriptorFactoriesKey, Func<Engine, object, PropertyDescriptor>> ClrPropertyDescriptorFactories =
-            new Dictionary<ClrPropertyDescriptorFactoriesKey, Func<Engine, object, PropertyDescriptor>>();
+        internal static Dictionary<ClrPropertyDescriptorFactoriesKey, ReflectionAccessor> ReflectionAccessors = new();
 
         internal readonly JintCallStack CallStack = new JintCallStack();
 

+ 0 - 1
Jint/Native/Array/ArrayConstructor.cs

@@ -8,7 +8,6 @@ using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Array

+ 15 - 5
Jint/Native/Function/BindFunctionInstance.cs

@@ -1,5 +1,4 @@
-using System.Linq;
-using Jint.Native.Object;
+using Jint.Native.Object;
 using Jint.Runtime;
 
 namespace Jint.Native.Function
@@ -24,7 +23,11 @@ namespace Jint.Native.Function
                 return ExceptionHelper.ThrowTypeError<ObjectInstance>(Engine);
             }
 
-            return f.Call(BoundThis, CreateArguments(arguments));
+            var args = CreateArguments(arguments);
+            var value = f.Call(BoundThis, args);
+            _engine._jsValueArrayPool.ReturnArray(args);
+
+            return value;
         }
 
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
@@ -34,7 +37,11 @@ namespace Jint.Native.Function
                 return ExceptionHelper.ThrowTypeError<ObjectInstance>(Engine);
             }
 
-            return target.Construct(CreateArguments(arguments), newTarget);
+            var args = CreateArguments(arguments);
+            var value = target.Construct(args, newTarget);
+            _engine._jsValueArrayPool.ReturnArray(args);
+
+            return value;
         }
 
         public override bool HasInstance(JsValue v)
@@ -49,7 +56,10 @@ namespace Jint.Native.Function
 
         private JsValue[] CreateArguments(JsValue[] arguments)
         {
-            return Enumerable.Union(BoundArgs, arguments).ToArray();
+            var combined = _engine._jsValueArrayPool.RentArray(BoundArgs.Length + arguments.Length);
+            System.Array.Copy(BoundArgs, combined, BoundArgs.Length);
+            System.Array.Copy(arguments, 0, combined, BoundArgs.Length, arguments.Length);
+            return combined;
         }
 
         internal override bool IsConstructor => TargetFunction is IConstructor;

+ 0 - 1
Jint/Native/Map/MapConstructor.cs

@@ -5,7 +5,6 @@ using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Map

+ 0 - 1
Jint/Native/Map/MapPrototype.cs

@@ -3,7 +3,6 @@ using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Map

+ 0 - 1
Jint/Native/Object/ObjectInstance.cs

@@ -14,7 +14,6 @@ using Jint.Native.String;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interpreter.Expressions;
 

+ 0 - 1
Jint/Native/RegExp/RegExpConstructor.cs

@@ -7,7 +7,6 @@ using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.RegExp

+ 0 - 1
Jint/Native/RegExp/RegExpPrototype.cs

@@ -11,7 +11,6 @@ using Jint.Native.Symbol;
 using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.RegExp

+ 0 - 1
Jint/Native/Set/SetConstructor.cs

@@ -4,7 +4,6 @@ using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Set

+ 0 - 1
Jint/Native/Set/SetPrototype.cs

@@ -3,7 +3,6 @@ using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Set

+ 0 - 1
Jint/Native/Symbol/SymbolPrototype.cs

@@ -2,7 +2,6 @@
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Symbol

+ 2 - 2
Jint/Runtime/Descriptors/Specialized/GetSetPropertyDescriptor.cs → Jint/Runtime/Descriptors/GetSetPropertyDescriptor.cs

@@ -1,9 +1,9 @@
 using Jint.Native;
 using Jint.Native.Function;
 
-namespace Jint.Runtime.Descriptors.Specialized
+namespace Jint.Runtime.Descriptors
 {
-    internal sealed class GetSetPropertyDescriptor : PropertyDescriptor
+    public sealed class GetSetPropertyDescriptor : PropertyDescriptor
     {
         private JsValue _get;
         private JsValue _set;

+ 0 - 1
Jint/Runtime/Descriptors/PropertyDescriptor.cs

@@ -3,7 +3,6 @@ using System.Runtime.CompilerServices;
 using Jint.Collections;
 using Jint.Native;
 using Jint.Native.Object;
-using Jint.Runtime.Descriptors.Specialized;
 
 namespace Jint.Runtime.Descriptors
 {

+ 0 - 47
Jint/Runtime/Descriptors/Specialized/FieldInfoDescriptor.cs

@@ -1,47 +0,0 @@
-using System.Globalization;
-using System.Reflection;
-using Jint.Native;
-
-namespace Jint.Runtime.Descriptors.Specialized
-{
-    internal sealed class FieldInfoDescriptor : PropertyDescriptor
-    {
-        private readonly Engine _engine;
-        private readonly FieldInfo _fieldInfo;
-        private readonly object _item;
-
-        public FieldInfoDescriptor(Engine engine, FieldInfo fieldInfo, object item) : base(PropertyFlag.CustomJsValue)
-        {
-            _engine = engine;
-            _fieldInfo = fieldInfo;
-            _item = item;
-
-            Writable = !fieldInfo.Attributes.HasFlag(FieldAttributes.InitOnly) && engine.Options._IsClrWriteAllowed; // don't write to fields marked as readonly
-        }
-
-        protected internal override JsValue CustomValue
-        {
-            get => JsValue.FromObject(_engine, _fieldInfo.GetValue(_item));
-            set
-            {
-                var currentValue = value;
-                object obj;
-                if (_fieldInfo.FieldType == typeof (JsValue))
-                {
-                    obj = currentValue;
-                }
-                else
-                {
-                    // attempt to convert the JsValue to the target type
-                    obj = currentValue.ToObject();
-                    if (obj.GetType() != _fieldInfo.FieldType)
-                    {
-                        obj = _engine.ClrTypeConverter.Convert(obj, _fieldInfo.FieldType, CultureInfo.InvariantCulture);
-                    }
-                }
-
-                _fieldInfo.SetValue(_item, obj);
-            }
-        }
-    }
-}

+ 0 - 165
Jint/Runtime/Descriptors/Specialized/IndexDescriptor.cs

@@ -1,165 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Reflection;
-using Jint.Native;
-
-namespace Jint.Runtime.Descriptors.Specialized
-{
-    internal sealed class IndexDescriptor : PropertyDescriptor
-    {
-        private readonly Engine _engine;
-        private readonly object _key;
-        private readonly object _target;
-        private readonly PropertyInfo _indexer;
-        private readonly MethodInfo _containsKey;
-
-        private static readonly PropertyInfo _iListIndexer = typeof(IList).GetProperty("Item");
-
-        internal IndexDescriptor(Engine engine, object target, PropertyInfo indexer, MethodInfo containsKey, object key)
-            : base(PropertyFlag.Enumerable | PropertyFlag.CustomJsValue)
-        {
-            _engine = engine;
-            _target = target;
-            _indexer = indexer;
-            _containsKey = containsKey;
-            _key = key;
-            Writable = engine.Options._IsClrWriteAllowed;
-        }
-
-        internal static bool TryFindIndexer(
-            Engine engine,
-            Type targetType,
-            string propertyName,
-            out Func<object, IndexDescriptor> factory)
-        {
-            var paramTypeArray = new Type[1];
-            Func<object, IndexDescriptor> ComposeIndexerFactory(PropertyInfo candidate, Type paramType)
-            {
-                if (engine.ClrTypeConverter.TryConvert(propertyName, paramType, CultureInfo.InvariantCulture, out var key))
-                {
-                    // the key can be converted for this indexer
-                    var indexerProperty = candidate;
-                    // get contains key method to avoid index exception being thrown in dictionaries
-                    paramTypeArray[0] = paramType;
-                    var containsKeyMethod = targetType.GetMethod(nameof(IDictionary<string,string>.ContainsKey), paramTypeArray);
-                    if (containsKeyMethod is null)
-                    {
-                        paramTypeArray[0] = typeof(object);
-                        containsKeyMethod = targetType.GetMethod(nameof(IDictionary.Contains), paramTypeArray);
-                    }
-                    return (target) => new IndexDescriptor(engine, target, indexerProperty, containsKeyMethod, key);
-                }
-
-                // the key type doesn't work for this indexer
-                return null;
-            }
-
-            // default indexer wins
-            if (typeof(IList).IsAssignableFrom(targetType))
-            {
-                factory = ComposeIndexerFactory(_iListIndexer, typeof(int));
-                if (factory != null)
-                {
-                    return true;
-                }
-            }
-
-            // try to find first indexer having either public getter or setter with matching argument type
-            foreach (var candidate in targetType.GetProperties())
-            {
-                var indexParameters = candidate.GetIndexParameters();
-                if (indexParameters.Length != 1)
-                {
-                    continue;
-                }
-
-                if (candidate.GetGetMethod() != null || candidate.GetSetMethod() != null)
-                {
-                    var paramType = indexParameters[0].ParameterType;
-                    factory = ComposeIndexerFactory(candidate, paramType);
-                    if (factory != null)
-                    {
-                        return true;
-                    }
-                }
-            }
-
-            factory = default;
-            return false;
-        }
-
-        protected internal override JsValue CustomValue
-        {
-            get
-            {
-                var getter = _indexer.GetGetMethod();
-
-                if (getter == null)
-                {
-                    ExceptionHelper.ThrowInvalidOperationException("Indexer has no public getter.");
-                }
-
-                object[] parameters = { _key };
-
-                if (_containsKey != null)
-                {
-                    if ((_containsKey.Invoke(_target, parameters) as bool?) != true)
-                    {
-                        return JsValue.Undefined;
-                    }
-                }
-
-                try
-                {
-                    return JsValue.FromObject(_engine, getter.Invoke(_target, parameters));
-                }
-                catch (TargetInvocationException tie)
-                {
-                    switch (tie.InnerException)
-                    {
-                        case null:
-                            throw;
-                        case ArgumentOutOfRangeException _:
-                        case IndexOutOfRangeException _:
-                        case InvalidOperationException _:
-                            return JsValue.Undefined;
-                        default:
-                            throw tie.InnerException;
-                    }
-                }
-            }
-            set
-            {
-                var setter = _indexer.GetSetMethod();
-                if (setter == null)
-                {
-                    ExceptionHelper.ThrowInvalidOperationException("Indexer has no public setter.");
-                }
-
-                var obj = value?.ToObject();
-                
-                // attempt to convert to expected type
-                if (obj != null && obj.GetType() != _indexer.PropertyType)
-                {
-                    obj = _engine.ClrTypeConverter.Convert(obj, _indexer.PropertyType, CultureInfo.InvariantCulture);
-                }
-                
-                object[] parameters = { _key,  obj };
-                try
-                {
-                    setter!.Invoke(_target, parameters);
-                }
-                catch (TargetInvocationException tie)
-                {
-                    if (tie.InnerException != null)
-                    {
-                        throw tie.InnerException;
-                    }
-                    throw;
-                }
-            }
-        }
-    }
-}

+ 0 - 68
Jint/Runtime/Descriptors/Specialized/PropertyInfoDescriptor.cs

@@ -1,68 +0,0 @@
-using System.Globalization;
-using System.Reflection;
-using Jint.Native;
-
-namespace Jint.Runtime.Descriptors.Specialized
-{
-    internal sealed class PropertyInfoDescriptor : PropertyDescriptor
-    {
-        private readonly Engine _engine;
-        private readonly PropertyInfo _propertyInfo;
-        private readonly object _item;
-
-        public PropertyInfoDescriptor(Engine engine, PropertyInfo propertyInfo, object item) 
-            : base(PropertyFlag.Enumerable | PropertyFlag.CustomJsValue)
-        {
-            _engine = engine;
-            _propertyInfo = propertyInfo;
-            _item = item;
-
-            Writable = propertyInfo.CanWrite && engine.Options._IsClrWriteAllowed;
-        }
-
-        protected internal override JsValue CustomValue
-        {
-            get
-            {
-                object v;
-                try
-                {
-                    v = _propertyInfo.GetValue(_item, null);
-                }
-                catch (TargetInvocationException exception)
-                {
-                    ExceptionHelper.ThrowMeaningfulException(_engine, exception);
-                    throw;
-                }
-
-                return JsValue.FromObject(_engine, v);
-            }
-            set
-            {
-                object obj;
-                if (_propertyInfo.PropertyType == typeof(JsValue))
-                {
-                    obj = value;
-                }
-                else
-                {
-                    // attempt to convert the JsValue to the target type
-                    obj = value.ToObject();
-                    if (obj != null && obj.GetType() != _propertyInfo.PropertyType)
-                    {
-                        obj = _engine.ClrTypeConverter.Convert(obj, _propertyInfo.PropertyType, CultureInfo.InvariantCulture);
-                    }
-                }
-
-                try
-                {
-                    _propertyInfo.SetValue(_item, obj, null);
-                }
-                catch (TargetInvocationException exception)
-                {
-                    ExceptionHelper.ThrowMeaningfulException(_engine, exception);
-                }
-            }
-        }
-    }
-}

+ 46 - 0
Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs

@@ -0,0 +1,46 @@
+using System.Reflection;
+using Jint.Native;
+using Jint.Runtime.Interop.Reflection;
+
+namespace Jint.Runtime.Descriptors.Specialized
+{
+    internal sealed class ReflectionDescriptor : PropertyDescriptor
+    {
+        private readonly Engine _engine;
+        private readonly ReflectionAccessor _reflectionAccessor;
+        private readonly object _target;
+
+        public ReflectionDescriptor(
+            Engine engine,
+            ReflectionAccessor reflectionAccessor,
+            object target)
+            : base(PropertyFlag.Enumerable | PropertyFlag.CustomJsValue)
+        {
+            _engine = engine;
+            _reflectionAccessor = reflectionAccessor;
+            _target = target;
+            Writable = reflectionAccessor.Writable && engine.Options._IsClrWriteAllowed;
+        }
+
+       
+        protected internal override JsValue CustomValue
+        {
+            get
+            {
+                var value = _reflectionAccessor.GetValue(_engine, _target);
+                return JsValue.FromObject(_engine, value);
+            }
+            set
+            {
+                try
+                {
+                    _reflectionAccessor.SetValue(_engine, _target, value);
+                }
+                catch (TargetInvocationException exception)
+                {
+                    ExceptionHelper.ThrowMeaningfulException(_engine, exception);
+                }
+            }
+        }
+    }
+}

+ 89 - 0
Jint/Runtime/Interop/MethodDescriptor.cs

@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace Jint.Runtime.Interop
+{
+    internal class MethodDescriptor
+    {
+        private MethodDescriptor(MethodBase method)
+        {
+            Method = method;
+            Parameters = method.GetParameters();
+            foreach (var parameter in Parameters)
+            {
+                if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute)))
+                {
+                    HasParams = true;
+                    break;
+                }
+
+                if (parameter.HasDefaultValue)
+                {
+                    ParameterDefaultValuesCount++;
+                }
+            }
+        }
+
+        public MethodBase Method { get; }
+        public ParameterInfo[] Parameters { get; }
+        public bool HasParams { get; }
+        public int ParameterDefaultValuesCount { get; }
+
+        public static MethodDescriptor[] Build<T>(List<T> source) where T : MethodBase
+        {
+            var descriptors = new MethodDescriptor[source.Count];
+            for (var i = 0; i < source.Count; i++)
+            {
+                descriptors[i] = new MethodDescriptor(source[i]);
+            }
+
+            return Prioritize(descriptors);
+        }
+
+        public static MethodDescriptor[] Build<T>(T[] source) where T : MethodBase
+        {
+            var descriptors = new MethodDescriptor[source.Length];
+            for (var i = 0; i < source.Length; i++)
+            {
+                descriptors[i] = new MethodDescriptor(source[i]);
+            }
+
+            return Prioritize(descriptors);
+        }
+
+        private static MethodDescriptor[] Prioritize(MethodDescriptor[] descriptors)
+        {
+            static int CreateComparison(MethodDescriptor d1, MethodDescriptor d2)
+            {
+                // put params versions to end, they can be tricky to match and can cause trouble / extra overhead
+                if (d1.HasParams)
+                {
+                    return 1;
+                }
+
+                if (d2.HasParams)
+                {
+                    return -1;
+                }
+
+                // then favor less parameters
+                if (d1.Parameters.Length > d2.Parameters.Length)
+                {
+                    return 1;
+                }
+
+                if (d2.Parameters.Length > d1.Parameters.Length)
+                {
+                    return -1;
+                }
+
+                return 0;
+            }
+
+            Array.Sort(descriptors, CreateComparison);
+
+            return descriptors;
+        }
+    }
+}

+ 20 - 17
Jint/Runtime/Interop/MethodInfoFunctionInstance.cs

@@ -6,39 +6,38 @@ using Jint.Native.Function;
 
 namespace Jint.Runtime.Interop
 {
-    public sealed class MethodInfoFunctionInstance : FunctionInstance
+    internal sealed class MethodInfoFunctionInstance : FunctionInstance
     {
         private static readonly JsString _name = new JsString("Function");
-        private readonly MethodInfo[] _methods;
+        private readonly MethodDescriptor[] _methods;
 
-        public MethodInfoFunctionInstance(Engine engine, MethodInfo[] methods)
+        public MethodInfoFunctionInstance(Engine engine, MethodDescriptor[] methods)
             : base(engine, _name)
         {
             _methods = methods;
             _prototype = engine.Function.PrototypeObject;
         }
 
-        public override JsValue Call(JsValue thisObject, JsValue[] arguments)
+        public override JsValue Call(JsValue thisObject, JsValue[] jsArguments)
         {
-            return Invoke(_methods, thisObject, arguments);
-        }
-
-        public JsValue Invoke(MethodInfo[] methodInfos, JsValue thisObject, JsValue[] jsArguments)
-        {
-            JsValue[] ArgumentProvider(MethodInfo method, bool hasParams) =>
-                hasParams
+            JsValue[] ArgumentProvider(MethodDescriptor method) =>
+                method.HasParams
                     ? ProcessParamsArrays(jsArguments, method)
                     : jsArguments;
 
             var converter = Engine.ClrTypeConverter;
 
-            foreach (var tuple in TypeConverter.FindBestMatch(_engine, methodInfos, ArgumentProvider))
+            object[] parameters = null;
+            foreach (var tuple in TypeConverter.FindBestMatch(_engine, _methods, ArgumentProvider))
             {
                 var method = tuple.Item1;
                 var arguments = tuple.Item2;
 
-                var parameters = new object[arguments.Length];
-                var methodParameters = method.GetParameters();
+                if (parameters == null || parameters.Length != arguments.Length)
+                {
+                    parameters = new object[arguments.Length];
+                }
+                var methodParameters = method.Parameters;
                 var argumentsMatch = true;
 
                 for (var i = 0; i < arguments.Length; i++)
@@ -85,7 +84,7 @@ namespace Jint.Runtime.Interop
                 // todo: cache method info
                 try
                 {
-                    return FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters));
+                    return FromObject(Engine, method.Method.Invoke(thisObject.ToObject(), parameters));
                 }
                 catch (TargetInvocationException exception)
                 {
@@ -99,18 +98,22 @@ namespace Jint.Runtime.Interop
         /// <summary>
         /// Reduces a flat list of parameters to a params array, if needed
         /// </summary>
-        private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, MethodInfo methodInfo)
+        private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, MethodDescriptor methodInfo)
         {
-            var parameters = methodInfo.GetParameters();
+            var parameters = methodInfo.Parameters;
 
             var nonParamsArgumentsCount = parameters.Length - 1;
             if (jsArguments.Length < nonParamsArgumentsCount)
+            {
                 return jsArguments;
+            }
 
             var argsToTransform = jsArguments.Skip(nonParamsArgumentsCount);
 
             if (argsToTransform.Length == 1 && argsToTransform[0].IsArray())
+            {
                 return jsArguments;
+            }
 
             var jsArray = Engine.Array.Construct(Arguments.Empty);
             Engine.Array.PrototypeObject.Push(jsArray, argsToTransform);

+ 175 - 122
Jint/Runtime/Interop/ObjectWrapper.cs

@@ -3,11 +3,12 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Reflection;
+using System.Threading;
 using Jint.Native;
 using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
+using Jint.Runtime.Interop.Reflection;
 
 namespace Jint.Runtime.Interop
 {
@@ -16,59 +17,60 @@ namespace Jint.Runtime.Interop
 	/// </summary>
 	public sealed class ObjectWrapper : ObjectInstance, IObjectWrapper
     {
+        private readonly TypeDescriptor _typeDescriptor;
+
         public ObjectWrapper(Engine engine, object obj)
             : base(engine)
         {
             Target = obj;
-            var type = obj.GetType();
-            if (ObjectIsArrayLikeClrCollection(type))
+            _typeDescriptor = TypeDescriptor.Get(obj.GetType());
+            if (_typeDescriptor.IsArrayLike)
             {
                 // create a forwarder to produce length from Count or Length if one of them is present
-                var lengthProperty = type.GetProperty("Count") ?? type.GetProperty("Length");
+                var lengthProperty = obj.GetType().GetProperty("Count") ?? obj.GetType().GetProperty("Length");
                 if (lengthProperty is null)
                 {
                     return;
                 }
-                IsArrayLike = true;
-                IsIntegerIndexedArray = typeof(IList).IsAssignableFrom(type);
-
-                var functionInstance = new ClrFunctionInstance(engine, "length", (thisObj, arguments) => JsNumber.Create((int) lengthProperty.GetValue(obj)));
+                var functionInstance = new ClrFunctionInstance(engine, "length", (_, _) => JsNumber.Create((int) lengthProperty.GetValue(obj)));
                 var descriptor = new GetSetPropertyDescriptor(functionInstance, Undefined, PropertyFlag.Configurable);
                 SetProperty(KnownKeys.Length, descriptor);
             }
         }
 
-        private static bool ObjectIsArrayLikeClrCollection(Type type)
+
+        public object Target { get; }
+
+        public override bool IsArrayLike => _typeDescriptor.IsArrayLike;
+
+        internal override bool IsIntegerIndexedArray => _typeDescriptor.IsIntegerIndexedArray;
+
+        public override bool Set(JsValue property, JsValue value, JsValue receiver)
         {
-            if (typeof(ICollection).IsAssignableFrom(type))
+            // check if we can take shortcuts for empty object, no need to generate properties
+            if (property is JsString stringKey)
             {
-                return true;
-            }
-            
-            foreach (var interfaceType in type.GetInterfaces())
-            {
-                if (!interfaceType.IsGenericType)
-                {
-                    continue;
-                }
-                
-                if (interfaceType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>)
-                    || interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>))
+                var member = stringKey.ToString();
+                if (_properties is null || !_properties.ContainsKey(member))
                 {
+                    // can try utilize fast path
+                    var accessor = GetAccessor(_engine, Target.GetType(), member);
+                    
+                    // CanPut logic
+                    if (!accessor.Writable || !_engine.Options._IsClrWriteAllowed)
+                    {
+                        return false;
+                    }
+                    
+                    accessor.SetValue(_engine, Target, value);
                     return true;
                 }
             }
 
-            return false;
+            return SetSlow(property, value);
         }
 
-        public object Target { get; }
-
-        public override bool IsArrayLike { get; }
-
-        internal override bool IsIntegerIndexedArray { get; }
-
-        public override bool Set(JsValue property, JsValue value, JsValue receiver)
+        private bool SetSlow(JsValue property, JsValue value)
         {
             if (!CanPut(property))
             {
@@ -88,16 +90,37 @@ namespace Jint.Runtime.Interop
 
         public override JsValue Get(JsValue property, JsValue receiver)
         {
-            if (property.IsSymbol())
+            if (property.IsInteger() && Target is IList list)
+            {
+                var index = (int) ((JsNumber) property)._value;
+                return (uint) index < list.Count ? FromObject(_engine, list[index]) : Undefined;
+            }
+            
+            if (property.IsSymbol() && property != GlobalSymbolRegistry.Iterator)
             {
                 // wrapped objects cannot have symbol properties
                 return Undefined;
             }
 
-            if (property.IsInteger() && Target is IList list)
+            if (property is JsString stringKey)
             {
-                var index = (int) ((JsNumber) property)._value;
-                return (uint) index < list.Count ? FromObject(_engine, list[index]) : Undefined;
+                var member = stringKey.ToString();
+                var result = Engine.Options._MemberAccessor?.Invoke(Engine, Target, member);
+                if (result is not null)
+                {
+                    return result;
+                }
+                
+                if (_properties is null || !_properties.ContainsKey(member))
+                {
+                    // can try utilize fast path
+                    var accessor = GetAccessor(_engine, Target.GetType(), member);
+                    var value = accessor.GetValue(_engine, Target);
+                    if (value is not null)
+                    {
+                        return FromObject(_engine, value);
+                    }
+                }
             }
 
             return base.Get(property, receiver);
@@ -188,96 +211,57 @@ namespace Jint.Runtime.Interop
                 return iteratorProperty;
             }
 
-            var memberAccessor = Engine.Options._MemberAccessor;
-
-            if (memberAccessor != null)
-            {
-                var result = memberAccessor.Invoke(Engine, Target, property.ToString());
-
-                if (result != null)
-                {
-                    return new PropertyDescriptor(result, PropertyFlag.OnlyEnumerable);
-                }
-            }
-
-            var type = Target.GetType();
-            var key = new ClrPropertyDescriptorFactoriesKey(type, property.ToString());
-
-            if (!_engine.ClrPropertyDescriptorFactories.TryGetValue(key, out var factory))
+            var member = property.ToString();
+            var result = Engine.Options._MemberAccessor?.Invoke(Engine, Target, member);
+            if (result is not null)
             {
-                factory = ResolveProperty(type, property.ToString());
-                _engine.ClrPropertyDescriptorFactories[key] = factory;
+                return new PropertyDescriptor(result, PropertyFlag.OnlyEnumerable);
             }
 
-            var descriptor = factory(_engine, Target);
-            AddProperty(property, descriptor);
+            var accessor = GetAccessor(_engine, Target.GetType(), member);
+            var descriptor = accessor.CreatePropertyDescriptor(_engine, Target);
+            SetProperty(member, descriptor);
             return descriptor;
         }
-        
-        private Func<Engine, object, PropertyDescriptor> ResolveProperty(Type type, string propertyName)
+
+        private static ReflectionAccessor GetAccessor(Engine engine, Type type, string member)
         {
-            var isNumber = uint.TryParse(propertyName, out _);
+            var key = new ClrPropertyDescriptorFactoriesKey(type, member);
 
-            // properties and fields cannot be numbers
-            if (!isNumber)
+            var factories = Engine.ReflectionAccessors;
+            if (factories.TryGetValue(key, out var accessor))
             {
-                // look for a property, bit be wary of indexers, we don't want indexers which have name "Item" to take precedence
-                PropertyInfo property = null;
-                foreach (var p in type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
-                {
-                    // only if it's not an indexer, we can do case-ignoring matches
-                    var isStandardIndexer = p.GetIndexParameters().Length == 1 && p.Name == "Item";
-                    if (!isStandardIndexer && EqualsIgnoreCasing(p.Name, propertyName))
-                    {
-                        property = p;
-                        break;
-                    }
-                }
-
-                if (property != null)
-                {
-                    return (engine, target) => new PropertyInfoDescriptor(engine, property, target);
-                }
+                return accessor;
+            }
 
-                // look for a field
-                FieldInfo field = null;
-                foreach (var f in type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
+            var factory = ResolvePropertyDescriptorFactory(engine, type, member);
+            // racy, we don't care, worst case we'll catch up later
+            Interlocked.CompareExchange(ref Engine.ReflectionAccessors,
+                new Dictionary<ClrPropertyDescriptorFactoriesKey, ReflectionAccessor>(factories)
                 {
-                    if (EqualsIgnoreCasing(f.Name, propertyName))
-                    {
-                        field = f;
-                        break;
-                    }
-                }
+                    [key] = factory
+                }, factories);
 
-                if (field != null)
-                {
-                    return (engine, target) => new FieldInfoDescriptor(engine, field, target);
-                }
-                
-                // if no properties were found then look for a method
-                List<MethodInfo> methods = null;
-                foreach (var m in type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
-                {
-                    if (EqualsIgnoreCasing(m.Name, propertyName))
-                    {
-                        methods ??= new List<MethodInfo>();
-                        methods.Add(m);
-                    }
-                }
+            return factory;
+        }
 
-                if (methods?.Count > 0)
-                {
-                    var array = methods.ToArray();
-                    return (engine, target) => new PropertyDescriptor(new MethodInfoFunctionInstance(engine, array), PropertyFlag.OnlyEnumerable);
-                }
+        private static ReflectionAccessor ResolvePropertyDescriptorFactory(Engine engine, Type type, string memberName)
+        {
+            var isNumber = uint.TryParse(memberName, out _);
 
+            // we can always check indexer if there's one, and then fall back to properties if indexer returns null
+            IndexerAccessor.TryFindIndexer(engine, type, memberName, out var indexerAccessor, out var indexer);
+            
+            // properties and fields cannot be numbers
+            if (!isNumber && TryFindStringPropertyAccessor(type, memberName, indexer, out var temp))
+            {
+                return temp;
             }
 
             // if no methods are found check if target implemented indexing
-            if (IndexDescriptor.TryFindIndexer(_engine, type, propertyName, out var indexerFactory))
+            if (indexerAccessor != null)
             {
-                return (engine, target) => indexerFactory(target);
+                return indexerAccessor;
             }
 
             // try to find a single explicit property implementation
@@ -286,7 +270,7 @@ namespace Jint.Runtime.Interop
             {
                 foreach (var iprop in iface.GetProperties())
                 {
-                    if (EqualsIgnoreCasing(iprop.Name, propertyName))
+                    if (EqualsIgnoreCasing(iprop.Name, memberName))
                     {
                         list ??= new List<PropertyInfo>();
                         list.Add(iprop);
@@ -296,7 +280,7 @@ namespace Jint.Runtime.Interop
 
             if (list?.Count == 1)
             {
-                return (engine, target) => new PropertyInfoDescriptor(engine, list[0], target);
+                return new PropertyAccessor(memberName, list[0]);
             }
 
             // try to find explicit method implementations
@@ -305,7 +289,7 @@ namespace Jint.Runtime.Interop
             {
                 foreach (var imethod in iface.GetMethods())
                 {
-                    if (EqualsIgnoreCasing(imethod.Name, propertyName))
+                    if (EqualsIgnoreCasing(imethod.Name, memberName))
                     {
                         explicitMethods ??= new List<MethodInfo>();
                         explicitMethods.Add(imethod);
@@ -315,36 +299,105 @@ namespace Jint.Runtime.Interop
 
             if (explicitMethods?.Count > 0)
             {
-                var array = explicitMethods.ToArray();
-                return (engine, target) => new PropertyDescriptor(new MethodInfoFunctionInstance(engine, array), PropertyFlag.OnlyEnumerable);
+                return new MethodAccessor(MethodDescriptor.Build(explicitMethods));
             }
 
             // try to find explicit indexer implementations
             foreach (var interfaceType in type.GetInterfaces())
             {
-                if (IndexDescriptor.TryFindIndexer(_engine, interfaceType, propertyName, out var interfaceIndexerFactory))
+                if (IndexerAccessor.TryFindIndexer(engine, interfaceType, memberName, out var accessor, out _))
                 {
-                    return (engine, target) => interfaceIndexerFactory(target);
+                    return accessor;
                 }
             }
 
-            return (engine, target) => PropertyDescriptor.Undefined;
+            return ConstantValueAccessor.NullAccessor;
         }
 
-        private static bool EqualsIgnoreCasing(string s1, string s2)
+        private static bool TryFindStringPropertyAccessor(
+            Type type,
+            string memberName,
+            PropertyInfo indexerToTry,
+            out ReflectionAccessor wrapper)
         {
-            bool equals = false;
-            if (s1.Length == s2.Length)
+            // look for a property, bit be wary of indexers, we don't want indexers which have name "Item" to take precedence
+            PropertyInfo property = null;
+            foreach (var p in type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
+            {
+                // only if it's not an indexer, we can do case-ignoring matches
+                var isStandardIndexer = p.GetIndexParameters().Length == 1 && p.Name == "Item";
+                if (!isStandardIndexer && EqualsIgnoreCasing(p.Name, memberName))
+                {
+                    property = p;
+                    break;
+                }
+            }
+
+            if (property != null)
+            {
+                wrapper = new PropertyAccessor(memberName, property, indexerToTry);
+                return true;
+            }
+
+            // look for a field
+            FieldInfo field = null;
+            foreach (var f in type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
             {
-                if (s1.Length > 0)
+                if (EqualsIgnoreCasing(f.Name, memberName))
                 {
-                    equals = char.ToLowerInvariant(s1[0]) == char.ToLowerInvariant(s2[0]);
+                    field = f;
+                    break;
                 }
-                if (equals && s1.Length > 1)
+            }
+
+            if (field != null)
+            {
+                wrapper = new FieldAccessor(field, memberName, indexerToTry);
+                return true;
+            }
+
+            // if no properties were found then look for a method
+            List<MethodInfo> methods = null;
+            foreach (var m in type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
+            {
+                if (EqualsIgnoreCasing(m.Name, memberName))
                 {
-                    equals = s1.Substring(1) == s2.Substring(1);
+                    methods ??= new List<MethodInfo>();
+                    methods.Add(m);
                 }
             }
+
+            if (methods?.Count > 0)
+            {
+                wrapper = new MethodAccessor(MethodDescriptor.Build(methods));
+                return true;
+            }
+
+            wrapper = default;
+            return false;
+        }
+
+        private static bool EqualsIgnoreCasing(string s1, string s2)
+        {
+            if (s1.Length != s2.Length)
+            {
+                return false;
+            }
+
+            var equals = false;
+            if (s1.Length > 0)
+            {
+                equals = char.ToLowerInvariant(s1[0]) == char.ToLowerInvariant(s2[0]);
+            }
+
+            if (@equals && s1.Length > 1)
+            {
+#if NETSTANDARD2_1
+                equals = s1.AsSpan(1).SequenceEqual(s2.AsSpan(1));
+#else
+                equals = s1.Substring(1) == s2.Substring(1);
+#endif
+            }
             return equals;
         }
     }

+ 37 - 0
Jint/Runtime/Interop/Reflection/ConstantValueAccessor.cs

@@ -0,0 +1,37 @@
+using System;
+using Jint.Native;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Runtime.Interop.Reflection
+{
+    internal sealed class ConstantValueAccessor : ReflectionAccessor
+    {
+        public static readonly ConstantValueAccessor NullAccessor = new(null);
+
+        public ConstantValueAccessor(JsValue value) : base(null, null)
+        {
+            ConstantValue = value;
+        }
+
+        public override bool Writable => false;
+
+        protected override JsValue ConstantValue { get; }
+
+        protected override object DoGetValue(object target)
+        {
+            return ConstantValue;
+        }
+
+        protected override void DoSetValue(object target, object value)
+        {
+            throw new InvalidOperationException();
+        }
+
+        public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target)
+        {
+            return ConstantValue is null 
+                ? PropertyDescriptor.Undefined 
+                : new(ConstantValue, PropertyFlag.AllForbidden);
+        }
+    }
+}

+ 27 - 0
Jint/Runtime/Interop/Reflection/FieldAccessor.cs

@@ -0,0 +1,27 @@
+using System.Reflection;
+
+namespace Jint.Runtime.Interop.Reflection
+{
+    internal sealed class FieldAccessor : ReflectionAccessor
+    {
+        private readonly FieldInfo _fieldInfo;
+
+        public FieldAccessor(FieldInfo fieldInfo, string memberName = null, PropertyInfo indexer = null)
+            : base(fieldInfo.FieldType, memberName, indexer)
+        {
+            _fieldInfo = fieldInfo;
+        }
+
+        public override bool Writable => !_fieldInfo.Attributes.HasFlag(FieldAttributes.InitOnly);
+
+        protected override object DoGetValue(object target)
+        {
+            return _fieldInfo.GetValue(target);
+        }
+
+        protected override void DoSetValue(object target, object value)
+        {
+            _fieldInfo.SetValue(target, value);
+        }
+    }
+}

+ 134 - 0
Jint/Runtime/Interop/Reflection/IndexerAccessor.cs

@@ -0,0 +1,134 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Reflection;
+using Jint.Native;
+
+namespace Jint.Runtime.Interop.Reflection
+{
+    internal sealed class IndexerAccessor : ReflectionAccessor
+    {
+        private readonly object _key;
+
+        private readonly PropertyInfo _indexer;
+        private readonly MethodInfo _getter;
+        private readonly MethodInfo _setter;
+        private readonly MethodInfo _containsKey;
+
+        private static readonly PropertyInfo _iListIndexer = typeof(IList).GetProperty("Item");
+
+        private IndexerAccessor(PropertyInfo indexer, MethodInfo containsKey, object key)
+            : base(indexer.PropertyType, key)
+        {
+            _indexer = indexer;
+            _containsKey = containsKey;
+            _key = key;
+
+            _getter = indexer.GetGetMethod();
+            _setter = indexer.GetSetMethod();
+        }
+
+        internal static bool TryFindIndexer(
+            Engine engine,
+            Type targetType,
+            string propertyName,
+            out IndexerAccessor indexerAccessor,
+            out PropertyInfo indexer)
+        {
+            var paramTypeArray = new Type[1];
+
+            IndexerAccessor ComposeIndexerFactory(PropertyInfo candidate, Type paramType)
+            {
+                if (engine.ClrTypeConverter.TryConvert(propertyName, paramType, CultureInfo.InvariantCulture, out var key))
+                {
+                    // the key can be converted for this indexer
+                    var indexerProperty = candidate;
+                    // get contains key method to avoid index exception being thrown in dictionaries
+                    paramTypeArray[0] = paramType;
+                    var containsKeyMethod = targetType.GetMethod(nameof(IDictionary<string, string>.ContainsKey), paramTypeArray);
+                    if (containsKeyMethod is null)
+                    {
+                        paramTypeArray[0] = typeof(object);
+                        containsKeyMethod = targetType.GetMethod(nameof(IDictionary.Contains), paramTypeArray);
+                    }
+
+                    return new IndexerAccessor(indexerProperty, containsKeyMethod, key);
+                }
+
+                // the key type doesn't work for this indexer
+                return null;
+            }
+
+            // default indexer wins
+            if (typeof(IList).IsAssignableFrom(targetType))
+            {
+                indexerAccessor = ComposeIndexerFactory(_iListIndexer, typeof(int));
+                if (indexerAccessor != null)
+                {
+                    indexer = _iListIndexer;
+                    return true;
+                }
+            }
+
+            // try to find first indexer having either public getter or setter with matching argument type
+            foreach (var candidate in targetType.GetProperties())
+            {
+                var indexParameters = candidate.GetIndexParameters();
+                if (indexParameters.Length != 1)
+                {
+                    continue;
+                }
+
+                if (candidate.GetGetMethod() != null || candidate.GetSetMethod() != null)
+                {
+                    var paramType = indexParameters[0].ParameterType;
+                    indexerAccessor = ComposeIndexerFactory(candidate, paramType);
+                    if (indexerAccessor != null)
+                    {
+                        indexer = candidate;
+                        return true;
+                    }
+                }
+            }
+
+            indexerAccessor = default;
+            indexer = default;
+            return false;
+        }
+
+        public override bool Writable => _indexer.CanWrite;
+
+        protected override object DoGetValue(object target)
+        {
+            if (_getter is null)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Indexer has no public getter.");
+                return null;
+            }
+
+            object[] parameters = {_key};
+
+            if (_containsKey != null)
+            {
+                if (_containsKey.Invoke(target, parameters) as bool? != true)
+                {
+                    return JsValue.Undefined;
+                }
+            }
+
+            return _getter.Invoke(target, parameters);
+        }
+
+        protected override void DoSetValue(object target, object value)
+        {
+            if (_setter is null)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Indexer has no public setter.");
+            }
+
+            object[] parameters = {_key, value};
+            _setter!.Invoke(target, parameters);
+        }
+    }
+}

+ 30 - 0
Jint/Runtime/Interop/Reflection/MethodAccessor.cs

@@ -0,0 +1,30 @@
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Runtime.Interop.Reflection
+{
+    internal sealed class MethodAccessor : ReflectionAccessor
+    {
+        private readonly MethodDescriptor[] _methods;
+
+        public MethodAccessor(MethodDescriptor[] methods) : base(null, null)
+        {
+            _methods = methods;
+        }
+
+        public override bool Writable => false;
+        
+        protected override object DoGetValue(object target)
+        {
+            return null;
+        }
+
+        protected override void DoSetValue(object target, object value)
+        {
+        }
+
+        public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target)
+        {
+            return new(new MethodInfoFunctionInstance(engine, _methods), PropertyFlag.OnlyEnumerable);
+        }
+    }
+}

+ 30 - 0
Jint/Runtime/Interop/Reflection/PropertyAccessor.cs

@@ -0,0 +1,30 @@
+using System.Reflection;
+
+namespace Jint.Runtime.Interop.Reflection
+{
+    internal sealed class PropertyAccessor : ReflectionAccessor
+    {
+        private readonly PropertyInfo _propertyInfo;
+
+        public PropertyAccessor(
+            string memberName,
+            PropertyInfo propertyInfo,
+            PropertyInfo indexerToTry = null) 
+            : base(propertyInfo.PropertyType, memberName, indexerToTry)
+        {
+            _propertyInfo = propertyInfo;
+        }
+
+        public override bool Writable => _propertyInfo.CanWrite;
+
+        protected override object DoGetValue(object target)
+        {
+            return _propertyInfo.GetValue(target, index: null);
+        }
+
+        protected override void DoSetValue(object target, object value)
+        {
+            _propertyInfo.SetValue(target, value, index: null);
+        }
+    }
+}

+ 123 - 0
Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs

@@ -0,0 +1,123 @@
+using System;
+using System.Globalization;
+using System.Reflection;
+using Jint.Native;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Descriptors.Specialized;
+
+namespace Jint.Runtime.Interop.Reflection
+{
+    /// <summary>
+    /// Strategy to read and write CLR object properties and fields.
+    /// </summary>
+    internal abstract class ReflectionAccessor
+    {
+        private readonly Type _memberType;
+        private readonly object _memberName;
+        private readonly PropertyInfo _indexer;
+
+        protected ReflectionAccessor(
+            Type memberType,
+            object memberName,
+            PropertyInfo indexer = null)
+        {
+            _memberType = memberType;
+            _memberName = memberName;
+            _indexer = indexer;
+        }
+
+        public abstract bool Writable { get; }
+
+        protected abstract object DoGetValue(object target);
+
+        protected abstract void DoSetValue(object target, object value);
+
+        public object GetValue(Engine engine, object target)
+        {
+            var constantValue = ConstantValue;
+            if (constantValue is not null)
+            {
+                return constantValue;
+            }
+            
+            // first check indexer so we don't confuse inherited properties etc
+            var value = TryReadFromIndexer(target);
+
+            if (value is null)
+            {
+                try
+                {
+                    value = DoGetValue(target);
+                }
+                catch (TargetInvocationException tie)
+                {
+                    switch (tie.InnerException)
+                    {
+                        case ArgumentOutOfRangeException _:
+                        case IndexOutOfRangeException _:
+                        case InvalidOperationException _:
+                        case NotSupportedException _:
+                            return JsValue.Undefined;
+                    }
+
+                    ExceptionHelper.ThrowMeaningfulException(engine, tie);
+                }
+            }
+
+            return value;
+        }
+
+        protected virtual JsValue ConstantValue => null;
+
+        private object TryReadFromIndexer(object target)
+        {
+            var getter = _indexer?.GetGetMethod();
+            if (getter is null)
+            {
+                return null;
+            }
+
+            try
+            {
+                object[] parameters = { _memberName };
+                return getter.Invoke(target, parameters);
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        public void SetValue(Engine engine, object target, JsValue value)
+        {
+            object converted;
+            if (_memberType == typeof(JsValue))
+            {
+                converted = value;
+            }
+            else
+            {
+                // attempt to convert the JsValue to the target type
+                converted = value.ToObject();
+                if (converted != null && converted.GetType() != _memberType)
+                {
+                    converted = engine.ClrTypeConverter.Convert(converted, _memberType, CultureInfo.InvariantCulture);
+                }
+            }
+
+            try
+            {
+                DoSetValue(target, converted);
+            }
+            catch (TargetInvocationException exception)
+            {
+                ExceptionHelper.ThrowMeaningfulException(engine, exception);
+            }
+        }
+
+        public virtual PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target)
+        {
+            return new ReflectionDescriptor(engine, this, target);
+        }
+    }
+}

+ 52 - 0
Jint/Runtime/Interop/TypeDescriptor.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace Jint.Runtime.Interop
+{
+    internal class TypeDescriptor
+    {
+        private static readonly ConcurrentDictionary<Type, TypeDescriptor> _cache = new();
+
+        private TypeDescriptor(Type type)
+        {
+            IsArrayLike = DetermineIfObjectIsArrayLikeClrCollection(type);
+            IsIntegerIndexedArray = typeof(IList).IsAssignableFrom(type);
+        }
+
+
+        public bool IsArrayLike { get; }
+        public bool IsIntegerIndexedArray { get; }
+
+        public static TypeDescriptor Get(Type type)
+        {
+            return _cache.GetOrAdd(type, t => new TypeDescriptor(t));
+        }
+        
+        private static bool DetermineIfObjectIsArrayLikeClrCollection(Type type)
+        {
+            if (typeof(ICollection).IsAssignableFrom(type))
+            {
+                return true;
+            }
+            
+            foreach (var interfaceType in type.GetInterfaces())
+            {
+                if (!interfaceType.IsGenericType)
+                {
+                    continue;
+                }
+                
+                if (interfaceType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>)
+                    || interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+    }
+}

+ 61 - 25
Jint/Runtime/Interop/TypeReference.cs

@@ -1,18 +1,22 @@
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Reflection;
+using Jint.Collections;
 using Jint.Native;
 using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
+using Jint.Runtime.Interop.Reflection;
 
 namespace Jint.Runtime.Interop
 {
     public sealed class TypeReference : FunctionInstance, IConstructor, IObjectWrapper
     {
         private static readonly JsString _name = new JsString("typereference");
+        private static readonly ConcurrentDictionary<Type, MethodDescriptor[]> _constructorCache = new();
+        private static readonly ConcurrentDictionary<Tuple<Type, string>, ReflectionAccessor> _memberAccessors = new();
 
         private TypeReference(Engine engine)
             : base(engine, _name, FunctionThisMode.Global, ObjectClass.TypeReference)
@@ -53,14 +57,16 @@ namespace Jint.Runtime.Interop
                 return result;
             }
 
-            var constructors = ReferenceType.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
+            var constructors = _constructorCache.GetOrAdd(
+                ReferenceType,
+                t => MethodDescriptor.Build(t.GetConstructors(BindingFlags.Public | BindingFlags.Instance)));
 
-            foreach (var tuple in TypeConverter.FindBestMatch(_engine, constructors, (info, b) => arguments))
+            foreach (var tuple in TypeConverter.FindBestMatch(_engine, constructors, _ => arguments))
             {
                 var method = tuple.Item1;
 
                 var parameters = new object[arguments.Length];
-                var methodParameters = method.GetParameters();
+                var methodParameters = method.Parameters;
                 try
                 {
                     for (var i = 0; i < arguments.Length; i++)
@@ -80,7 +86,7 @@ namespace Jint.Runtime.Interop
                         }
                     }
 
-                    var constructor = (ConstructorInfo) method;
+                    var constructor = (ConstructorInfo) method.Method;
                     var instance = constructor.Invoke(parameters);
                     var result = TypeConverter.ToObject(Engine, FromObject(Engine, instance));
 
@@ -139,51 +145,81 @@ namespace Jint.Runtime.Interop
 
         public override PropertyDescriptor GetOwnProperty(JsValue property)
         {
-            // todo: cache members locally
-            var name = property.ToString();
-            if (ReferenceType.IsEnum)
+            if (property is not JsString jsString)
             {
-                Array enumValues = Enum.GetValues(ReferenceType);
-                Array enumNames = Enum.GetNames(ReferenceType);
+                return PropertyDescriptor.Undefined;
+            }
+            
+            var key = jsString._value;
+            var descriptor = PropertyDescriptor.Undefined;
+
+            if (_properties?.TryGetValue(key, out descriptor) != true)
+            {
+                descriptor = CreatePropertyDescriptor(key);
+                _properties ??= new PropertyDictionary();
+                _properties[key] = descriptor;
+            }
 
-                for (int i = 0; i < enumValues.Length; i++)
+            return descriptor;
+        }
+
+        private PropertyDescriptor CreatePropertyDescriptor(string name)
+        {
+            var accessor = _memberAccessors.GetOrAdd(
+                new Tuple<Type, string>(ReferenceType, name),
+                key => ResolveMemberAccessor(key.Item1, key.Item2)
+            );
+            return accessor.CreatePropertyDescriptor(_engine, ReferenceType);
+        }
+
+        private static ReflectionAccessor ResolveMemberAccessor(Type type, string name)
+        {
+            if (type.IsEnum)
+            {
+                var enumValues = Enum.GetValues(type);
+                var enumNames = Enum.GetNames(type);
+
+                for (var i = 0; i < enumValues.Length; i++)
                 {
                     if (enumNames.GetValue(i) as string == name)
                     {
-                        return new PropertyDescriptor((int) enumValues.GetValue(i), PropertyFlag.AllForbidden);
+                        return new ConstantValueAccessor((int) enumValues.GetValue(i));
                     }
                 }
-                return PropertyDescriptor.Undefined;
+
+                return ConstantValueAccessor.NullAccessor;
             }
 
-            var propertyInfo = ReferenceType.GetProperty(name, BindingFlags.Public | BindingFlags.Static);
+            var propertyInfo = type.GetProperty(name, BindingFlags.Public | BindingFlags.Static);
             if (propertyInfo != null)
             {
-                return new PropertyInfoDescriptor(Engine, propertyInfo, Type);
+                return new PropertyAccessor(name, propertyInfo);
             }
 
-            var fieldInfo = ReferenceType.GetField(name, BindingFlags.Public | BindingFlags.Static);
+            var fieldInfo = type.GetField(name, BindingFlags.Public | BindingFlags.Static);
             if (fieldInfo != null)
             {
-                return new FieldInfoDescriptor(Engine, fieldInfo, Type);
+                return new FieldAccessor(fieldInfo, name);
             }
 
-            List<MethodInfo> methodInfo = null;
-            foreach (var mi in ReferenceType.GetMethods(BindingFlags.Public | BindingFlags.Static))
+            List<MethodInfo> methods = null;
+            foreach (var mi in type.GetMethods(BindingFlags.Public | BindingFlags.Static))
             {
-                if (mi.Name == name)
+                if (mi.Name != name)
                 {
-                    methodInfo = methodInfo ?? new List<MethodInfo>();
-                    methodInfo.Add(mi);
+                    continue;
                 }
+
+                methods ??= new List<MethodInfo>();
+                methods.Add(mi);
             }
 
-            if (methodInfo == null || methodInfo.Count == 0)
+            if (methods == null || methods.Count == 0)
             {
-                return PropertyDescriptor.Undefined;
+                return ConstantValueAccessor.NullAccessor;
             }
 
-            return new PropertyDescriptor(new MethodInfoFunctionInstance(Engine, methodInfo.ToArray()), PropertyFlag.AllForbidden);
+            return new MethodAccessor(MethodDescriptor.Build(methods));
         }
 
         public object Target => ReferenceType;

+ 0 - 1
Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs

@@ -4,7 +4,6 @@ using Jint.Native;
 using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 
 namespace Jint.Runtime.Interpreter.Expressions
 {

+ 46 - 51
Jint/Runtime/TypeConverter.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Globalization;
-using System.Reflection;
 using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Jint.Native;
@@ -11,6 +10,7 @@ using Jint.Native.Object;
 using Jint.Native.String;
 using Jint.Native.Symbol;
 using Jint.Pooling;
+using Jint.Runtime.Interop;
 
 namespace Jint.Runtime
 {
@@ -111,37 +111,45 @@ namespace Jint.Runtime
             return OrdinaryToPrimitive(oi, preferredType == Types.None ? Types.Number :  preferredType);
         }
 
-        private static readonly JsString[] StringHintCallOrder = { (JsString) "toString", (JsString) "valueOf"};
-        private static readonly JsString[] NumberHintCallOrder = { (JsString) "valueOf", (JsString) "toString"};
-        
         /// <summary>
         /// https://tc39.es/ecma262/#sec-ordinarytoprimitive
         /// </summary>
         internal static JsValue OrdinaryToPrimitive(ObjectInstance input, Types hint = Types.None)
         {
-            var callOrder = Array.Empty<JsString>();
+            JsString property1;
+            JsString property2;
+            
             if (hint == Types.String)
             {
-                callOrder = StringHintCallOrder;
+                property1 = (JsString) "toString";
+                property2 = (JsString) "valueOf";
+            }
+            else if (hint == Types.Number)
+            {
+                property1 = (JsString) "valueOf";
+                property2 = (JsString) "toString";
+            }
+            else
+            {
+                return ExceptionHelper.ThrowTypeError<JsValue>(input.Engine);
             }
 
-            if (hint == Types.Number)
+            if (input.Get(property1) is ICallable method1)
             {
-                callOrder = NumberHintCallOrder;
+                var val = method1.Call(input, Arguments.Empty);
+                if (val.IsPrimitive())
+                {
+                    return val;
+                }
             }
 
-            foreach (var property in callOrder)
+            if (input.Get(property2) is ICallable method2)
             {
-                var method = input.Get(property) as ICallable;
-                if (method is object)
+                var val = method2.Call(input, Arguments.Empty);
+                if (val.IsPrimitive())
                 {
-                    var val = method.Call(input, Arguments.Empty);
-                    if (val.IsPrimitive())
-                    {
-                        return val;
-                    }
+                    return val;
                 }
- 
             }
 
             return ExceptionHelper.ThrowTypeError<JsValue>(input.Engine);
@@ -479,7 +487,7 @@ namespace Jint.Runtime
                 InternalTypes.Undefined => Undefined.Text,
                 InternalTypes.Null => Null.Text,
                 InternalTypes.Object when o is IPrimitiveInstance p => ToString(ToPrimitive(p.PrimitiveValue, Types.String)),
-                InternalTypes.Object when o is Interop.IObjectWrapper p => p.Target?.ToString(),
+                InternalTypes.Object when o is IObjectWrapper p => p.Target?.ToString(),
                 _ => ToString(ToPrimitive(o, Types.String))
             };
         }
@@ -533,57 +541,44 @@ namespace Jint.Runtime
             }
         }
 
-        public static IEnumerable<Tuple<MethodBase, JsValue[]>> FindBestMatch<T>(Engine engine, T[] methods, Func<T, bool, JsValue[]> argumentProvider) where T : MethodBase
+        internal static IEnumerable<Tuple<MethodDescriptor, JsValue[]>> FindBestMatch(
+            Engine engine,
+            MethodDescriptor[] methods,
+            Func<MethodDescriptor, JsValue[]> argumentProvider)
         {
-            List<Tuple<T, JsValue[]>> matchingByParameterCount = null;
+            List<Tuple<MethodDescriptor, JsValue[]>> matchingByParameterCount = null;
             foreach (var m in methods)
             {
-                bool hasParams = false;
-                var parameterInfos = m.GetParameters();
-                foreach (var parameter in parameterInfos)
-                {
-                    if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute)))
-                    {
-                        hasParams = true;
-                        break;
-                    }
-                }
-
-                var arguments = argumentProvider(m, hasParams);
+                var parameterInfos = m.Parameters;
+                var arguments = argumentProvider(m);
                 if (parameterInfos.Length == arguments.Length)
                 {
                     if (methods.Length == 0 && arguments.Length == 0)
                     {
-                        yield return new Tuple<MethodBase, JsValue[]>(m, arguments);
+                        yield return new Tuple<MethodDescriptor, JsValue[]>(m, arguments);
                         yield break;
                     }
 
-                    matchingByParameterCount ??= new List<Tuple<T, JsValue[]>>();
-                    matchingByParameterCount.Add(new Tuple<T, JsValue[]>(m, arguments));
+                    matchingByParameterCount ??= new List<Tuple<MethodDescriptor, JsValue[]>>();
+                    matchingByParameterCount.Add(new Tuple<MethodDescriptor, JsValue[]>(m, arguments));
                 }
                 else if (parameterInfos.Length > arguments.Length)
                 {
                     // check if we got enough default values to provide all parameters (or more in case some default values are provided/overwritten)
-                    var defaultValuesCount = 0;
-                    foreach (var param in parameterInfos)
-                    {
-                        if (param.HasDefaultValue) defaultValuesCount++;
-                    }
-
-                    if (parameterInfos.Length <= arguments.Length + defaultValuesCount)
+                    if (parameterInfos.Length <= arguments.Length + m.ParameterDefaultValuesCount)
                     {
                         // create missing arguments from default values
-
-                        var argsWithDefaults = new List<JsValue>(arguments);
+                        var argsWithDefaults = new JsValue[parameterInfos.Length];
+                        Array.Copy(arguments, argsWithDefaults, arguments.Length);
                         for (var i = arguments.Length; i < parameterInfos.Length; i++)
                         {
                             var param = parameterInfos[i];
                             var value = JsValue.FromObject(engine, param.DefaultValue);
-                            argsWithDefaults.Add(value);
+                            argsWithDefaults[i] = value;
                         }
 
-                        matchingByParameterCount = matchingByParameterCount ?? new List<Tuple<T, JsValue[]>>();
-                        matchingByParameterCount.Add(new Tuple<T, JsValue[]>(m, argsWithDefaults.ToArray()));
+                        matchingByParameterCount ??= new List<Tuple<MethodDescriptor, JsValue[]>>();
+                        matchingByParameterCount.Add(new Tuple<MethodDescriptor, JsValue[]>(m, argsWithDefaults));
                     }
                 }
             }
@@ -596,7 +591,7 @@ namespace Jint.Runtime
             foreach (var tuple in matchingByParameterCount)
             {
                 var perfectMatch = true;
-                var parameters = tuple.Item1.GetParameters();
+                var parameters = tuple.Item1.Parameters;
                 var arguments = tuple.Item2;
                 for (var i = 0; i < arguments.Length; i++)
                 {
@@ -619,7 +614,7 @@ namespace Jint.Runtime
 
                 if (perfectMatch)
                 {
-                    yield return new Tuple<MethodBase, JsValue[]>(tuple.Item1, arguments);
+                    yield return new Tuple<MethodDescriptor, JsValue[]>(tuple.Item1, arguments);
                     yield break;
                 }
             }
@@ -627,11 +622,11 @@ namespace Jint.Runtime
             for (var i = 0; i < matchingByParameterCount.Count; i++)
             {
                 var tuple = matchingByParameterCount[i];
-                yield return new Tuple<MethodBase, JsValue[]>(tuple.Item1, tuple.Item2);
+                yield return new Tuple<MethodDescriptor, JsValue[]>(tuple.Item1, tuple.Item2);
             }
         }
 
-        public static bool TypeIsNullable(Type type)
+        internal static bool TypeIsNullable(Type type)
         {
             return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
         }