Browse Source

More improvements for CLR Interop (#1616)

viruscamp 2 years ago
parent
commit
f4982ee8ff

+ 9 - 11
Jint.Repl/Program.cs

@@ -66,22 +66,20 @@ namespace Jint.Repl
                 try
                 {
                     var result = engine.Evaluate(input, parserOptions);
+                    JsValue str = result;
                     if (!result.IsPrimitive() && result is not IPrimitiveInstance)
                     {
-                        var str = serializer.Serialize(result, JsValue.Undefined, "  ");
-                        Console.WriteLine(str);
-                    }
-                    else
-                    {
-                        if (result.IsString())
-                        {
-                            Console.WriteLine(serializer.Serialize(result, JsValue.Undefined, JsValue.Undefined));
-                        }
-                        else
+                        str = serializer.Serialize(result, JsValue.Undefined, "  ");
+                        if (str == JsValue.Undefined)
                         {
-                            Console.WriteLine(result);
+                            str = result;
                         }
                     }
+                    else if (result.IsString())
+                    {
+                        str = serializer.Serialize(result, JsValue.Undefined, JsValue.Undefined);
+                    }
+                    Console.WriteLine(str);
                 }
                 catch (JavaScriptException je)
                 {

+ 242 - 137
Jint.Tests/Runtime/InteropExplicitTypeTests.cs

@@ -1,186 +1,291 @@
-using System.Reflection;
+namespace Jint.Tests.Runtime;
 
-namespace Jint.Tests.Runtime
+using Jint.Runtime.Interop;
+
+public class InteropExplicitTypeTests
 {
-    public class InteropExplicitTypeTests
+    public interface I1
+    {
+        string Name { get; }
+    }
+
+    public class Super
+    {
+        public string Name { get; } = "Super";
+    }
+
+    public class CI1 : Super, I1
     {
-        public interface I1
+        public new string Name { get; } = "CI1";
+
+        string I1.Name { get; } = "CI1 as I1";
+    }
+
+    public class Indexer<T>
+    {
+        private readonly T t;
+        public Indexer(T t)
         {
-            string Name { get; }
+            this.t = t;
         }
-
-        public class Super
+        public T this[int index]
         {
-            public string Name { get; } = "Super";
+            get { return t; }
         }
+    }
 
-        public class CI1 : Super, I1
+    public class InterfaceHolder
+    {
+        public InterfaceHolder()
         {
-            public new string Name { get; } = "CI1";
+            var ci1 = new CI1();
+            this.ci1 = ci1;
+            this.i1 = ci1;
+            this.super = ci1;
 
-            string I1.Name { get; } = "CI1 as I1";
+            this.IndexerCI1 = new Indexer<CI1>(ci1);
+            this.IndexerI1 = new Indexer<I1>(ci1);
+            this.IndexerSuper = new Indexer<Super>(ci1);
         }
 
-        public class Indexer<T>
-        {
-            private readonly T t;
-            public Indexer(T t)
-            {
-                this.t = t;
-            }
-            public T this[int index]
-            {
-                get { return t; }
-            }
-        }
+        public readonly CI1 ci1;
+        public readonly I1 i1;
+        public readonly Super super;
 
-        public class InterfaceHolder
-        {
-            public InterfaceHolder()
-            {
-                var ci1 = new CI1();
-                this.ci1 = ci1;
-                this.i1 = ci1;
-                this.super = ci1;
+        public CI1 CI1 { get => ci1; }
+        public I1 I1 { get => i1; }
+        public Super Super { get => super; }
 
-                this.IndexerCI1 = new Indexer<CI1>(ci1);
-                this.IndexerI1 = new Indexer<I1>(ci1);
-                this.IndexerSuper = new Indexer<Super>(ci1);
-            }
+        public CI1 GetCI1() => ci1;
+        public I1 GetI1() => i1;
+        public Super GetSuper() => super;
 
-            public readonly CI1 ci1;
-            public readonly I1 i1;
-            public readonly Super super;
+        public Indexer<CI1> IndexerCI1 { get; }
+        public Indexer<I1> IndexerI1 { get; }
+        public Indexer<Super> IndexerSuper { get; }
 
-            public CI1 CI1 { get => ci1; }
-            public I1 I1 { get => i1; }
-            public Super Super { get => super; }
+    }
 
-            public CI1 GetCI1() => ci1;
-            public I1 GetI1() => i1;
-            public Super GetSuper() => super;
+    private readonly Engine _engine;
+    private readonly InterfaceHolder holder;
 
-            public Indexer<CI1> IndexerCI1 { get; }
-            public Indexer<I1> IndexerI1 { get; }
-            public Indexer<Super> IndexerSuper { get; }
+    public InteropExplicitTypeTests()
+    {
+        holder = new InterfaceHolder();
+        _engine = new Engine(cfg => cfg.AllowClr(
+                    typeof(CI1).Assembly,
+                    typeof(Console).Assembly,
+                    typeof(File).Assembly))
+                .SetValue("log", new Action<object>(Console.WriteLine))
+                .SetValue("assert", new Action<bool>(Assert.True))
+                .SetValue("equal", new Action<object, object>(Assert.Equal))
+                .SetValue("holder", holder)
+        ;
+    }
+    [Fact]
+    public void EqualTest()
+    {
+        Assert.Equal(_engine.Evaluate("holder.I1"), _engine.Evaluate("holder.i1"));
+        Assert.NotEqual(_engine.Evaluate("holder.I1"), _engine.Evaluate("holder.ci1"));
 
-        }
+        Assert.Equal(_engine.Evaluate("holder.Super"), _engine.Evaluate("holder.super"));
+        Assert.NotEqual(_engine.Evaluate("holder.Super"), _engine.Evaluate("holder.ci1"));
+    }
 
-        private readonly Engine _engine;
-        private readonly InterfaceHolder holder;
+    [Fact]
+    public void ExplicitInterfaceFromField()
+    {
+        Assert.Equal(holder.i1.Name, _engine.Evaluate("holder.i1.Name"));
+        Assert.NotEqual(holder.i1.Name, _engine.Evaluate("holder.ci1.Name"));
+    }
 
-        public InteropExplicitTypeTests()
-        {
-            holder = new InterfaceHolder();
-            _engine = new Engine(cfg => cfg.AllowClr(
-                        typeof(Console).GetTypeInfo().Assembly,
-                        typeof(File).GetTypeInfo().Assembly))
-                    .SetValue("log", new Action<object>(Console.WriteLine))
-                    .SetValue("assert", new Action<bool>(Assert.True))
-                    .SetValue("equal", new Action<object, object>(Assert.Equal))
-                    .SetValue("holder", holder)
-            ;
-        }
-        [Fact]
-        public void EqualTest()
-        {
-            Assert.Equal(_engine.Evaluate("holder.I1"), _engine.Evaluate("holder.i1"));
-            Assert.NotEqual(_engine.Evaluate("holder.I1"), _engine.Evaluate("holder.ci1"));
+    [Fact]
+    public void ExplicitInterfaceFromProperty()
+    {
+        Assert.Equal(holder.I1.Name, _engine.Evaluate("holder.I1.Name"));
+        Assert.NotEqual(holder.I1.Name, _engine.Evaluate("holder.CI1.Name"));
+    }
 
-            Assert.Equal(_engine.Evaluate("holder.Super"), _engine.Evaluate("holder.super"));
-            Assert.NotEqual(_engine.Evaluate("holder.Super"), _engine.Evaluate("holder.ci1"));
-        }
+    [Fact]
+    public void ExplicitInterfaceFromMethod()
+    {
+        Assert.Equal(holder.GetI1().Name, _engine.Evaluate("holder.GetI1().Name"));
+        Assert.NotEqual(holder.GetI1().Name, _engine.Evaluate("holder.GetCI1().Name"));
+    }
 
-        [Fact]
-        public void ExplicitInterfaceFromField()
-        {
-            Assert.Equal(holder.i1.Name, _engine.Evaluate("holder.i1.Name"));
-            Assert.NotEqual(holder.i1.Name, _engine.Evaluate("holder.ci1.Name"));
-        }
+    [Fact]
+    public void ExplicitInterfaceFromIndexer()
+    {
+        Assert.Equal(holder.IndexerI1[0].Name, _engine.Evaluate("holder.IndexerI1[0].Name"));
+    }
 
-        [Fact]
-        public void ExplicitInterfaceFromProperty()
-        {
-            Assert.Equal(holder.I1.Name, _engine.Evaluate("holder.I1.Name"));
-            Assert.NotEqual(holder.I1.Name, _engine.Evaluate("holder.CI1.Name"));
-        }
 
-        [Fact]
-        public void ExplicitInterfaceFromMethod()
-        {
-            Assert.Equal(holder.GetI1().Name, _engine.Evaluate("holder.GetI1().Name"));
-            Assert.NotEqual(holder.GetI1().Name, _engine.Evaluate("holder.GetCI1().Name"));
-        }
+    [Fact]
+    public void SuperClassFromField()
+    {
+        Assert.Equal(holder.super.Name, _engine.Evaluate("holder.super.Name"));
+        Assert.NotEqual(holder.super.Name, _engine.Evaluate("holder.ci1.Name"));
+    }
 
-        [Fact]
-        public void ExplicitInterfaceFromIndexer()
-        {
-            Assert.Equal(holder.IndexerI1[0].Name, _engine.Evaluate("holder.IndexerI1[0].Name"));
-        }
+    [Fact]
+    public void SuperClassFromProperty()
+    {
+        Assert.Equal(holder.Super.Name, _engine.Evaluate("holder.Super.Name"));
+        Assert.NotEqual(holder.Super.Name, _engine.Evaluate("holder.CI1.Name"));
+    }
 
+    [Fact]
+    public void SuperClassFromMethod()
+    {
+        Assert.Equal(holder.GetSuper().Name, _engine.Evaluate("holder.GetSuper().Name"));
+        Assert.NotEqual(holder.GetSuper().Name, _engine.Evaluate("holder.GetCI1().Name"));
+    }
 
-        [Fact]
-        public void SuperClassFromField()
-        {
-            Assert.Equal(holder.super.Name, _engine.Evaluate("holder.super.Name"));
-            Assert.NotEqual(holder.super.Name, _engine.Evaluate("holder.ci1.Name"));
-        }
+    [Fact]
+    public void SuperClassFromIndexer()
+    {
+        Assert.Equal(holder.IndexerSuper[0].Name, _engine.Evaluate("holder.IndexerSuper[0].Name"));
+    }
 
-        [Fact]
-        public void SuperClassFromProperty()
+    public struct NullabeStruct : I1
+    {
+        public NullabeStruct()
         {
-            Assert.Equal(holder.Super.Name, _engine.Evaluate("holder.Super.Name"));
-            Assert.NotEqual(holder.Super.Name, _engine.Evaluate("holder.CI1.Name"));
         }
+        public string name = "NullabeStruct";
+
+        public string Name { get => name; }
+
+        string I1.Name { get => "NullabeStruct as I1"; }
+    }
+
+    public class NullableHolder
+    {
+        public I1 I1 { get; set; }
+        public NullabeStruct? NullabeStruct { get; set; }
+    }
+
+    [Fact]
+    public void TypedObjectWrapperForNullableType()
+    {
+        var nullableHolder = new NullableHolder();
+        _engine.SetValue("nullableHolder", nullableHolder);
+        _engine.SetValue("nullabeStruct", new NullabeStruct());
+
+        Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct"), Native.JsValue.Null);
+        _engine.Evaluate("nullableHolder.NullabeStruct = nullabeStruct");
+        Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct.Name"), nullableHolder.NullabeStruct?.Name);
+    }
+
+    [Fact]
+    public void ClrHelperUnwrap()
+    {
+        Assert.NotEqual(holder.CI1.Name, _engine.Evaluate("holder.I1.Name"));
+        Assert.Equal(holder.CI1.Name, _engine.Evaluate("clrHelper.unwrap(holder.I1).Name"));
+    }
+
+    [Fact]
+    public void ClrHelperWrap()
+    {
+        _engine.Execute("Jint = importNamespace('Jint');");
+        Assert.NotEqual(holder.I1.Name, _engine.Evaluate("holder.CI1.Name"));
+        Assert.Equal(holder.I1.Name, _engine.Evaluate("clrHelper.wrap(holder.CI1, Jint.Tests.Runtime.InteropExplicitTypeTests.I1).Name"));
+    }
 
-        [Fact]
-        public void SuperClassFromMethod()
+    [Fact]
+    public void ClrHelperTypeOf()
+    {
+        Action<Engine> runner = engine =>
         {
-            Assert.Equal(holder.GetSuper().Name, _engine.Evaluate("holder.GetSuper().Name"));
-            Assert.NotEqual(holder.GetSuper().Name, _engine.Evaluate("holder.GetCI1().Name"));
-        }
+            engine.SetValue("clrobj", new object());
+            Assert.Equal(engine.Evaluate("System.Object"), engine.Evaluate("clrHelper.typeOf(clrobj)"));
+        };
 
-        [Fact]
-        public void SuperClassFromIndexer()
+        runner.Invoke(new Engine(cfg =>
         {
-            Assert.Equal(holder.IndexerSuper[0].Name, _engine.Evaluate("holder.IndexerSuper[0].Name"));
-        }
+            cfg.AllowClr();
+            cfg.Interop.AllowGetType = true;
+        }));
 
-        public struct NullabeStruct: I1
+        var ex = Assert.Throws<InvalidOperationException>(() =>
         {
-            public NullabeStruct()
+            runner.Invoke(new Engine(cfg =>
             {
-            }
-            public string name = "NullabeStruct";
+                cfg.AllowClr();
+            }));
+        });
+        Assert.Equal("Invalid when Engine.Options.Interop.AllowGetType == false", ex.Message);
+    }
+
+    [Fact]
+    public void ClrHelperTypeOfForNestedType()
+    {
+        var engine = new Engine(cfg =>
+        {
+            cfg.AllowClr(GetType().Assembly);
+            cfg.Interop.AllowGetType = true;
+        });
 
-            public string Name { get => name; }
+        engine.SetValue("holder", holder);
+        engine.Execute("Jint = importNamespace('Jint');");
+        Assert.Equal(engine.Evaluate("Jint.Tests.Runtime.InteropExplicitTypeTests.CI1"), engine.Evaluate("clrHelper.typeOf(holder.CI1)"));
+        Assert.Equal(engine.Evaluate("Jint.Tests.Runtime.InteropExplicitTypeTests.I1"), engine.Evaluate("clrHelper.typeOf(holder.I1)"));
+    }
 
