From f4982ee8ff37c5004776e60867299d8aefc949e5 Mon Sep 17 00:00:00 2001 From: viruscamp Date: Wed, 9 Aug 2023 22:45:35 +0800 Subject: [PATCH] More improvements for CLR Interop (#1616) --- Jint.Repl/Program.cs | 20 +- .../Runtime/InteropExplicitTypeTests.cs | 379 +++++++++++------- Jint.Tests/Runtime/InteropTests.cs | 12 + Jint/Options.cs | 23 +- Jint/Runtime/Interop/ClrFunctionInstance.cs | 6 +- Jint/Runtime/Interop/ClrHelper.cs | 85 ++++ .../Runtime/Interop/DefaultObjectConverter.cs | 2 +- .../Interop/MethodInfoFunctionInstance.cs | 26 +- Jint/Runtime/Interop/NamespaceReference.cs | 4 +- Jint/Runtime/Interop/ObjectWrapper.cs | 22 +- .../Interop/Reflection/MethodAccessor.cs | 9 +- .../Interop/Reflection/NestedTypeAccessor.cs | 22 + .../Interop/Reflection/ReflectionAccessor.cs | 2 +- Jint/Runtime/Interop/TypeReference.cs | 5 + Jint/Runtime/Interop/TypeResolver.cs | 15 +- 15 files changed, 431 insertions(+), 201 deletions(-) create mode 100644 Jint/Runtime/Interop/ClrHelper.cs create mode 100644 Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs diff --git a/Jint.Repl/Program.cs b/Jint.Repl/Program.cs index d906454cec..d52bdfbd41 100644 --- a/Jint.Repl/Program.cs +++ b/Jint.Repl/Program.cs @@ -66,22 +66,20 @@ private static void Main(string[] args) 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) { diff --git a/Jint.Tests/Runtime/InteropExplicitTypeTests.cs b/Jint.Tests/Runtime/InteropExplicitTypeTests.cs index 9aeaea3494..859294dec1 100644 --- a/Jint.Tests/Runtime/InteropExplicitTypeTests.cs +++ b/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 + { + 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); + this.IndexerI1 = new Indexer(ci1); + this.IndexerSuper = new Indexer(ci1); } - public class Indexer - { - 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); - this.IndexerI1 = new Indexer(ci1); - this.IndexerSuper = new Indexer(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 IndexerCI1 { get; } + public Indexer IndexerI1 { get; } + public Indexer 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 IndexerCI1 { get; } - public Indexer IndexerI1 { get; } - public Indexer 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(Console.WriteLine)) + .SetValue("assert", new Action(Assert.True)) + .SetValue("equal", new Action(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(Console.WriteLine)) - .SetValue("assert", new Action(Assert.True)) - .SetValue("equal", new Action(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 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(() => { - 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 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(() => { - 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 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(() => + { + runner.Invoke(new Engine(cfg => + { + cfg.AllowClr(); + })); + }); + Assert.Equal("Invalid when Engine.Options.Interop.AllowGetType == false", ex.Message); } } diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index 07f345a3c5..d8b0ac5368 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -1893,6 +1893,18 @@ public void ShouldImportNamespaceNestedNestedType() "); } + [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() { diff --git a/Jint/Options.cs b/Jint/Options.cs index 63f57f2769..4224613a3a 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -120,21 +120,8 @@ internal void Apply(Engine engine) (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 @@ private static void AttachExtensionMethodsToPrototype(Engine engine, ObjectInsta 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); } diff --git a/Jint/Runtime/Interop/ClrFunctionInstance.cs b/Jint/Runtime/Interop/ClrFunctionInstance.cs index 7440ab474f..d777dc5a05 100644 --- a/Jint/Runtime/Interop/ClrFunctionInstance.cs +++ b/Jint/Runtime/Interop/ClrFunctionInstance.cs @@ -10,7 +10,6 @@ namespace Jint.Runtime.Interop /// public sealed class ClrFunctionInstance : FunctionInstance, IEquatable { - private readonly string? _name; internal readonly Func _func; public ClrFunctionInstance( @@ -19,9 +18,8 @@ public ClrFunctionInstance( Func 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 @@ public bool Equals(ClrFunctionInstance? other) return false; } - - public override string ToString() => "function " + _name + "() { [native code] }"; } } diff --git a/Jint/Runtime/Interop/ClrHelper.cs b/Jint/Runtime/Interop/ClrHelper.cs new file mode 100644 index 0000000000..e11763216c --- /dev/null +++ b/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; + } + + /// + /// Call JsValue.ToString(), mainly for NamespaceReference. + /// + public JsValue ToString(JsValue value) + { + return value.ToString(); + } + + /// + /// Cast `obj as ISomeInterface` to `obj` + /// + public JsValue Unwrap(ObjectWrapper obj) + { + return new ObjectWrapper(obj.Engine, obj.Target); + } + + /// + /// Cast `obj` to `obj as ISomeInterface` + /// + 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); + } + + /// + /// Get `TypeReference(ISomeInterface)` from `obj as ISomeInterface` + /// + public JsValue TypeOf(ObjectWrapper obj) + { + MustAllowGetType(); + return TypeReference.CreateTypeReference(obj.Engine, obj.ClrType); + } + + /// + /// Cast `TypeReference(SomeClass)` to `ObjectWrapper(SomeClass)` + /// + public JsValue TypeToObject(TypeReference type) + { + MustAllowGetType(); + var engine = type.Engine; + return engine.Options.Interop.WrapObjectHandler.Invoke(engine, type.ReferenceType, null) ?? JsValue.Undefined; + } + + /// + /// Cast `ObjectWrapper(SomeClass)` to `TypeReference(SomeClass)` + /// + 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"); + } + } +} diff --git a/Jint/Runtime/Interop/DefaultObjectConverter.cs b/Jint/Runtime/Interop/DefaultObjectConverter.cs index 8c3a647389..f9739bbfb2 100644 --- a/Jint/Runtime/Interop/DefaultObjectConverter.cs +++ b/Jint/Runtime/Interop/DefaultObjectConverter.cs @@ -37,7 +37,7 @@ internal static class DefaultObjectConverter 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; diff --git a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs index 81deb326ee..8d7385b2a0 100644 --- a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs +++ b/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 @@ private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, MethodDescriptor me newArgumentsCollection[nonParamsArgumentsCount] = jsArray; return newArgumentsCollection; } + + public override string ToString() + { + return $"function {_targetType}.{_name}() {{ [native code] }}"; + } } } diff --git a/Jint/Runtime/Interop/NamespaceReference.cs b/Jint/Runtime/Interop/NamespaceReference.cs index fd795201ab..e72bb9a124 100644 --- a/Jint/Runtime/Interop/NamespaceReference.cs +++ b/Jint/Runtime/Interop/NamespaceReference.cs @@ -94,7 +94,7 @@ public JsValue GetPath(string path) // 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 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) public override string ToString() { - return "[Namespace: " + _path + "]"; + return "[CLR namespace: " + _path + "]"; } } } diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs index 74eb62a65e..2b76866843 100644 --- a/Jint/Runtime/Interop/ObjectWrapper.cs +++ b/Jint/Runtime/Interop/ObjectWrapper.cs @@ -17,14 +17,13 @@ namespace Jint.Runtime.Interop public sealed class ObjectWrapper : ObjectInstance, IObjectWrapper, IEquatable { 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 @@ public ObjectWrapper(Engine engine, object obj, Type? type = null) } public object Target { get; } + internal Type ClrType { get; } public override bool IsArrayLike => _typeDescriptor.IsArrayLike; @@ -51,7 +51,7 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) 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 @@ private IEnumerable EnumerateOwnPropertyKeys(Types types) 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 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) 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 @@ public static PropertyDescriptor GetPropertyDescriptor(Engine engine, object tar 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 @@ public static PropertyDescriptor GetPropertyDescriptor(Engine engine, object tar 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 @@ public bool Equals(ObjectWrapper? other) 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 @@ public EnumerableIterator(Engine engine, IEnumerable target) : base(engine) public override void Close(CompletionType completion) { - (_enumerator as IDisposable)?.Dispose(); + (_enumerator as IDisposable)?.Dispose(); base.Close(completion); } diff --git a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs b/Jint/Runtime/Interop/Reflection/MethodAccessor.cs index 601e914dc0..3a7a8b80df 100644 --- a/Jint/Runtime/Interop/Reflection/MethodAccessor.cs +++ b/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 @@ protected override void DoSetValue(object target, object? value) 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); } } } diff --git a/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs b/Jint/Runtime/Interop/Reflection/NestedTypeAccessor.cs new file mode 100644 index 0000000000..c4d7a66e65 --- /dev/null +++ b/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) + { + } +} diff --git a/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs b/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs index ea28ec602e..233f6f05ed 100644 --- a/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs +++ b/Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs @@ -12,7 +12,7 @@ namespace Jint.Runtime.Interop.Reflection /// internal abstract class ReflectionAccessor { - private readonly Type _memberType; + protected readonly Type _memberType; private readonly object? _memberName; private readonly PropertyInfo? _indexer; diff --git a/Jint/Runtime/Interop/TypeReference.cs b/Jint/Runtime/Interop/TypeReference.cs index 92f0f61705..c4c99b3f22 100644 --- a/Jint/Runtime/Interop/TypeReference.cs +++ b/Jint/Runtime/Interop/TypeReference.cs @@ -346,5 +346,10 @@ private static JsValue HasInstance(JsValue thisObject, JsValue[] arguments) return derivedType != null && baseType != null && (derivedType == baseType || derivedType.IsSubclassOf(baseType)); } + + public override string ToString() + { + return "[CLR type: " + ReferenceType + "]"; + } } } diff --git a/Jint/Runtime/Interop/TypeResolver.cs b/Jint/Runtime/Interop/TypeResolver.cs index 744c634e88..af40e876ac 100644 --- a/Jint/Runtime/Interop/TypeResolver.cs +++ b/Jint/Runtime/Interop/TypeResolver.cs @@ -160,7 +160,7 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( 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 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory( if (matches.Count > 0) { - return new MethodAccessor(MethodDescriptor.Build(matches)); + return new MethodAccessor(type, memberName, MethodDescriptor.Build(matches)); } } @@ -299,7 +299,16 @@ internal bool TryFindMemberAccessor( 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; }