Browse Source

Fix assigning to Nullable Enum (#715)

Gökhan Kurt 5 years ago
parent
commit
bf3cc2113d
2 changed files with 59 additions and 33 deletions
  1. 25 8
      Jint.Tests/Runtime/InteropTests.cs
  2. 34 25
      Jint/Runtime/Interop/DefaultTypeConverter.cs

+ 25 - 8
Jint.Tests/Runtime/InteropTests.cs

@@ -136,7 +136,7 @@ namespace Jint.Tests.Runtime
             var del = System.Linq.Expressions.Expression.Lambda(exp, parameters).Compile();
 
             _engine.SetValue("add", del);
-            
+
             RunTest(@"
                 assert(add(1,1) === 2);
             ");
@@ -631,11 +631,23 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        private struct TestStruct
+        {
+            public int Value;
+
+            public TestStruct(int value)
+            {
+                Value = value;
+            }
+        }
+
         private class TestClass
         {
             public int? NullableInt { get; set; }
             public DateTime? NullableDate { get; set; }
             public bool? NullableBool { get; set; }
+            public TestEnumInt32? NullableEnum { get; set; }
+            public TestStruct? NullableStruct { get; set; }
         }
 
         [Fact]
@@ -643,15 +655,20 @@ namespace Jint.Tests.Runtime
         {
             var instance = new TestClass();
             _engine.SetValue("instance", instance);
+            _engine.SetValue("TestStruct", typeof(TestStruct));
 
             RunTest(@"
                 instance.NullableInt = 2;
                 instance.NullableDate = new Date();
                 instance.NullableBool = true;
+                instance.NullableEnum = 1;
+                instance.NullableStruct = new TestStruct(5);
 
                 assert(instance.NullableInt===2);
                 assert(instance.NullableDate!=null);
                 assert(instance.NullableBool===true);
+                assert(instance.NullableEnum===1);
+                assert(instance.NullableStruct.Value===5);
             ");
         }
 
@@ -1484,7 +1501,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         public void ShouldImportNamespaceNestedType()
         {
-          RunTest(@"
+            RunTest(@"
                 var shapes = importNamespace('Shapes.Circle');
                 var kinds = shapes.Kind;
                 assert(kinds.Unit === 0);
@@ -1496,7 +1513,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         public void ShouldImportNamespaceNestedNestedType()
         {
-          RunTest(@"
+            RunTest(@"
                 var meta = importNamespace('Shapes.Circle.Meta');
                 var usages = meta.Usage;
                 assert(usages.Public === 0);
@@ -1842,7 +1859,7 @@ namespace Jint.Tests.Runtime
             Assert.Equal(engine.Invoke("throwException3").AsString(), exceptionMessage);
             Assert.Throws<ArgumentNullException>(() => engine.Invoke("throwException4"));
         }
-        
+
         [Fact]
         public void ArrayFromShouldConvertListToArrayLike()
         {
@@ -1867,7 +1884,7 @@ namespace Jint.Tests.Runtime
                 assert(arr[1].Name === 'Mika');
             ");
         }
-        
+
         [Fact]
         public void ArrayFromShouldConvertArrayToArrayLike()
         {
@@ -1892,7 +1909,7 @@ namespace Jint.Tests.Runtime
                 assert(arr[1].Name === 'Mika');
             ");
         }
-        
+
         [Fact]
         public void ArrayFromShouldConvertIEnumerable()
         {
@@ -1901,7 +1918,7 @@ namespace Jint.Tests.Runtime
                 new Person {Name = "Mike"},
                 new Person {Name = "Mika"}
             }.Select(x => x);
-            
+
             _engine.SetValue("a", enumerable);
 
             RunTest(@"
@@ -1928,6 +1945,6 @@ namespace Jint.Tests.Runtime
             engine.Execute("P.Name = 'b';");
             engine.Execute("P.Name += 'c';");
             Assert.Equal("bc", p.Name);
-        } 
+        }
     }
 }

+ 34 - 25
Jint/Runtime/Interop/DefaultTypeConverter.cs

@@ -20,9 +20,18 @@ namespace Jint.Runtime.Interop
         private static readonly ConcurrentDictionary<string, bool> _knownConversions = new ConcurrentDictionary<string, bool>();
 #endif
 
-        private static readonly MethodInfo convertChangeType = typeof(Convert).GetMethod("ChangeType", new [] { typeof(object), typeof(Type), typeof(IFormatProvider) });
-        private static readonly MethodInfo jsValueFromObject = typeof(JsValue).GetMethod(nameof(JsValue.FromObject));
-        private static readonly MethodInfo jsValueToObject = typeof(JsValue).GetMethod(nameof(JsValue.ToObject));
+        private static readonly Type nullableType = typeof(Nullable<>);
+        private static readonly Type intType = typeof(int);
+        private static readonly Type iCallableType = typeof(Func<JsValue, JsValue[], JsValue>);
+        private static readonly Type jsValueType = typeof(JsValue);
+        private static readonly Type objectType = typeof(object);
+        private static readonly Type engineType = typeof(Engine);
+        private static readonly Type typeType = typeof(Type);
+
+        private static readonly MethodInfo convertChangeType = typeof(Convert).GetMethod("ChangeType", new [] { objectType, typeType, typeof(IFormatProvider) });
+        private static readonly MethodInfo jsValueFromObject = jsValueType.GetMethod(nameof(JsValue.FromObject));
+        private static readonly MethodInfo jsValueToObject = jsValueType.GetMethod(nameof(JsValue.ToObject));
+
 
         public DefaultTypeConverter(Engine engine)
         {
@@ -47,9 +56,14 @@ namespace Jint.Runtime.Interop
                 return value;
             }
 
+            if (type.IsGenericType && type.GetGenericTypeDefinition() == nullableType)
+            {
+                type = Nullable.GetUnderlyingType(type);
+            }
+
             if (type.IsEnum)
             {
-                var integer = System.Convert.ChangeType(value, typeof(int), formatProvider);
+                var integer = System.Convert.ChangeType(value, intType, formatProvider);
                 if (integer == null)
                 {
                     ExceptionHelper.ThrowArgumentOutOfRangeException();
@@ -60,7 +74,7 @@ namespace Jint.Runtime.Interop
 
             var valueType = value.GetType();
             // is the javascript value an ICallable instance ?
-            if (valueType == typeof(Func<JsValue, JsValue[], JsValue>))
+            if (valueType == iCallableType)
             {
                 var function = (Func<JsValue, JsValue[], JsValue>)value;
 
@@ -84,20 +98,20 @@ namespace Jint.Runtime.Interop
                             var param = @params[i];
                             if (param.Type.IsValueType)
                             {
-                                var boxing = Expression.Convert(param, typeof(object));
-                                tmpVars[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, typeof(Engine)), boxing);
+                                var boxing = Expression.Convert(param, objectType);
+                                tmpVars[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), boxing);
                             }
                             else
                             {
-                                tmpVars[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, typeof(Engine)), param);
+                                tmpVars[i] = Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), param);
                             }
                         }
-                        var @vars = Expression.NewArrayInit(typeof(JsValue), tmpVars);
+                        var @vars = Expression.NewArrayInit(jsValueType, tmpVars);
 
                         var callExpresion = Expression.Block(Expression.Call(
                                                 Expression.Call(Expression.Constant(function.Target),
                                                     function.Method,
-                                                    Expression.Constant(JsValue.Undefined, typeof(JsValue)),
+                                                    Expression.Constant(JsValue.Undefined, jsValueType),
                                                     @vars),
                                                 jsValueToObject), Expression.Empty());
 
@@ -117,10 +131,10 @@ namespace Jint.Runtime.Interop
                         var initializers = new MethodCallExpression[@params.Length];
                         for (int i = 0; i < @params.Length; i++)
                         {
-                            var boxingExpression = Expression.Convert(@params[i], typeof(object));
-                            initializers[i]= Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, typeof(Engine)), boxingExpression);
+                            var boxingExpression = Expression.Convert(@params[i], objectType);
+                            initializers[i]= Expression.Call(null, jsValueFromObject, Expression.Constant(_engine, engineType), boxingExpression);
                         }
