Skip to content

Commit

Permalink
Merge pull request #25 from koculu/24-feature-add-an-interface-to-cus…
Browse files Browse the repository at this point in the history
…tomize-retrieving-type-members

Add MemberProvider interface.
  • Loading branch information
koculu authored Dec 24, 2023
2 parents 891179b + 41a67bf commit 9adc58e
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 25 deletions.
72 changes: 72 additions & 0 deletions src/Topaz.Test/AwaitTests.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -31,6 +36,19 @@ public async Task SimpleTask()
return;
}

public async IAsyncEnumerator<int> 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()
{
Expand Down Expand Up @@ -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<System.Int32>.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);
}
}
11 changes: 11 additions & 0 deletions src/Topaz/Interop/IMemberInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -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);
}
17 changes: 17 additions & 0 deletions src/Topaz/Interop/Impl/MemberInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
19 changes: 14 additions & 5 deletions src/Topaz/Interop/Impl/NamespaceProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,22 @@ public sealed class NamespaceProxy : ITypeProxy
/// Generic type search up to given parameter count
/// </summary>
public int MaxGenericTypeArgumentCount { get; }

/// <summary>
/// If enabled, sub namespaces are accessible.
/// </summary>
public bool AllowSubNamespaces { get; }

/// <summary>
/// ValueConverter to be passed to created TypeProxy.
/// ValueConverter to be passed to the created TypeProxy.
/// </summary>
public IValueConverter ValueConverter { get; }

/// <summary>
/// MemberInfoProvider to be passed to the created TypeProxy.
/// </summary>
public IMemberInfoProvider MemberInfoProvider { get; }

/// <summary>
/// ProxyOptions to be passed to created TypeProxy.
/// </summary>
Expand All @@ -47,17 +52,19 @@ public sealed class NamespaceProxy : ITypeProxy
public Type ProxiedType { get; }

public NamespaceProxy(
string name,
string name,
IReadOnlySet<string> whitelist,
bool allowSubNamespaces,
IValueConverter valueConverter,
IMemberInfoProvider memberInfoProvider,
int maxGenericTypeArgumentCount = 5,
ProxyOptions proxyOptions = ProxyOptions.Default)
{
Name = name;
Whitelist = whitelist;
AllowSubNamespaces = allowSubNamespaces;
ValueConverter = valueConverter;
MemberInfoProvider = memberInfoProvider;
MaxGenericTypeArgumentCount = maxGenericTypeArgumentCount;
ProxyOptions = proxyOptions;
}
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down
10 changes: 6 additions & 4 deletions src/Topaz/Interop/Impl/ObjectProxyUsingReflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public override int GetHashCode()

public IValueConverter ValueConverter { get; }

public IMemberInfoProvider MemberInfoProvider { get; }

public ProxyOptions ProxyOptions { get; }

readonly PropertyInfo[] _indexedProperties;
Expand All @@ -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 &&
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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];
Expand Down
28 changes: 15 additions & 13 deletions src/Topaz/Interop/Impl/TypeProxyUsingReflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,7 +34,7 @@ public sealed class TypeProxyUsingReflection : ITypeProxy
static readonly ConcurrentDictionary<Type, TypeProxyUsingReflection> CreatedTypeProxies = new();

private const string GENERIC_ARGUMENTS_METHOD = "GenericArguments";

private const string STATIC_GENERIC_ARGUMENTS_METHOD = "StaticGenericArguments";

readonly static MethodAndParameterInfo genericMethodSelectorParameterInfo = new MethodAndParameterInfo(
Expand All @@ -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) &&
Expand Down Expand Up @@ -96,8 +100,8 @@ private object CallNonGenericConstructor(Type type, IReadOnlyList<object> 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;
Expand Down Expand Up @@ -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)
Expand All @@ -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<object>(() =>
Expand Down Expand Up @@ -269,7 +272,7 @@ public bool TryGetStaticMember(
return false;
}

var invokerContext =
var invokerContext =
new InvokerUsingReflectionContext(Name + "." + memberName, methods, options, ValueConverter);
var methodGetter = new Func<object>(() =>
{
Expand Down Expand Up @@ -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);
Expand All @@ -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];
Expand Down Expand Up @@ -374,7 +376,7 @@ public bool TrySetStaticMember(
}
return false;
}

public static object GetTypeProxy(Type type)
{
CreatedTypeProxies.TryGetValue(type, out var proxy);
Expand Down
9 changes: 6 additions & 3 deletions src/Topaz/TopazEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
}

Expand All @@ -99,7 +102,7 @@ public void AddNamespace(string @namespace, IReadOnlySet<string> whitelist = nul
{
GlobalScope.SetValueAndKind(
name ?? @namespace,
new NamespaceProxy(@namespace, whitelist, allowSubNamespaces, ValueConverter),
new NamespaceProxy(@namespace, whitelist, allowSubNamespaces, ValueConverter, MemberInfoProvider),
VariableKind.Const);
}

Expand Down
2 changes: 2 additions & 0 deletions src/Topaz/TopazEngineSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ public sealed class TopazEngineSetup
public IMemberAccessPolicy MemberAccessPolicy { get; set; }

public IValueConverter ValueConverter { get; set; }

public IMemberInfoProvider MemberInfoProvider { get; set; }
}

0 comments on commit 9adc58e

Please sign in to comment.