-            string I1.Name { get => "NullabeStruct as I1"; }
-        }
+    public class TypeHolder
+    {
+        public static Type Type => typeof(TypeHolder);
+    }
+
+    [Fact]
+    public void ClrHelperTypeToObject()
+    {
+        Action<Engine> runner = engine =>
+        {
+            engine.SetValue("TypeHolder", typeof(TypeHolder));
+            Assert.True(engine.Evaluate("TypeHolder") is TypeReference);
+            Assert.True(engine.Evaluate("clrHelper.typeToObject(TypeHolder)") is ObjectWrapper);
+        };
 
-        public class NullableHolder
+        runner.Invoke(new Engine(cfg =>
         {
-            public I1? I1 { get; set; }
-            public NullabeStruct? NullabeStruct { get; set; }
-        }
+            cfg.AllowClr();
+            cfg.Interop.AllowGetType = true;
+        }));
 
-        [Fact]
-        public void TestNullable()
+        var ex = Assert.Throws<InvalidOperationException>(() =>
         {
-            var nullableHolder = new NullableHolder();
-            _engine.SetValue("nullableHolder", nullableHolder);
-            _engine.SetValue("nullabeStruct", new NullabeStruct());
+            runner.Invoke(new Engine(cfg =>
+            {
+                cfg.AllowClr();
+            }));
+        });
+        Assert.Equal("Invalid when Engine.Options.Interop.AllowGetType == false", ex.Message);
+    }
 