-                        var @vars = Expression.NewArrayInit(typeof(JsValue), initializers);
+                        var @vars = Expression.NewArrayInit(jsValueType, initializers);
 
                         // the final result's type needs to be changed before casting,
                         // for instance when a function returns a number (double) but C# expects an integer
@@ -131,10 +145,10 @@ namespace Jint.Runtime.Interop
                                                     Expression.Call(
                                                             Expression.Call(Expression.Constant(function.Target),
                                                                     function.Method,
-                                                                    Expression.Constant(JsValue.Undefined, typeof(JsValue)),
+                                                                    Expression.Constant(JsValue.Undefined, jsValueType),
                                                                     @vars),
                                                             jsValueToObject),
-                                                        Expression.Constant(returnType, typeof(Type)),
+                                                        Expression.Constant(returnType, typeType),
                                                         Expression.Constant(System.Globalization.CultureInfo.InvariantCulture, typeof(IFormatProvider))
                                                         ),
                                                     returnType);
@@ -156,24 +170,24 @@ namespace Jint.Runtime.Interop
                         var @params = new ParameterExpression[arguments.Length];
                         for (var i = 0; i < @params.Length; i++)
                         {
-                            @params[i] = Expression.Parameter(typeof(object), arguments[i].Name);
+                            @params[i] = Expression.Parameter(objectType, arguments[i].Name);
                         }
 
                         var initializers = new MethodCallExpression[@params.Length];
                         for (int i = 0; i < @params.Length; i++)
                         {
-                            initializers[i] = Expression.Call(null, typeof(JsValue).GetMethod("FromObject"), Expression.Constant(_engine, typeof(Engine)), @params[i]);
+                            initializers[i] = Expression.Call(null, jsValueType.GetMethod("FromObject"), Expression.Constant(_engine, engineType), @params[i]);
                         }
 
-                        var @vars = Expression.NewArrayInit(typeof(JsValue), initializers);
+                        var @vars = Expression.NewArrayInit(jsValueType, initializers);
 
                         var callExpression = Expression.Block(
                                                 Expression.Call(
                                                     Expression.Call(Expression.Constant(function.Target),
                                                         function.Method,
-                                                        Expression.Constant(JsValue.Undefined, typeof(JsValue)),
+                                                        Expression.Constant(JsValue.Undefined, jsValueType),
                                                         @vars),
-                                                    typeof(JsValue).GetMethod("ToObject")),
+                                                    jsValueType.GetMethod("ToObject")),
                                                 Expression.Empty());
 
                         var dynamicExpression = Expression.Invoke(Expression.Lambda(callExpression, new ReadOnlyCollection<ParameterExpression>(@params)), new ReadOnlyCollection<ParameterExpression>(@params));
@@ -203,11 +217,6 @@ namespace Jint.Runtime.Interop
                 return result;
             }
 
-            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
-            {
-                type = Nullable.GetUnderlyingType(type);
-            }
-
             if (value is ExpandoObject eObj)
             {
                 // public empty constructor required