From 41a67bfec9d7538b54b7c8a8f2db404cc5031d00 Mon Sep 17 00:00:00 2001 From: Ahmed Yasin Koculu Date: Sun, 24 Dec 2023 04:10:20 +0100 Subject: [PATCH] Add MemberProvider interface. --- src/Topaz.Test/AwaitTests.cs | 72 +++++++++++++++++++ src/Topaz/Interop/IMemberInfoProvider.cs | 11 +++ src/Topaz/Interop/Impl/MemberInfoProvider.cs | 17 +++++ src/Topaz/Interop/Impl/NamespaceProxy.cs | 19 +++-- .../Impl/ObjectProxyUsingReflection.cs | 10 +-- .../Interop/Impl/TypeProxyUsingReflection.cs | 28 ++++---- src/Topaz/TopazEngine.cs | 9 ++- src/Topaz/TopazEngineSetup.cs | 2 + 8 files changed, 143 insertions(+), 25 deletions(-) create mode 100644 src/Topaz/Interop/IMemberInfoProvider.cs create mode 100644 src/Topaz/Interop/Impl/MemberInfoProvider.cs diff --git a/src/Topaz.Test/AwaitTests.cs b/src/Topaz.Test/AwaitTests.cs index 78e1f22..430e9e2 100644 --- a/src/Topaz.Test/AwaitTests.cs +++ b/src/Topaz.Test/AwaitTests.cs @@ -1,7 +1,12 @@ using NUnit.Framework; +using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Tenray.Topaz.API; +using Tenray.Topaz.Interop; namespace Tenray.Topaz.Test; @@ -31,6 +36,19 @@ public async Task SimpleTask() return; } + public async IAsyncEnumerator GetAsyncEnumerator() + { + yield return 3; + await Task.Delay(1); + yield return 1; + await Task.Delay(1); + yield return 2; + await Task.Delay(1); + yield return 3; + await Task.Delay(1); + yield return 3; + } + [Test] public void GenericValueTaskAwait() { @@ -98,4 +116,58 @@ public void SimpleTaskAwait() "); Assert.That(model.result2, Is.EqualTo(null)); } + + [Test] + public void AsyncEnumerator() + { + var engine = new TopazEngine(new TopazEngineSetup + { + MemberInfoProvider = new CustomMemberInfoProvider() + }); + dynamic model = new JsObject(); + engine.SetValue("test", this); + engine.SetValue("model", model); + engine.ExecuteScriptAsync(@" +let enumerator = test.GetAsyncEnumerator(); +var i = 0 +while(1) { + const hasNext = await enumerator.MoveNextAsync(); + if (!hasNext) break; + ++i; +} +model.result1 = i; +").Wait(); + Assert.That(model.result1, Is.EqualTo(5)); + engine.ExecuteScript(@" +enumerator = test.GetAsyncEnumerator(); +var i = 0 +while(1) { + const hasNext = await enumerator.MoveNextAsync(); + if (!hasNext) break; + ++i; +} +model.result2 = i; +"); + Assert.That(model.result2, Is.EqualTo(5)); + } +} + +class CustomMemberInfoProvider : IMemberInfoProvider +{ + public MemberInfo[] GetInstanceMembers(object instance, string memberName) + { + if (memberName == "MoveNextAsync") + { + // Handle special case for auto generated async enumerators. + // MoveNextAsync is not accessible through its name and it is not public. + // https://github.com/dotnet/roslyn/issues/71406 + return instance.GetType().GetMember("System.Collections.Generic.IAsyncEnumerator.MoveNextAsync", BindingFlags.NonPublic | BindingFlags.Instance); + } + return instance.GetType().GetMember(memberName, BindingFlags.Public | BindingFlags.Instance); + } + + public MemberInfo[] GetStaticMembers(Type type, string memberName) + { + return type.GetMember(memberName, BindingFlags.Public | BindingFlags.Static); + } } \ No newline at end of file diff --git a/src/Topaz/Interop/IMemberInfoProvider.cs b/src/Topaz/Interop/IMemberInfoProvider.cs new file mode 100644 index 0000000..3af6daa --- /dev/null +++ b/src/Topaz/Interop/IMemberInfoProvider.cs @@ -0,0 +1,11 @@ +using System; +using System.Reflection; + +namespace Tenray.Topaz.Interop; + +public interface IMemberInfoProvider +{ + MemberInfo[] GetInstanceMembers(object instance, string memberName); + + MemberInfo[] GetStaticMembers(Type type, string memberName); +} \ No newline at end of file diff --git a/src/Topaz/Interop/Impl/MemberInfoProvider.cs b/src/Topaz/Interop/Impl/MemberInfoProvider.cs new file mode 100644 index 0000000..e6da637 --- /dev/null +++ b/src/Topaz/Interop/Impl/MemberInfoProvider.cs @@ -0,0 +1,17 @@ +using System; +using System.Reflection; + +namespace Tenray.Topaz.Interop; + +public class MemberInfoProvider : IMemberInfoProvider +{ + public MemberInfo[] GetInstanceMembers(object instance, string memberName) + { + return instance.GetType().GetMember(memberName, BindingFlags.Public | BindingFlags.Instance); + } + + public MemberInfo[] GetStaticMembers(Type type, string memberName) + { + return type.GetMember(memberName, BindingFlags.Public | BindingFlags.Static); + } +} diff --git a/src/Topaz/Interop/Impl/NamespaceProxy.cs b/src/Topaz/Interop/Impl/NamespaceProxy.cs index 3a12a3a..1f80a5b 100644 --- a/src/Topaz/Interop/Impl/NamespaceProxy.cs +++ b/src/Topaz/Interop/Impl/NamespaceProxy.cs @@ -25,17 +25,22 @@ public sealed class NamespaceProxy : ITypeProxy /// Generic type search up to given parameter count /// public int MaxGenericTypeArgumentCount { get; } - + /// /// If enabled, sub namespaces are accessible. /// public bool AllowSubNamespaces { get; } /// - /// ValueConverter to be passed to created TypeProxy. + /// ValueConverter to be passed to the created TypeProxy. /// public IValueConverter ValueConverter { get; } + /// + /// MemberInfoProvider to be passed to the created TypeProxy. + /// + public IMemberInfoProvider MemberInfoProvider { get; } + /// /// ProxyOptions to be passed to created TypeProxy. /// @@ -47,10 +52,11 @@ public sealed class NamespaceProxy : ITypeProxy public Type ProxiedType { get; } public NamespaceProxy( - string name, + string name, IReadOnlySet whitelist, bool allowSubNamespaces, IValueConverter valueConverter, + IMemberInfoProvider memberInfoProvider, int maxGenericTypeArgumentCount = 5, ProxyOptions proxyOptions = ProxyOptions.Default) { @@ -58,6 +64,7 @@ public NamespaceProxy( Whitelist = whitelist; AllowSubNamespaces = allowSubNamespaces; ValueConverter = valueConverter; + MemberInfoProvider = memberInfoProvider; MaxGenericTypeArgumentCount = maxGenericTypeArgumentCount; ProxyOptions = proxyOptions; } @@ -84,11 +91,13 @@ public bool TryGetStaticMember( var type = FindType(fullname); if (type == null) { - if (AllowSubNamespaces) { + if (AllowSubNamespaces) + { value = new NamespaceProxy(fullname, Whitelist, true, ValueConverter, + MemberInfoProvider, MaxGenericTypeArgumentCount, ProxyOptions); return true; @@ -111,7 +120,7 @@ public bool TryGetStaticMember( value = TypeProxyUsingReflection.GetTypeProxy(type); if (value == null) - value = new TypeProxyUsingReflection(type, ValueConverter, fullname, ProxyOptions); + value = new TypeProxyUsingReflection(type, ValueConverter, MemberInfoProvider, fullname, ProxyOptions); return true; } diff --git a/src/Topaz/Interop/Impl/ObjectProxyUsingReflection.cs b/src/Topaz/Interop/Impl/ObjectProxyUsingReflection.cs index d8594da..ae00e42 100644 --- a/src/Topaz/Interop/Impl/ObjectProxyUsingReflection.cs +++ b/src/Topaz/Interop/Impl/ObjectProxyUsingReflection.cs @@ -44,6 +44,8 @@ public override int GetHashCode() public IValueConverter ValueConverter { get; } + public IMemberInfoProvider MemberInfoProvider { get; } + public ProxyOptions ProxyOptions { get; } readonly PropertyInfo[] _indexedProperties; @@ -69,11 +71,13 @@ public ObjectProxyUsingReflection( Type proxiedType, ExtensionMethodRegistry extensionMethodRegistry, IValueConverter valueConverter, + IMemberInfoProvider memberInfoProvider, ProxyOptions proxyOptions = ProxyOptions.Default) { ProxiedType = proxiedType; ExtensionMethodRegistry = extensionMethodRegistry; ValueConverter = valueConverter; + MemberInfoProvider = memberInfoProvider; ProxyOptions = proxyOptions; if (proxiedType != null && @@ -203,8 +207,7 @@ public bool TryGetObjectMember( value = cachedGetter(instance); return true; } - var members = instanceType - .GetMember(memberName, BindingFlags.Public | BindingFlags.Instance); + var members = MemberInfoProvider.GetInstanceMembers(instance, memberName); if (members.Length == 0) { if (!options.HasFlag(ProxyOptions.AllowMethod)) @@ -441,8 +444,7 @@ public bool TrySetObjectMember( return true; } - var members = instanceType - .GetMember(memberName, BindingFlags.Public | BindingFlags.Instance); + var members = MemberInfoProvider.GetInstanceMembers(instance, memberName); if (members.Length == 0) return false; var firstMember = members[0]; diff --git a/src/Topaz/Interop/Impl/TypeProxyUsingReflection.cs b/src/Topaz/Interop/Impl/TypeProxyUsingReflection.cs index d96a4b6..0eb6bd9 100644 --- a/src/Topaz/Interop/Impl/TypeProxyUsingReflection.cs +++ b/src/Topaz/Interop/Impl/TypeProxyUsingReflection.cs @@ -11,16 +11,18 @@ public sealed class TypeProxyUsingReflection : ITypeProxy { public IValueConverter ValueConverter { get; } + public IMemberInfoProvider MemberInfoProvider { get; } + public string Name { get; } public Type ProxiedType { get; } - + public ProxyOptions ProxyOptions { get; } readonly ConstructorInfo[] _constructors; readonly ParameterInfo[][] _constructorParameters; - + readonly PropertyInfo[] _indexedProperties; readonly ParameterInfo[][] _indexedPropertyParameters; @@ -32,7 +34,7 @@ public sealed class TypeProxyUsingReflection : ITypeProxy static readonly ConcurrentDictionary CreatedTypeProxies = new(); private const string GENERIC_ARGUMENTS_METHOD = "GenericArguments"; - + private const string STATIC_GENERIC_ARGUMENTS_METHOD = "StaticGenericArguments"; readonly static MethodAndParameterInfo genericMethodSelectorParameterInfo = new MethodAndParameterInfo( @@ -47,11 +49,13 @@ public sealed class TypeProxyUsingReflection : ITypeProxy public TypeProxyUsingReflection( Type proxiedType, IValueConverter valueConverter, + IMemberInfoProvider memberInfoProvider, string name = null, ProxyOptions proxyOptions = ProxyOptions.Default) { Name = name ?? proxiedType.FullName; ValueConverter = valueConverter; + MemberInfoProvider = memberInfoProvider; ProxiedType = proxiedType; ProxyOptions = proxyOptions; if (proxyOptions.HasFlag(ProxyOptions.AllowConstructor) && @@ -96,8 +100,8 @@ private object CallNonGenericConstructor(Type type, IReadOnlyList args) return CreateDelegate(type, args); if (args.Count == 0) - return Activator.CreateInstance(type); - + return Activator.CreateInstance(type); + if (!options.HasFlag(ProxyOptions.AutomaticTypeConversion)) return Activator.CreateInstance(type, args); var constructors = _constructors; @@ -202,8 +206,7 @@ public bool TryGetStaticMember( value = cachedGetter(); return true; } - var members = ProxiedType - .GetMember(memberName, BindingFlags.Public | BindingFlags.Static); + var members = MemberInfoProvider.GetStaticMembers(ProxiedType, memberName); if (members.Length == 0) { if (memberName == GENERIC_ARGUMENTS_METHOD) @@ -222,7 +225,7 @@ public bool TryGetStaticMember( { if (!allowProperty) return false; - if (property.GetMethod == null || + if (property.GetMethod == null || property.GetMethod.IsPrivate) return false; var getter = new Func(() => @@ -269,7 +272,7 @@ public bool TryGetStaticMember( return false; } - var invokerContext = + var invokerContext = new InvokerUsingReflectionContext(Name + "." + memberName, methods, options, ValueConverter); var methodGetter = new Func(() => { @@ -306,7 +309,7 @@ public bool TrySetStaticMember( out var convertedArgs)) { var indexedProperty = _indexedProperties[index]; - if (indexedProperty.SetMethod == null || + if (indexedProperty.SetMethod == null || indexedProperty.SetMethod.IsPrivate) return false; _indexedProperties[index].SetValue(null, value, convertedArgs); @@ -329,8 +332,7 @@ public bool TrySetStaticMember( return true; } - var members = ProxiedType - .GetMember(memberName, BindingFlags.Public | BindingFlags.Static); + var members = MemberInfoProvider.GetStaticMembers(ProxiedType, memberName); if (members.Length == 0) return false; var firstMember = members[0]; @@ -374,7 +376,7 @@ public bool TrySetStaticMember( } return false; } - + public static object GetTypeProxy(Type type) { CreatedTypeProxies.TryGetValue(type, out var proxy); diff --git a/src/Topaz/TopazEngine.cs b/src/Topaz/TopazEngine.cs index 277110c..992f086 100644 --- a/src/Topaz/TopazEngine.cs +++ b/src/Topaz/TopazEngine.cs @@ -35,6 +35,8 @@ public sealed class TopazEngine : ITopazEngine public IValueConverter ValueConverter { get; } + public IMemberInfoProvider MemberInfoProvider { get; } + internal ScriptExecutorPool ScriptExecutorPool = new(); public TopazEngine(TopazEngineSetup setup = null) @@ -49,8 +51,9 @@ public TopazEngine(TopazEngineSetup setup = null) Options.UseThreadSafeJsObjects = setup.IsThreadSafe; ObjectProxyRegistry = setup.ObjectProxyRegistry ?? new DictionaryObjectProxyRegistry(); extensionMethodRegistry = new(); + MemberInfoProvider = setup.MemberInfoProvider ?? new MemberInfoProvider(); ValueConverter = setup.ValueConverter ?? new DefaultValueConverter(); - DefaultObjectProxy = setup.DefaultObjectProxy ?? new ObjectProxyUsingReflection(null, extensionMethodRegistry, ValueConverter); + DefaultObjectProxy = setup.DefaultObjectProxy ?? new ObjectProxyUsingReflection(null, extensionMethodRegistry, ValueConverter, MemberInfoProvider); DelegateInvoker = setup.DelegateInvoker ?? new DelegateInvoker(ValueConverter); MemberAccessPolicy = setup.MemberAccessPolicy ?? new DefaultMemberAccessPolicy(this); } @@ -86,7 +89,7 @@ public void AddType(Type type, string name = null, ITypeProxy typeProxy = null) name ?? type.FullName, typeProxy ?? TypeProxyUsingReflection.GetTypeProxy(type) ?? - new TypeProxyUsingReflection(type, ValueConverter, name), + new TypeProxyUsingReflection(type, ValueConverter, MemberInfoProvider, name), VariableKind.Const); } @@ -99,7 +102,7 @@ public void AddNamespace(string @namespace, IReadOnlySet whitelist = nul { GlobalScope.SetValueAndKind( name ?? @namespace, - new NamespaceProxy(@namespace, whitelist, allowSubNamespaces, ValueConverter), + new NamespaceProxy(@namespace, whitelist, allowSubNamespaces, ValueConverter, MemberInfoProvider), VariableKind.Const); } diff --git a/src/Topaz/TopazEngineSetup.cs b/src/Topaz/TopazEngineSetup.cs index 656321e..42e6b9c 100644 --- a/src/Topaz/TopazEngineSetup.cs +++ b/src/Topaz/TopazEngineSetup.cs @@ -22,4 +22,6 @@ public sealed class TopazEngineSetup public IMemberAccessPolicy MemberAccessPolicy { get; set; } public IValueConverter ValueConverter { get; set; } + + public IMemberInfoProvider MemberInfoProvider { get; set; } }