-            Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct"), Native.JsValue.Null);
-            _engine.Evaluate("nullableHolder.NullabeStruct = nullabeStruct");
-            Assert.Equal(_engine.Evaluate("nullableHolder.NullabeStruct.Name"), nullableHolder.NullabeStruct?.Name);
-        }
+    [Fact]
+    public void ClrHelperObjectToType()
+    {
+        Action<Engine> runner = engine =>
+        {
+            engine.SetValue("TypeHolder", typeof(TypeHolder));
+            Assert.True(engine.Evaluate("TypeHolder.Type") is ObjectWrapper);
+            Assert.True(engine.Evaluate("clrHelper.objectToType(TypeHolder.Type)") is TypeReference);
+        };
 
-        [Fact]
-        public void TestUnwrapClr()
+        runner.Invoke(new Engine(cfg =>
         {
-            Assert.NotEqual(holder.CI1.Name, _engine.Evaluate("holder.I1.Name"));
-            Assert.Equal(holder.CI1.Name, _engine.Evaluate("unwrapClr(holder.I1).Name"));
-        }
+            cfg.AllowClr();
+            cfg.Interop.AllowGetType = true;
+        }));
+
+        var ex = Assert.Throws<InvalidOperationException>(() =>
+        {
+            runner.Invoke(new Engine(cfg =>
+            {
+                cfg.AllowClr();
+            }));
+        });
+        Assert.Equal("Invalid when Engine.Options.Interop.AllowGetType == false", ex.Message);
     }
 }

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

