Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions src/NodeApi.DotNetHost/JSMarshaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1831,6 +1831,10 @@ private LambdaExpression BuildConvertFromJSValueExpression(Type toType)
statements = BuildFromJSToStructExpressions(toType, variables, valueParameter);
}
}
else if (IsSettableCollectionType(toType))
{
statements = BuildFromJSToCollectionExpressions(toType, variables, valueParameter);
}
else if (toType.IsClass)
{
if (toType == typeof(Stream))
Expand Down Expand Up @@ -1863,10 +1867,6 @@ private LambdaExpression BuildConvertFromJSValueExpression(Type toType)
};
}
}
else if (toType.IsInterface && toType.Namespace == typeof(ICollection<>).Namespace)
{
statements = BuildFromJSToCollectionExpressions(toType, variables, valueParameter);
}
else if (toType.IsInterface)
{
// It could be either a wrapped .NET object passed back from JS or a JS object
Expand Down Expand Up @@ -2091,6 +2091,23 @@ private LambdaExpression BuildConvertToJSValueExpression(Type fromType)
parameters: new[] { valueParameter });
}

private static bool IsSettableCollectionType(Type toType) {
var isCollectionInterface = (
toType.IsInterface &&
toType.Namespace == typeof(ICollection<>).Namespace
);
Type[] exportedGenericClassCollections = {
typeof(System.Collections.Generic.List<>),
};
var isExportedCollectionType = false;
if (toType.IsGenericType) {
isExportedCollectionType = exportedGenericClassCollections.Contains(
toType.GetGenericTypeDefinition()
);
}
return isCollectionInterface || isExportedCollectionType;
}

private static bool IsGettableCollectionType(Type fromType) {
var isCollectionInterface = (
fromType.IsInterface &&
Expand Down Expand Up @@ -2442,6 +2459,28 @@ private IEnumerable<Expression> BuildFromJSToCollectionExpressions(
Expression.Convert(valueExpression, jsCollectionType, asJSCollectionMethod),
GetFromJSValueExpression(elementType)));
}
else if (typeDefinition == typeof(List<>))
{
Type jsCollectionType = typeof(JSArray);
MethodInfo asCollectionMethod = typeof(JSCollectionExtensions).GetStaticMethod(
nameof(JSCollectionExtensions.AsListClass),
new[] { jsCollectionType, typeof(JSValue.To<>), typeof(JSValue.From<>) },
elementType
);
MethodInfo asJSCollectionMethod = jsCollectionType.GetExplicitConversion(
typeof(JSValue),
jsCollectionType
);
yield return Expression.Coalesce(
Expression.TypeAs(Expression.Call(s_tryUnwrap, valueExpression), toType),
Expression.Call(
asCollectionMethod,
Expression.Convert(valueExpression, jsCollectionType, asJSCollectionMethod),
GetFromJSValueExpression(elementType),
GetToJSValueExpression(elementType)
)
);
}
else if (typeDefinition == typeof(IDictionary<,>))
{
Type keyType = elementType;
Expand Down Expand Up @@ -2577,7 +2616,7 @@ private IEnumerable<Expression> BuildToJSFromCollectionExpressions(
*/
MethodInfo wrapMethod = typeof(JSRuntimeContext).GetInstanceMethod(
nameof(JSRuntimeContext.GetOrCreateCollectionWrapper),
new[] { typeof(IList<>), typeof(JSValue.From<>), typeof(JSValue.To<>) },
new[] { typeDefinition, typeof(JSValue.From<>), typeof(JSValue.To<>) },
elementType);
yield return Expression.Call(
Expression.Property(null, s_context),
Expand Down
45 changes: 45 additions & 0 deletions src/NodeApi/Interop/JSCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ public static IList<T> AsList<T>(this JSArray array, JSValue.To<T> fromJS, JSVal
=> ((JSValue)array).IsNullOrUndefined() ? null! :
new JSArrayList<T>((JSValue)array, fromJS, toJS);

/// <summary>
/// Creates a list adapter for a JS Array object, without copying.
/// </summary>
public static List<T> AsListClass<T>(this JSArray array, JSValue.To<T> fromJS, JSValue.From<T> toJS)
=> ((JSValue)array).IsNullOrUndefined() ? null! :
new JSArrayListClass<T>((JSValue)array, fromJS, toJS);

/// <summary>
/// Creates an enumerable adapter for a JS Set object, without copying.
/// </summary>
Expand Down Expand Up @@ -387,6 +394,44 @@ public T this[int index]
public void RemoveAt(int index) => Value.CallMethod("splice", index, 1);
}

internal class JSArrayListClass<T> : List<T>, IEnumerable<T>, IEquatable<JSValue>
{
protected JSValue.To<T> FromJS { get; }
protected JSValue.From<T> ToJS { get; }
private readonly JSReference _iterableReference;
internal JSArrayListClass(JSValue array, JSValue.To<T> fromJS, JSValue.From<T> toJS)
{
ToJS = toJS;
_iterableReference = new JSReference(array);
FromJS = fromJS;
}

public JSValue Value => _iterableReference.GetValue()!.Value;
public new int Count => Value.GetArrayLength();

bool IEquatable<JSValue>.Equals(JSValue other) => Value.Equals(other);

public new IEnumerator<T> GetEnumerator() => new JSIterableEnumerator<T>(Value, FromJS);

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
=> GetEnumerator();

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_iterableReference.Dispose();
}
}

}

internal class JSSetReadOnlyCollection<T> : JSIterableEnumerable<T>, IReadOnlyCollection<T>
{
internal JSSetReadOnlyCollection(JSValue value, JSValue.To<T> fromJS) : base(value, fromJS)
Expand Down
15 changes: 15 additions & 0 deletions src/NodeApi/Interop/JSRuntimeContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,21 @@ public JSValue GetOrCreateCollectionWrapper<T>(
});
}

public JSValue GetOrCreateCollectionWrapper<T>(
List<T> collection,
JSValue.From<T> toJS,
JSValue.To<T> fromJS)
{
return collection is JSArrayListClass<T> adapter ? adapter.Value :
GetOrCreateCollectionProxy(collection, () =>
{
JSProxy.Handler proxyHandler = _collectionProxyHandlerMap.GetOrAdd(
typeof(IList<T>),
(_) => CreateArrayProxyHandlerForList(toJS, fromJS));
return new JSProxy(new JSArray(), proxyHandler, collection);
});
}

#if !NETFRAMEWORK
public JSValue GetOrCreateCollectionWrapper<T>(
IReadOnlySet<T> collection,
Expand Down