@@ -1893,6 +1893,18 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        [Fact]
+        public void ShouldGetNestedTypeFromParentType()
+        {
+            RunTest(@"
+                var Shapes = importNamespace('Shapes');
+                var usages = Shapes.Circle.Meta.Usage;
+                assert(usages.Public === 0);
+                assert(usages.Private === 1);
+                assert(usages.Internal === 11);
+            ");
+        }
+
         [Fact]
         public void ShouldGetNestedNestedProp()
         {

+ 4 - 19
Jint/Options.cs

@@ -120,21 +120,8 @@ namespace Jint
                         (thisObj, arguments) =>
                             new NamespaceReference(engine, TypeConverter.ToString(arguments.At(0)))),
                     PropertyFlag.AllForbidden));
-                engine.Realm.GlobalObject.SetProperty("unwrapClr", new PropertyDescriptor(new ClrFunctionInstance(
-                    engine,
-                    "unwrapClr",
-                    (thisObj, arguments) =>
-                    {
-                        var arg = arguments.At(0);
-                        if (arg is ObjectWrapper obj)
-                        {
-                            return new ObjectWrapper(engine, obj.Target);
-                        }
-                        else
-                        {
-                            return arg;
-                        }
-                    }),
+                engine.Realm.GlobalObject.SetProperty("clrHelper", new PropertyDescriptor(
+                    new ObjectWrapper(engine, new ClrHelper(Interop)),
                     PropertyFlag.AllForbidden));
             }
 
@@ -183,12 +170,10 @@ namespace Jint
 
             foreach (var overloads in methods.GroupBy(x => x.Name))
             {
+                string name = overloads.Key;
                 PropertyDescriptor CreateMethodInstancePropertyDescriptor(ClrFunctionInstance? function)
                 {
-                    var instance = function is null
-                        ? new MethodInfoFunctionInstance(engine, MethodDescriptor.Build(overloads.ToList()))
-                        : new MethodInfoFunctionInstance(engine, MethodDescriptor.Build(overloads.ToList()), function);
-
+                    var instance = new MethodInfoFunctionInstance(engine, objectType, name, MethodDescriptor.Build(overloads.ToList()), function);
                     return new PropertyDescriptor(instance, PropertyFlag.AllForbidden);
                 }
 

+ 1 - 5
Jint/Runtime/Interop/ClrFunctionInstance.cs

@@ -10,7 +10,6 @@ namespace Jint.Runtime.Interop
     /// </summary>
     public sealed class ClrFunctionInstance : FunctionInstance, IEquatable<ClrFunctionInstance>
     {
-        private readonly string? _name;
         internal readonly Func<JsValue, JsValue[], JsValue> _func;
 
         public ClrFunctionInstance(
@@ -19,9 +18,8 @@ namespace Jint.Runtime.Interop
             Func<JsValue, JsValue[], JsValue> func,
             int length = 0,
             PropertyFlag lengthFlags = PropertyFlag.AllForbidden)
-            : base(engine, engine.Realm, name != null ? new JsString(name) : null)
+            : base(engine, engine.Realm, new JsString(name))
         {
-            _name = name;
             _func = func;
 
             _prototype = engine._originalIntrinsics.Function.PrototypeObject;
@@ -76,7 +74,5 @@ namespace Jint.Runtime.Interop
 
             return false;
         }
-
-        public override string ToString() => "function " + _name + "() { [native code] }";
     }
 }

+ 85 - 0
Jint/Runtime/Interop/ClrHelper.cs

@@ -0,0 +1,85 @@
+namespace Jint.Runtime.Interop;
+
+using Jint.Native;
+
+public class ClrHelper
+{
+    private readonly InteropOptions _interopOptions;
+
+    internal ClrHelper(InteropOptions interopOptions)
+    {
+        _interopOptions = interopOptions;
+    }
+
+    /// <summary>
+    /// Call JsValue.ToString(), mainly for NamespaceReference.
+    /// </summary>
+    public JsValue ToString(JsValue value)
+    {
+        return value.ToString();
+    }
+
+    /// <summary>
+    /// Cast `obj as ISomeInterface` to `obj`
+    /// </summary>
+    public JsValue Unwrap(ObjectWrapper obj)
+    {
+        return new ObjectWrapper(obj.Engine, obj.Target);
+    }
+
+    /// <summary>
+    /// Cast `obj` to `obj as ISomeInterface`
+    /// </summary>
+    public JsValue Wrap(ObjectWrapper obj, TypeReference type)
+    {
+        if (!type.ReferenceType.IsInstanceOfType(obj.Target))
+        {
+            ExceptionHelper.ThrowTypeError(type.Engine.Realm, "Argument obj must be an instance of type");
+        }
+        return new ObjectWrapper(obj.Engine, obj.Target, type.ReferenceType);
+    }
+
+    /// <summary>
+    /// Get `TypeReference(ISomeInterface)` from `obj as ISomeInterface`
+    /// </summary>
+    public JsValue TypeOf(ObjectWrapper obj)
+    {
+        MustAllowGetType();
+        return TypeReference.CreateTypeReference(obj.Engine, obj.ClrType);
+    }
+
+    /// <summary>
+    /// Cast `TypeReference(SomeClass)` to `ObjectWrapper(SomeClass)`
+    /// </summary>
+    public JsValue TypeToObject(TypeReference type)
+    {
+        MustAllowGetType();
+        var engine = type.Engine;
+        return engine.Options.Interop.WrapObjectHandler.Invoke(engine, type.ReferenceType, null) ?? JsValue.Undefined;
+    }
+
+    /// <summary>
+    /// Cast `ObjectWrapper(SomeClass)` to `TypeReference(SomeClass)`
+    /// </summary>
+    public JsValue ObjectToType(ObjectWrapper obj)
+    {
+        MustAllowGetType();
+        if (obj.Target is Type t)
+        {
+            return TypeReference.CreateTypeReference(obj.Engine, t);
+        }
+        else
+        {
+            ExceptionHelper.ThrowArgumentException("Must be an ObjectWrapper of Type", "obj");
+        }
+        return JsValue.Undefined;
+    }
+
+    private void MustAllowGetType()
+    {
+        if (!_interopOptions.AllowGetType)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Invalid when Engine.Options.Interop.AllowGetType == false");
+        }
+    }
+}

+ 1 - 1
Jint/Runtime/Interop/DefaultObjectConverter.cs

@@ -37,7 +37,7 @@ namespace Jint
         public static bool TryConvert(Engine engine, object value, Type? type, [NotNullWhen(true)] out JsValue? result)
         {
             result = null;
-            Type valueType = ObjectWrapper.ClrType(value, type);
+            Type valueType = ObjectWrapper.GetClrType(value, type);
 
             var typeMappers = _typeMappers;
 

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

@@ -9,21 +9,24 @@ namespace Jint.Runtime.Interop
 {
     internal sealed class MethodInfoFunctionInstance : FunctionInstance
     {
-        private static readonly JsString _name = new JsString("Function");
+        private readonly Type _targetType;
+        private readonly string _name;
         private readonly MethodDescriptor[] _methods;
         private readonly ClrFunctionInstance? _fallbackClrFunctionInstance;
 
-        public MethodInfoFunctionInstance(Engine engine, MethodDescriptor[] methods)
-            : base(engine, engine.Realm, _name)
+        public MethodInfoFunctionInstance(
+            Engine engine,
+            Type targetType,
+            string name,
+            MethodDescriptor[] methods,
+            ClrFunctionInstance? fallbackClrFunctionInstance = null)
+            : base(engine, engine.Realm, new JsString(name))
         {
+            _targetType = targetType;
+            _name = name;
             _methods = methods;
-            _prototype = engine.Realm.Intrinsics.Function.PrototypeObject;
-        }
-
-        public MethodInfoFunctionInstance(Engine engine, MethodDescriptor[] methods, ClrFunctionInstance fallbackClrFunctionInstance)
-            : this(engine, methods)
-        {
             _fallbackClrFunctionInstance = fallbackClrFunctionInstance;
+            _prototype = engine.Realm.Intrinsics.Function.PrototypeObject;
         }
 
         private static bool IsGenericParameter(object argObj, Type parameterType)
@@ -285,5 +288,10 @@ namespace Jint.Runtime.Interop
             newArgumentsCollection[nonParamsArgumentsCount] = jsArray;
             return newArgumentsCollection;
         }
+
+        public override string ToString()
+        {
+            return $"function {_targetType}.{_name}() {{ [native code] }}";
+        }
     }
 }

+ 2 - 2
Jint/Runtime/Interop/NamespaceReference.cs

@@ -94,7 +94,7 @@ namespace Jint.Runtime.Interop
             // and only then in mscorlib. Probelm usage: System.IO.File.CreateText
 
             // search in loaded assemblies
-            var lookupAssemblies = new[] {Assembly.GetCallingAssembly(), Assembly.GetExecutingAssembly()};
+            var lookupAssemblies = new[] { Assembly.GetCallingAssembly(), Assembly.GetExecutingAssembly() };
 
             foreach (var assembly in lookupAssemblies)
             {
@@ -192,7 +192,7 @@ namespace Jint.Runtime.Interop
 
         public override string ToString()
         {
-            return "[Namespace: " + _path + "]";
+            return "[CLR namespace: " + _path + "]";
         }
     }
 }

+ 11 - 11
Jint/Runtime/Interop/ObjectWrapper.cs

@@ -17,14 +17,13 @@ namespace Jint.Runtime.Interop
     public sealed class ObjectWrapper : ObjectInstance, IObjectWrapper, IEquatable<ObjectWrapper>
     {
         private readonly TypeDescriptor _typeDescriptor;
-        private readonly Type _clrType;
 
         public ObjectWrapper(Engine engine, object obj, Type? type = null)
             : base(engine)
         {
             Target = obj;
-            _clrType = ClrType(obj, type);
-            _typeDescriptor = TypeDescriptor.Get(_clrType);
+            ClrType = GetClrType(obj, type);
+            _typeDescriptor = TypeDescriptor.Get(ClrType);
             if (_typeDescriptor.LengthProperty is not null)
             {
                 // create a forwarder to produce length from Count or Length if one of them is present
@@ -35,6 +34,7 @@ namespace Jint.Runtime.Interop
         }
 
         public object Target { get; }
+        internal Type ClrType { get; }
 
         public override bool IsArrayLike => _typeDescriptor.IsArrayLike;
 
@@ -51,7 +51,7 @@ namespace Jint.Runtime.Interop
                 if (_properties is null || !_properties.ContainsKey(member))
                 {
                     // can try utilize fast path
-                    var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, _clrType, member, forWrite: true);
+                    var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, ClrType, member, forWrite: true);
 
                     if (ReferenceEquals(accessor, ConstantValueAccessor.NullAccessor))
                     {
@@ -163,7 +163,7 @@ namespace Jint.Runtime.Interop
             else if (includeStrings)
             {
                 // we take public properties and fields
-                var type = _clrType;
+                var type = ClrType;
                 foreach (var p in type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public))
                 {
                     var indexParameters = p.GetIndexParameters();
@@ -235,7 +235,7 @@ namespace Jint.Runtime.Interop
                 return new PropertyDescriptor(result, PropertyFlag.OnlyEnumerable);
             }
 
-            var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, _clrType, member);
+            var accessor = _engine.Options.Interop.TypeResolver.GetAccessor(_engine, ClrType, member);
             var descriptor = accessor.CreatePropertyDescriptor(_engine, Target, enumerable: !isDictionary);
             if (!isDictionary && !ReferenceEquals(descriptor, PropertyDescriptor.Undefined))
             {
@@ -254,7 +254,7 @@ namespace Jint.Runtime.Interop
                 return member switch
                 {
                     PropertyInfo pi => new PropertyAccessor(pi.Name, pi),
-                    MethodBase mb => new MethodAccessor(MethodDescriptor.Build(new[] {mb})),
+                    MethodBase mb => new MethodAccessor(target.GetType(), member.Name, MethodDescriptor.Build(new[] { mb })),
                     FieldInfo fi => new FieldAccessor(fi),
                     _ => null
                 };
@@ -262,7 +262,7 @@ namespace Jint.Runtime.Interop
             return engine.Options.Interop.TypeResolver.GetAccessor(engine, target.GetType(), member.Name, Factory).CreatePropertyDescriptor(engine, target);
         }
 
-        public static Type ClrType(object obj, Type? type)
+        internal static Type GetClrType(object obj, Type? type)
         {
             if (type is null || type == typeof(object))
             {
@@ -319,14 +319,14 @@ namespace Jint.Runtime.Interop
                 return true;
             }
 
-            return Equals(Target, other.Target) && Equals(_clrType, other._clrType);
+            return Equals(Target, other.Target) && Equals(ClrType, other.ClrType);
         }
 
         public override int GetHashCode()
         {
             var hashCode = -1468639730;
             hashCode = hashCode * -1521134295 + Target.GetHashCode();
-            hashCode = hashCode * -1521134295 + _clrType.GetHashCode();
+            hashCode = hashCode * -1521134295 + ClrType.GetHashCode();
             return hashCode;
         }
 
@@ -368,7 +368,7 @@ namespace Jint.Runtime.Interop
 
             public override void Close(CompletionType completion)
             {
-               (_enumerator as IDisposable)?.Dispose();
+                (_enumerator as IDisposable)?.Dispose();
                 base.Close(completion);
             }
 

+ 7 - 2
Jint/Runtime/Interop/Reflection/MethodAccessor.cs

@@ -4,10 +4,15 @@ namespace Jint.Runtime.Interop.Reflection
 {
     internal sealed class MethodAccessor : ReflectionAccessor
     {
+        private readonly Type _targetType;
+        private readonly string _name;
         private readonly MethodDescriptor[] _methods;
 
-        public MethodAccessor(MethodDescriptor[] methods) : base(null!, null!)
+        public MethodAccessor(Type targetType, string name, MethodDescriptor[] methods)
+            : base(null!, name)
         {
+            _targetType = targetType;
+            _name = name;
             _methods = methods;
         }
 
@@ -24,7 +29,7 @@ namespace Jint.Runtime.Interop.Reflection
 
         public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true)
         {
-            return new(new MethodInfoFunctionInstance(engine, _methods), PropertyFlag.AllForbidden);
+            return new(new MethodInfoFunctionInstance(engine, _targetType, _name, _methods), PropertyFlag.AllForbidden);
         }
     }
 }

+ 22 - 0
Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs

@@ -0,0 +1,22 @@
+namespace Jint.Runtime.Interop.Reflection;
+
+internal sealed class NestedTypeAccessor : ReflectionAccessor
+{
+    private readonly TypeReference _typeReference;
+
+    public NestedTypeAccessor(TypeReference typeReference, string name) : base(typeof(Type), name)
+    {
+        _typeReference = typeReference;
+    }
+
+    public override bool Writable => false;
+
+    protected override object? DoGetValue(object target)
+    {
+        return _typeReference;
+    }
+
+    protected override void DoSetValue(object target, object? value)
+    {
+    }
+}

+ 1 - 1
Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs

@@ -12,7 +12,7 @@ namespace Jint.Runtime.Interop.Reflection
     /// </summary>
     internal abstract class ReflectionAccessor
     {
-        private readonly Type _memberType;
+        protected readonly Type _memberType;
         private readonly object? _memberName;
         private readonly PropertyInfo? _indexer;
 

+ 5 - 0
Jint/Runtime/Interop/TypeReference.cs

@@ -346,5 +346,10 @@ namespace Jint.Runtime.Interop
 
             return derivedType != null && baseType != null && (derivedType == baseType || derivedType.IsSubclassOf(baseType));
         }
+
+        public override string ToString()
+        {
+            return "[CLR type: " + ReferenceType + "]";
+        }
     }
 }

+ 12 - 3
Jint/Runtime/Interop/TypeResolver.cs

@@ -160,7 +160,7 @@ namespace Jint.Runtime.Interop
 
             if (explicitMethods?.Count > 0)
             {
-                return new MethodAccessor(MethodDescriptor.Build(explicitMethods));
+                return new MethodAccessor(type, memberName, MethodDescriptor.Build(explicitMethods));
             }
 
             // try to find explicit indexer implementations
@@ -193,7 +193,7 @@ namespace Jint.Runtime.Interop
 
                 if (matches.Count > 0)
                 {
-                    return new MethodAccessor(MethodDescriptor.Build(matches));
+                    return new MethodAccessor(type, memberName, MethodDescriptor.Build(matches));
                 }
             }
 
@@ -299,7 +299,16 @@ namespace Jint.Runtime.Interop
 
             if (methods?.Count > 0)
             {
-                accessor = new MethodAccessor(MethodDescriptor.Build(methods));
+                accessor = new MethodAccessor(type, memberName, MethodDescriptor.Build(methods));
+                return true;
+            }
+
+            // look for nested type
+            var nestedType = type.GetNestedType(memberName, bindingFlags);
+            if (nestedType != null)
+            {
+                var typeReference = TypeReference.CreateTypeReference(engine, nestedType);
+                accessor = new NestedTypeAccessor(typeReference, memberName);
                 return true;
             }