From d52ad7c8e438d34fd07808e97e2a809396c2dc0a Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Wed, 7 Aug 2024 18:00:00 -0700 Subject: [PATCH] Implement strongly typed cast from/to JSValue (#196) --- src/NodeApi/IJSValue.cs | 110 ++++ src/NodeApi/Interop/JSAbortSignal.cs | 111 +++- src/NodeApi/JSArray.cs | 106 +++- src/NodeApi/JSAsyncIterable.cs | 108 +++- src/NodeApi/JSBigInt.cs | 108 +++- src/NodeApi/JSDate.cs | 107 +++- src/NodeApi/JSFunction.cs | 106 +++- src/NodeApi/JSIterable.cs | 107 +++- src/NodeApi/JSMap.cs | 106 +++- src/NodeApi/JSObject.cs | 106 +++- src/NodeApi/JSPromise.cs | 106 +++- src/NodeApi/JSProxy.cs | 110 +++- src/NodeApi/JSSet.cs | 106 +++- src/NodeApi/JSSymbol.cs | 106 +++- src/NodeApi/JSTypedArray.cs | 132 ++++- src/NodeApi/JSValue.cs | 76 ++- src/node-api-dotnet/pack.js | 7 + test/TestCases/napi-dotnet/JSValueCast.cs | 571 +++++++++++++++++++++ test/TestCases/napi-dotnet/jsvalue_cast.js | 160 ++++++ 19 files changed, 2413 insertions(+), 36 deletions(-) create mode 100644 src/NodeApi/IJSValue.cs create mode 100644 test/TestCases/napi-dotnet/JSValueCast.cs create mode 100644 test/TestCases/napi-dotnet/jsvalue_cast.js diff --git a/src/NodeApi/IJSValue.cs b/src/NodeApi/IJSValue.cs new file mode 100644 index 00000000..20a6c708 --- /dev/null +++ b/src/NodeApi/IJSValue.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +#if !NET7_0_OR_GREATER +using System.Reflection; +#endif + +namespace Microsoft.JavaScript.NodeApi; + +/// +/// A base interface for a struct that represents a JavaScript value type or a built-in +/// object type. It provides functionality for converting between the struct +/// and . +/// +/// The derived struct type. +public interface IJSValue : IEquatable where TSelf : struct, IJSValue +{ + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + bool Is() where T : struct, IJSValue; + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + T? As() where T : struct, IJSValue; + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + T AsUnchecked() where T : struct, IJSValue; + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + T CastTo() where T : struct, IJSValue; + +#if NET7_0_OR_GREATER + /// + /// Checks if the derived struct `TSelf` can be created from a . + /// + static abstract bool CanCreateFrom(JSValue value); + + /// + /// Creates a new instance of the derived struct `TSelf` from a without + /// checking the enclosed handle type. + /// + static abstract TSelf CreateUnchecked(JSValue value); +#endif +} + +#if !NET7_0_OR_GREATER +/// +/// Implements IJSValue interface static functions for the previous .Net versions. +/// +/// +internal static class IJSValueShim where T : struct, IJSValue +{ + /// + /// A static field to keep a reference to the CanCreateFrom private method. + /// + private static readonly Func s_canCreateFrom = + (Func)Delegate.CreateDelegate( + typeof(Func), + typeof(T).GetMethod( + nameof(CanCreateFrom), + BindingFlags.Static | BindingFlags.NonPublic)!); + + /// + /// A static field to keep a reference to the CreateUnchecked private method. + /// + private static readonly Func s_createUnchecked = + (Func)Delegate.CreateDelegate( + typeof(Func), + typeof(T).GetMethod( + nameof(CreateUnchecked), + BindingFlags.Static | BindingFlags.NonPublic)!); + + /// + /// Invokes `T.CanCreateFrom` static public method. + /// + public static bool CanCreateFrom(JSValue value) => s_canCreateFrom(value); + + /// + /// Invokes `T.CreateUnchecked` static private method. + /// + public static T CreateUnchecked(JSValue value) => s_createUnchecked(value); +} +#endif diff --git a/src/NodeApi/Interop/JSAbortSignal.cs b/src/NodeApi/Interop/JSAbortSignal.cs index c072b51d..0373de24 100644 --- a/src/NodeApi/Interop/JSAbortSignal.cs +++ b/src/NodeApi/Interop/JSAbortSignal.cs @@ -15,15 +15,37 @@ namespace Microsoft.JavaScript.NodeApi.Interop; /// https://nodejs.org/api/globals.html#class-abortsignal /// https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal /// -public readonly struct JSAbortSignal : IEquatable +public readonly struct JSAbortSignal : IJSValue { private readonly JSValue _value; - public static explicit operator JSAbortSignal(JSValue value) => new(value); - public static implicit operator JSValue(JSAbortSignal promise) => promise._value; + /// + /// Implicitly converts a to a . + /// + /// The to convert. + public static implicit operator JSValue(JSAbortSignal signal) => signal._value; + + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSAbortSignal?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSAbortSignal(JSValue value) => value.CastTo(); public static explicit operator JSAbortSignal(JSObject obj) => (JSAbortSignal)(JSValue)obj; - public static implicit operator JSObject(JSAbortSignal promise) => (JSObject)promise._value; + public static implicit operator JSObject(JSAbortSignal signal) => (JSObject)signal._value; private JSAbortSignal(JSValue value) { @@ -35,9 +57,90 @@ public static explicit operator CancellationToken(JSAbortSignal signal) public static explicit operator JSAbortSignal(CancellationToken cancellation) => FromCancellationToken(cancellation); + public static explicit operator JSAbortSignal(CancellationToken? cancellation) => cancellation.HasValue ? FromCancellationToken(cancellation.Value) : default; + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsObject() && value.InstanceOf(JSValue.Global["AbortSignal"]); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSAbortSignal IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSAbortSignal CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + private CancellationToken ToCancellationToken() { if (!_value.IsObject()) diff --git a/src/NodeApi/JSArray.cs b/src/NodeApi/JSArray.cs index 603d5343..a9573eeb 100644 --- a/src/NodeApi/JSArray.cs +++ b/src/NodeApi/JSArray.cs @@ -8,13 +8,35 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly partial struct JSArray : IList, IEquatable +public readonly partial struct JSArray : IJSValue, IList { private readonly JSValue _value; - public static explicit operator JSArray(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSArray arr) => arr._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSArray?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSArray(JSValue value) => value.CastTo(); + public static explicit operator JSArray(JSObject obj) => (JSArray)(JSValue)obj; public static implicit operator JSObject(JSArray arr) => (JSObject)arr._value; @@ -45,6 +67,86 @@ public JSArray(JSValue[] array) } } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsArray(); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSArray IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSArray CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + /// public int Length => _value.GetArrayLength(); diff --git a/src/NodeApi/JSAsyncIterable.cs b/src/NodeApi/JSAsyncIterable.cs index e01b1289..3cdb444c 100644 --- a/src/NodeApi/JSAsyncIterable.cs +++ b/src/NodeApi/JSAsyncIterable.cs @@ -8,13 +8,37 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly partial struct JSAsyncIterable : IAsyncEnumerable, IEquatable +public readonly partial struct JSAsyncIterable : + IJSValue, IAsyncEnumerable { private readonly JSValue _value; - public static explicit operator JSAsyncIterable(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSAsyncIterable iterable) => iterable._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSAsyncIterable?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSAsyncIterable(JSValue value) + => value.CastTo(); + public static explicit operator JSAsyncIterable(JSObject obj) => (JSAsyncIterable)(JSValue)obj; public static implicit operator JSObject(JSAsyncIterable iterable) => (JSObject)iterable._value; @@ -23,6 +47,86 @@ private JSAsyncIterable(JSValue value) _value = value; } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsObject() && value.HasProperty(JSSymbol.AsyncIterator); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSAsyncIterable IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSAsyncIterable CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + #pragma warning disable IDE0060 // Unused parameter public Enumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => new(_value); diff --git a/src/NodeApi/JSBigInt.cs b/src/NodeApi/JSBigInt.cs index c2a5642e..249e3acd 100644 --- a/src/NodeApi/JSBigInt.cs +++ b/src/NodeApi/JSBigInt.cs @@ -7,13 +7,35 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly struct JSBigInt : IEquatable +public readonly struct JSBigInt : IJSValue { private readonly JSValue _value; - public static explicit operator JSBigInt(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSBigInt value) => value._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSBigInt?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSBigInt(JSValue value) => value.CastTo(); + public static implicit operator JSBigInt(BigInteger value) => new(value); public static explicit operator BigInteger(JSBigInt value) => value.ToBigInteger(); @@ -39,13 +61,91 @@ public JSBigInt(BigInteger value) : this(JSValue.CreateBigInt(value)) { } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsBigInt(); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSBigInt IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSBigInt CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + public int GetWordCount() => _value.GetBigIntWordCount(); public void CopyTo(Span destination, out int sign, out int wordCount) => _value.GetBigIntWords(destination, out sign, out wordCount); - public JSValue AsJSValue() => _value; - public BigInteger ToBigInteger() => _value.ToBigInteger(); public long ToInt64(out bool isLossless) => _value.ToInt64BigInt(out isLossless); diff --git a/src/NodeApi/JSDate.cs b/src/NodeApi/JSDate.cs index 91006c61..625c8d26 100644 --- a/src/NodeApi/JSDate.cs +++ b/src/NodeApi/JSDate.cs @@ -7,13 +7,35 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly struct JSDate : IEquatable +public readonly struct JSDate : IJSValue { private readonly JSValue _value; - public static explicit operator JSDate(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSDate date) => date._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSDate?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSDate(JSValue value) => value.CastTo(); + private JSDate(JSValue value) { _value = value; @@ -36,6 +58,87 @@ public JSDate(string dateString) public long DateValue => (long)_value.CallMethod("valueOf"); + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsDate(); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSDate IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSDate CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + + public static JSDate FromDateTime(DateTime value) { DateTimeKind kind = value.Kind; diff --git a/src/NodeApi/JSFunction.cs b/src/NodeApi/JSFunction.cs index 9fd7f61b..67e91e30 100644 --- a/src/NodeApi/JSFunction.cs +++ b/src/NodeApi/JSFunction.cs @@ -9,13 +9,35 @@ namespace Microsoft.JavaScript.NodeApi; /// /// Represents a JavaScript Function value. /// -public readonly struct JSFunction : IEquatable +public readonly struct JSFunction : IJSValue { private readonly JSValue _value; - public static explicit operator JSFunction(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSFunction function) => function._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSFunction?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSFunction(JSValue value) => value.CastTo(); + private JSFunction(JSValue value) { _value = value; @@ -267,6 +289,86 @@ public JSFunction( { } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsFunction(); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSFunction IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSFunction CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + /// /// Gets the name of the function, or an empty string if the function is unnamed. /// diff --git a/src/NodeApi/JSIterable.cs b/src/NodeApi/JSIterable.cs index 5b5d6641..f36c05cf 100644 --- a/src/NodeApi/JSIterable.cs +++ b/src/NodeApi/JSIterable.cs @@ -8,15 +8,34 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly partial struct JSIterable : IEnumerable, IEquatable +public readonly partial struct JSIterable : IJSValue, IEnumerable { private readonly JSValue _value; - public static explicit operator JSIterable(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSIterable iterable) => iterable._value; - public static explicit operator JSArray(JSIterable iterable) => (JSArray)iterable._value; - public static implicit operator JSIterable(JSArray array) => (JSIterable)(JSValue)array; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSIterable?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSIterable(JSValue value) => value.CastTo(); public static explicit operator JSIterable(JSObject obj) => (JSIterable)(JSValue)obj; public static implicit operator JSObject(JSIterable iterable) => (JSObject)iterable._value; @@ -26,6 +45,86 @@ private JSIterable(JSValue value) _value = value; } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsObject() && value.HasProperty(JSSymbol.Iterator); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSIterable IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSIterable CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + public Enumerator GetEnumerator() => new(_value); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/src/NodeApi/JSMap.cs b/src/NodeApi/JSMap.cs index 66c39b44..ef113e69 100644 --- a/src/NodeApi/JSMap.cs +++ b/src/NodeApi/JSMap.cs @@ -8,13 +8,35 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly partial struct JSMap : IDictionary, IEquatable +public readonly partial struct JSMap : IJSValue, IDictionary { private readonly JSValue _value; - public static explicit operator JSMap(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSMap map) => map._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSMap?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSMap(JSValue value) => value.CastTo(); + public static explicit operator JSMap(JSObject obj) => (JSMap)(JSValue)obj; public static implicit operator JSObject(JSMap map) => (JSObject)map._value; @@ -40,6 +62,86 @@ public JSMap(JSIterable iterable) _value = JSRuntimeContext.Current.Import(null, "Map").CallAsConstructor(iterable); } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsObject() && value.InstanceOf(JSValue.Global["Map"]); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSMap IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSMap CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + public int Count => (int)_value["size"]; public Enumerator GetEnumerator() => new(_value); diff --git a/src/NodeApi/JSObject.cs b/src/NodeApi/JSObject.cs index 891fc8dd..4ff1be65 100644 --- a/src/NodeApi/JSObject.cs +++ b/src/NodeApi/JSObject.cs @@ -8,13 +8,35 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly partial struct JSObject : IDictionary, IEquatable +public readonly partial struct JSObject : IJSValue, IDictionary { private readonly JSValue _value; - public static explicit operator JSObject(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSObject obj) => obj._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSObject?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSObject(JSValue value) => value.CastTo(); + private JSObject(JSValue value) { _value = value; @@ -24,6 +46,86 @@ public JSObject() : this(JSValue.CreateObject()) { } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsObject(); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSObject IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSObject CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + public JSObject(IEnumerable> properties) : this() { foreach (KeyValuePair property in properties) diff --git a/src/NodeApi/JSPromise.cs b/src/NodeApi/JSPromise.cs index d670a40b..ccd10ea8 100644 --- a/src/NodeApi/JSPromise.cs +++ b/src/NodeApi/JSPromise.cs @@ -14,13 +14,35 @@ namespace Microsoft.JavaScript.NodeApi; /// Represents a JavaScript Promise object. /// /// -public readonly struct JSPromise : IEquatable +public readonly struct JSPromise : IJSValue { private readonly JSValue _value; - public static explicit operator JSPromise(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSPromise promise) => promise._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSPromise?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSPromise(JSValue value) => value.CastTo(); + public static explicit operator JSPromise(JSObject obj) => (JSPromise)(JSValue)obj; public static implicit operator JSObject(JSPromise promise) => (JSObject)promise._value; @@ -143,6 +165,86 @@ private static async void ResolveDeferred( } } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsPromise(); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSPromise IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSPromise CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + /// /// Registers callbacks that are invoked when a promise is fulfilled and/or rejected, /// and returns a new chained promise. diff --git a/src/NodeApi/JSProxy.cs b/src/NodeApi/JSProxy.cs index 71de1298..ce4e9f23 100644 --- a/src/NodeApi/JSProxy.cs +++ b/src/NodeApi/JSProxy.cs @@ -12,14 +12,36 @@ namespace Microsoft.JavaScript.NodeApi; /// /// Enables creation of JS Proxy objects with C# handler callbacks. /// -public readonly partial struct JSProxy : IEquatable +public readonly partial struct JSProxy : IJSValue { private readonly JSValue _value; private readonly JSValue _revoke = default; - public static explicit operator JSProxy(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSProxy proxy) => proxy._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSProxy?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSProxy(JSValue value) => value.CastTo(); + private JSProxy(JSValue value) { _value = value; @@ -65,6 +87,90 @@ public JSProxy( } } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + { + // According to JavaScript specification we cannot differentiate Proxy instance + // from other objects. + return value.IsObject(); + } + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSProxy IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSProxy CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + /// /// Revokes the proxy, so that further access to the target is no longer trapped by /// the proxy handler. diff --git a/src/NodeApi/JSSet.cs b/src/NodeApi/JSSet.cs index b07b2a7f..d6041af1 100644 --- a/src/NodeApi/JSSet.cs +++ b/src/NodeApi/JSSet.cs @@ -9,13 +9,35 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly partial struct JSSet : ISet, IEquatable +public readonly partial struct JSSet : IJSValue, ISet { private readonly JSValue _value; - public static explicit operator JSSet(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSSet set) => set._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSSet?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSSet(JSValue value) => value.CastTo(); + public static explicit operator JSSet(JSObject obj) => (JSSet)(JSValue)obj; public static implicit operator JSObject(JSSet set) => (JSObject)set._value; @@ -44,6 +66,86 @@ public JSSet(JSIterable iterable) _value = JSRuntimeContext.Current.Import(null, "Set").CallAsConstructor(iterable); } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsObject() && value.InstanceOf(JSValue.Global["Set"]); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSSet IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSSet CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + public int Count => (int)_value["size"]; bool ICollection.IsReadOnly => false; diff --git a/src/NodeApi/JSSymbol.cs b/src/NodeApi/JSSymbol.cs index 2b5e4049..f1babbf8 100644 --- a/src/NodeApi/JSSymbol.cs +++ b/src/NodeApi/JSSymbol.cs @@ -6,7 +6,7 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly struct JSSymbol : IEquatable +public readonly struct JSSymbol : IJSValue { private readonly JSValue _value; @@ -16,9 +16,31 @@ namespace Microsoft.JavaScript.NodeApi; private static readonly Lazy s_asyncIteratorSymbol = new(() => new JSReference(JSValue.Global["Symbol"]["asyncIterator"])); - public static explicit operator JSSymbol(JSValue value) => new(value); + /// + /// Implicitly converts a to a . + /// + /// The to convert. public static implicit operator JSValue(JSSymbol symbol) => symbol._value; + /// + /// Explicitly converts a to a nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or `null` if it was failed. + /// + public static explicit operator JSSymbol?(JSValue value) => value.As(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSSymbol(JSValue value) => value.CastTo(); + private JSSymbol(JSValue value) { _value = value; @@ -29,6 +51,86 @@ public JSSymbol(string? description = null) _value = JSValue.CreateSymbol(description ?? JSValue.Undefined); } + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public T AsUnchecked() where T : struct, IJSValue => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public T CastTo() where T : struct, IJSValue => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsSymbol(); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSSymbol IJSValue.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSSymbol CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + /// /// Gets the symbol's description, or null if it does not have one. /// diff --git a/src/NodeApi/JSTypedArray.cs b/src/NodeApi/JSTypedArray.cs index 16324186..58b38b38 100644 --- a/src/NodeApi/JSTypedArray.cs +++ b/src/NodeApi/JSTypedArray.cs @@ -9,12 +9,39 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly struct JSTypedArray : IEquatable where T : struct +//TODO: Add support for Uint8ClampedArray +public readonly struct JSTypedArray : IJSValue> + where T : struct { private readonly JSValue _value; - public static explicit operator JSTypedArray(JSValue value) => new(value); - public static implicit operator JSValue(JSTypedArray arr) => arr._value; + /// + /// Implicitly converts a to a . + /// + /// The to convert. + public static implicit operator JSValue(JSTypedArray array) => array._value; + + /// + /// Explicitly converts a to a + /// nullable . + /// + /// The to convert. + /// + /// The if it was successfully created or + /// `null` if it was failed. + /// + public static explicit operator JSTypedArray?(JSValue value) => value.As>(); + + /// + /// Explicitly converts a to a . + /// + /// The to convert. + /// struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be created based on this `JSValue`. + /// + public static explicit operator JSTypedArray(JSValue value) + => value.CastTo>(); private static int ElementSize { get; } = default(T) switch { @@ -46,6 +73,21 @@ namespace Microsoft.JavaScript.NodeApi; _ => throw new InvalidCastException("Invalid typed-array type: " + typeof(T)), }; + private static string JSTypeName { get; } = default(T) switch + { + sbyte => "Int8Array", + byte => "Uint8Array", + short => "Int16Array", + ushort => "Uint16Array", + int => "Int32Array", + uint => "Uint32Array", + long => "BigInt64Array", + ulong => "BigUint64Array", + float => "Float32Array", + double => "Float64Array", + _ => throw new InvalidCastException("Invalid typed-array type: " + typeof(T)), + }; + private JSTypedArray(JSValue value) { _value = value; @@ -101,6 +143,90 @@ public unsafe JSTypedArray(ReadOnlyMemory data) } } + #region IJSValue> implementation + + /// + /// Checks if the T struct can be created from this instance`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this instance. Otherwise it returns `false`. + /// + public bool Is() where TOther : struct, IJSValue + => _value.Is(); + + /// + /// Tries to create a T struct from this instance. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public TOther? As() where TOther : struct, IJSValue + => _value.As(); + + /// + /// Creates a T struct from this instance without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + public TOther AsUnchecked() where TOther : struct, IJSValue + => _value.AsUnchecked(); + + /// + /// Creates a T struct from this instance. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this instance. + /// + /// Thrown when the T struct cannot be crated based on this instance. + /// + public TOther CastTo() where TOther : struct, IJSValue + => _value.CastTo(); + + /// + /// Determines whether a can be created from + /// the specified . + /// + /// The to check. + /// + /// true if a can be created from + /// the specified ; otherwise, false. + /// +#if NET7_0_OR_GREATER + static bool IJSValue>.CanCreateFrom(JSValue value) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue value) +#pragma warning restore IDE0051 +#endif + => value.IsObject() && value.InstanceOf(JSValue.Global[JSTypeName]); + + /// + /// Creates a new instance of from + /// the specified . + /// + /// + /// The to create a from. + /// + /// + /// A new instance of created from + /// the specified . + /// +#if NET7_0_OR_GREATER + static JSTypedArray IJSValue>.CreateUnchecked(JSValue value) => new(value); +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSTypedArray CreateUnchecked(JSValue value) => new(value); +#pragma warning restore IDE0051 +#endif + + #endregion + /// /// Checks if this Memory is already owned by a JS TypedArray value, and if so /// returns that JS value. diff --git a/src/NodeApi/JSValue.cs b/src/NodeApi/JSValue.cs index 07289740..421b0273 100644 --- a/src/NodeApi/JSValue.cs +++ b/src/NodeApi/JSValue.cs @@ -15,7 +15,7 @@ namespace Microsoft.JavaScript.NodeApi; -public readonly struct JSValue : IEquatable +public readonly struct JSValue : IJSValue { private readonly napi_value _handle = default; private readonly JSValueScope? _scope = null; @@ -391,6 +391,80 @@ public JSValueType TypeOf() => _handle.IsNull public bool IsBigInt() => TypeOf() == JSValueType.BigInt; + #region IJSValue implementation + + /// + /// Checks if the T struct can be created from this `JSValue`. + /// + /// A struct that implements IJSValue interface. + /// + /// `true` if the T struct can be created from this `JSValue`. Otherwise it returns `false`. + /// + public bool Is() where T : struct, IJSValue +#if NET7_0_OR_GREATER + => T.CanCreateFrom(this); +#else + => IJSValueShim.CanCreateFrom(this); +#endif + + /// + /// Tries to create a T struct from this `JSValue`. + /// It returns `null` if the T struct cannot be created. + /// + /// A struct that implements IJSValue interface. + /// + /// Nullable value that contains T struct if it was successfully created + /// or `null` if it was failed. + /// + public T? As() where T : struct, IJSValue + => Is() ? AsUnchecked() : default(T?); + + /// + /// Creates a T struct from this `JSValue` without checking the enclosed handle type. + /// It must be used only when the handle type is known to be correct. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this `JSValue`. + public T AsUnchecked() where T : struct, IJSValue +#if NET7_0_OR_GREATER + => T.CreateUnchecked(this); +#else + => IJSValueShim.CreateUnchecked(this); +#endif + + /// + /// Creates a T struct from this `JSValue`. + /// It throws `InvalidCastException` in case of failure. + /// + /// A struct that implements IJSValue interface. + /// T struct created based on this `JSValue`. + /// + /// Thrown when the T struct cannot be crated based on this `JSValue`. + /// + public T CastTo() where T : struct, IJSValue + => As() + ?? throw new InvalidCastException( + $"JSValue cannot be casted to target type {typeof(T).Name}."); + +#if NET7_0_OR_GREATER + static bool IJSValue.CanCreateFrom(JSValue _) +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static bool CanCreateFrom(JSValue _) +#pragma warning restore IDE0051 +#endif + => true; + +#if NET7_0_OR_GREATER + static JSValue IJSValue.CreateUnchecked(JSValue value) => value; +#else +#pragma warning disable IDE0051 // It is used by the IJSValueShim class through reflection. + private static JSValue CreateUnchecked(JSValue value) => value; +#pragma warning restore IDE0051 +#endif + + #endregion + public double GetValueDouble() => GetRuntime(out napi_env env, out napi_value handle) .GetValueDouble(env, handle, out double result).ThrowIfFailed(result); diff --git a/src/node-api-dotnet/pack.js b/src/node-api-dotnet/pack.js index 4a7f8667..32890aec 100644 --- a/src/node-api-dotnet/pack.js +++ b/src/node-api-dotnet/pack.js @@ -70,6 +70,7 @@ function packMainPackage() { `NodeApi/${assemblyName}.runtimeconfig.json`, `NodeApi/${assemblyName}.dll`, `NodeApi.DotNetHost/${assemblyName}.DotNetHost.dll`, + `NodeApi/Microsoft.Bcl.AsyncInterfaces.dll`, `NodeApi/System.Memory.dll`, `NodeApi/System.Runtime.CompilerServices.Unsafe.dll`, `NodeApi/System.Threading.Tasks.Extensions.dll`, @@ -171,6 +172,12 @@ function copyFrameworkSpecificBinaries(targetFrameworks, packageStageDir, ...bin binFileName.startsWith('System.') && !binFileName.includes('MetadataLoadContext') ) return; + + // Exclude Microsoft.Bcl.AsyncInterfaces from new platforms + if ( + tfm.includes('.') && + binFileName.startsWith('Microsoft.Bcl.AsyncInterfaces') + ) return; const binPath = path.join(outBinDir, projectName, tfm, rids[0], binFileName); copyFile(binPath, path.join(tfmStageDir, binFileName)); diff --git a/test/TestCases/napi-dotnet/JSValueCast.cs b/test/TestCases/napi-dotnet/JSValueCast.cs new file mode 100644 index 00000000..a322a0b7 --- /dev/null +++ b/test/TestCases/napi-dotnet/JSValueCast.cs @@ -0,0 +1,571 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma warning disable IDE0060 // Unused parameters +#pragma warning disable IDE0301 // Collection initialization can be simplified + +using System; +using System.Collections.Generic; +using System.Numerics; +using Interop = Microsoft.JavaScript.NodeApi.Interop; + +namespace Microsoft.JavaScript.NodeApi.TestCases; + +/// +/// Tests type casting between JSValue and IJSValue derived types. +/// +[JSExport] +public static class JSValueCast +{ + #region JSAbortSignal + + public static string ValueAsAbortSignal(JSValue value) + => (Interop.JSAbortSignal?)value is not null ? "ok" : "failed"; + + public static string ValueIsAbortSignal(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToAbortSignal(JSValue value) + { + try + { + Interop.JSAbortSignal signal = (Interop.JSAbortSignal)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSArray + + public static string ValueAsArray(JSValue value) + => (JSArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsArray(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToArray(JSValue value) + { + try + { + JSArray signal = (JSArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSAsyncIterable + + public static string ValueAsAsyncIterable(JSValue value) + => (JSAsyncIterable?)value is not null ? "ok" : "failed"; + + public static string ValueIsAsyncIterable(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToAsyncIterable(JSValue value) + { + try + { + JSAsyncIterable signal = (JSAsyncIterable)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSBigInt + + public static string ValueAsBigInt(JSValue value) + => (JSBigInt?)value is not null ? "ok" : "failed"; + + public static string ValueIsBigInt(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToBigInt(JSValue value) + { + try + { + JSBigInt signal = (JSBigInt)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSDate + + public static string ValueAsDate(JSValue value) + => (JSDate?)value is not null ? "ok" : "failed"; + + public static string ValueIsDate(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToDate(JSValue value) + { + try + { + JSDate signal = (JSDate)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSFunction + + public static string ValueAsFunction(JSValue value) + => (JSFunction?)value is not null ? "ok" : "failed"; + + public static string ValueIsFunction(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToFunction(JSValue value) + { + try + { + JSFunction signal = (JSFunction)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSIterable + + public static string ValueAsIterable(JSValue value) + => (JSIterable?)value is not null ? "ok" : "failed"; + + public static string ValueIsIterable(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToIterable(JSValue value) + { + try + { + JSIterable signal = (JSIterable)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSMap + + public static string ValueAsMap(JSValue value) + => (JSMap?)value is not null ? "ok" : "failed"; + + public static string ValueIsMap(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToMap(JSValue value) + { + try + { + JSMap signal = (JSMap)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSObject + + public static string ValueAsObject(JSValue value) + => (JSObject?)value is not null ? "ok" : "failed"; + + public static string ValueIsObject(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToObject(JSValue value) + { + try + { + JSObject signal = (JSObject)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSPromise + + public static string ValueAsPromise(JSValue value) + => (JSPromise?)value is not null ? "ok" : "failed"; + + public static string ValueIsPromise(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToPromise(JSValue value) + { + try + { + JSPromise signal = (JSPromise)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSProxy + + public static string ValueAsProxy(JSValue value) + => (JSProxy?)value is not null ? "ok" : "failed"; + + public static string ValueIsProxy(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToProxy(JSValue value) + { + try + { + JSProxy signal = (JSProxy)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSSet + + public static string ValueAsSet(JSValue value) + => (JSSet?)value is not null ? "ok" : "failed"; + + public static string ValueIsSet(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToSet(JSValue value) + { + try + { + JSSet signal = (JSSet)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSSymbol + + public static string ValueAsSymbol(JSValue value) + => (JSSymbol?)value is not null ? "ok" : "failed"; + + public static string ValueIsSymbol(JSValue value) + => value.Is() ? "ok" : "failed"; + + public static string ValueCastToSymbol(JSValue value) + { + try + { + JSSymbol signal = (JSSymbol)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSTypedArray + + public static string ValueAsTypedArrayInt8(JSValue value) + => (JSTypedArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsTypedArrayInt8(JSValue value) + => value.Is>() ? "ok" : "failed"; + + public static string ValueCastToTypedArrayInt8(JSValue value) + { + try + { + JSTypedArray signal = (JSTypedArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSTypedArray + + public static string ValueAsTypedArrayUint8(JSValue value) + => (JSTypedArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsTypedArrayUint8(JSValue value) + => value.Is>() ? "ok" : "failed"; + + public static string ValueCastToTypedArrayUint8(JSValue value) + { + try + { + JSTypedArray signal = (JSTypedArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSTypedArray + + public static string ValueAsTypedArrayInt16(JSValue value) + => (JSTypedArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsTypedArrayInt16(JSValue value) + => value.Is>() ? "ok" : "failed"; + + public static string ValueCastToTypedArrayInt16(JSValue value) + { + try + { + JSTypedArray signal = (JSTypedArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSTypedArray + + public static string ValueAsTypedArrayUint16(JSValue value) + => (JSTypedArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsTypedArrayUint16(JSValue value) + => value.Is>() ? "ok" : "failed"; + + public static string ValueCastToTypedArrayUint16(JSValue value) + { + try + { + JSTypedArray signal = (JSTypedArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSTypedArray + + public static string ValueAsTypedArrayInt32(JSValue value) + => (JSTypedArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsTypedArrayInt32(JSValue value) + => value.Is>() ? "ok" : "failed"; + + public static string ValueCastToTypedArrayInt32(JSValue value) + { + try + { + JSTypedArray signal = (JSTypedArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSTypedArray + + public static string ValueAsTypedArrayUint32(JSValue value) + => (JSTypedArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsTypedArrayUint32(JSValue value) + => value.Is>() ? "ok" : "failed"; + + public static string ValueCastToTypedArrayUint32(JSValue value) + { + try + { + JSTypedArray signal = (JSTypedArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSTypedArray + + public static string ValueAsTypedArrayBigInt64(JSValue value) + => (JSTypedArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsTypedArrayBigInt64(JSValue value) + => value.Is>() ? "ok" : "failed"; + + public static string ValueCastToTypedArrayBigInt64(JSValue value) + { + try + { + JSTypedArray signal = (JSTypedArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSTypedArray + + public static string ValueAsTypedArrayBigUint64(JSValue value) + => (JSTypedArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsTypedArrayBigUint64(JSValue value) + => value.Is>() ? "ok" : "failed"; + + public static string ValueCastToTypedArrayBigUint64(JSValue value) + { + try + { + JSTypedArray signal = (JSTypedArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSTypedArray + + public static string ValueAsTypedArrayFloat32(JSValue value) + => (JSTypedArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsTypedArrayFloat32(JSValue value) + => value.Is>() ? "ok" : "failed"; + + public static string ValueCastToTypedArrayFloat32(JSValue value) + { + try + { + JSTypedArray signal = (JSTypedArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion + + #region JSTypedArray + + public static string ValueAsTypedArrayFloat64(JSValue value) + => (JSTypedArray?)value is not null ? "ok" : "failed"; + + public static string ValueIsTypedArrayFloat64(JSValue value) + => value.Is>() ? "ok" : "failed"; + + public static string ValueCastToTypedArrayFloat64(JSValue value) + { + try + { + JSTypedArray signal = (JSTypedArray)value; + JSValue value2 = signal; + return value.Handle == value2.Handle ? "ok" : "failed roundrip"; + } + catch (InvalidCastException) + { + return "failed"; + } + } + + #endregion +} diff --git a/test/TestCases/napi-dotnet/jsvalue_cast.js b/test/TestCases/napi-dotnet/jsvalue_cast.js new file mode 100644 index 00000000..622e5266 --- /dev/null +++ b/test/TestCases/napi-dotnet/jsvalue_cast.js @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const assert = require('assert'); + +/** @type {import('./napi-dotnet')} */ +const binding = require('../common').binding; + +const JSValueCast = binding.JSValueCast; +assert.strictEqual(typeof JSValueCast, 'object'); + +assert.strictEqual(typeof JSValueCast.valueAsAbortSignal, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsAbortSignal, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToAbortSignal, 'function'); +assert.strictEqual(JSValueCast.valueAsAbortSignal(AbortSignal.abort()), "ok"); +assert.strictEqual(JSValueCast.valueAsAbortSignal({}), "failed"); +assert.strictEqual(JSValueCast.valueIsAbortSignal(AbortSignal.abort()), "ok"); +assert.strictEqual(JSValueCast.valueIsAbortSignal({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToAbortSignal(AbortSignal.abort()), "ok"); +assert.strictEqual(JSValueCast.valueCastToAbortSignal({}), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsArray, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsArray, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToArray, 'function'); +assert.strictEqual(JSValueCast.valueAsArray(["foo"]), "ok"); +assert.strictEqual(JSValueCast.valueAsArray({}), "failed"); +assert.strictEqual(JSValueCast.valueIsArray(["foo"]), "ok"); +assert.strictEqual(JSValueCast.valueIsArray({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToArray(["foo"]), "ok"); +assert.strictEqual(JSValueCast.valueCastToArray({}), "failed"); + +const asyncIterable = { + async *[Symbol.asyncIterator]() { + yield await Promise.resolve("a"); + }, +}; +const asyncIterable2 = Object.create(asyncIterable); + +assert.strictEqual(typeof JSValueCast.valueAsAsyncIterable, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsAsyncIterable, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToAsyncIterable, 'function'); +assert.strictEqual(JSValueCast.valueAsAsyncIterable(asyncIterable), "ok"); +assert.strictEqual(JSValueCast.valueAsAsyncIterable(asyncIterable2), "ok"); +assert.strictEqual(JSValueCast.valueAsAsyncIterable({}), "failed"); +assert.strictEqual(JSValueCast.valueIsAsyncIterable(asyncIterable), "ok"); +assert.strictEqual(JSValueCast.valueIsAsyncIterable(asyncIterable2), "ok"); +assert.strictEqual(JSValueCast.valueIsAsyncIterable({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToAsyncIterable(asyncIterable), "ok"); +assert.strictEqual(JSValueCast.valueCastToAsyncIterable(asyncIterable2), "ok"); +assert.strictEqual(JSValueCast.valueCastToAsyncIterable({}), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsBigInt, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsBigInt, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToBigInt, 'function'); +assert.strictEqual(JSValueCast.valueAsBigInt(42n), "ok"); +assert.strictEqual(JSValueCast.valueAsBigInt({}), "failed"); +assert.strictEqual(JSValueCast.valueIsBigInt(42n), "ok"); +assert.strictEqual(JSValueCast.valueIsBigInt({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToBigInt(42n), "ok"); +assert.strictEqual(JSValueCast.valueCastToBigInt({}), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsDate, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsDate, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToDate, 'function'); +assert.strictEqual(JSValueCast.valueAsDate(new Date()), "ok"); +assert.strictEqual(JSValueCast.valueAsDate({}), "failed"); +assert.strictEqual(JSValueCast.valueIsDate(new Date()), "ok"); +assert.strictEqual(JSValueCast.valueIsDate({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToDate(new Date()), "ok"); +assert.strictEqual(JSValueCast.valueCastToDate({}), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsFunction, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsFunction, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToFunction, 'function'); +assert.strictEqual(JSValueCast.valueAsFunction(() => { }), "ok"); +assert.strictEqual(JSValueCast.valueAsFunction({}), "failed"); +assert.strictEqual(JSValueCast.valueIsFunction(() => { }), "ok"); +assert.strictEqual(JSValueCast.valueIsFunction({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToFunction(() => { }), "ok"); +assert.strictEqual(JSValueCast.valueCastToFunction({}), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsIterable, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsIterable, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToIterable, 'function'); +assert.strictEqual(JSValueCast.valueAsIterable([]), "ok"); +assert.strictEqual(JSValueCast.valueAsIterable({}), "failed"); +assert.strictEqual(JSValueCast.valueIsIterable([]), "ok"); +assert.strictEqual(JSValueCast.valueIsIterable({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToIterable([]), "ok"); +assert.strictEqual(JSValueCast.valueCastToIterable({}), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsMap, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsMap, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToMap, 'function'); +assert.strictEqual(JSValueCast.valueAsMap(new Map()), "ok"); +assert.strictEqual(JSValueCast.valueAsMap({}), "failed"); +assert.strictEqual(JSValueCast.valueIsMap(new Map()), "ok"); +assert.strictEqual(JSValueCast.valueIsMap({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToMap(new Map()), "ok"); +assert.strictEqual(JSValueCast.valueCastToMap({}), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsObject, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsObject, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToObject, 'function'); +assert.strictEqual(JSValueCast.valueAsObject({}), "ok"); +assert.strictEqual(JSValueCast.valueAsObject(""), "failed"); +assert.strictEqual(JSValueCast.valueIsObject({}), "ok"); +assert.strictEqual(JSValueCast.valueIsObject(""), "failed"); +assert.strictEqual(JSValueCast.valueCastToObject({}), "ok"); +assert.strictEqual(JSValueCast.valueCastToObject(""), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsPromise, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsPromise, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToPromise, 'function'); +assert.strictEqual(JSValueCast.valueAsPromise(Promise.resolve(123)), "ok"); +assert.strictEqual(JSValueCast.valueAsPromise({}), "failed"); +assert.strictEqual(JSValueCast.valueIsPromise(Promise.resolve(123)), "ok"); +assert.strictEqual(JSValueCast.valueIsPromise({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToPromise(Promise.resolve(123)), "ok"); +assert.strictEqual(JSValueCast.valueCastToPromise({}), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsProxy, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsProxy, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToProxy, 'function'); +assert.strictEqual(JSValueCast.valueAsProxy(new Proxy({}, {})), "ok"); +assert.strictEqual(JSValueCast.valueAsProxy("1"), "failed"); +assert.strictEqual(JSValueCast.valueIsProxy(new Proxy({}, {})), "ok"); +assert.strictEqual(JSValueCast.valueIsProxy("1"), "failed"); +assert.strictEqual(JSValueCast.valueCastToProxy(new Proxy({}, {})), "ok"); +assert.strictEqual(JSValueCast.valueCastToProxy("1"), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsSet, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsSet, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToSet, 'function'); +assert.strictEqual(JSValueCast.valueAsSet(new Set()), "ok"); +assert.strictEqual(JSValueCast.valueAsSet({}), "failed"); +assert.strictEqual(JSValueCast.valueIsSet(new Set()), "ok"); +assert.strictEqual(JSValueCast.valueIsSet({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToSet(new Set()), "ok"); +assert.strictEqual(JSValueCast.valueCastToSet({}), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsSymbol, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsSymbol, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToSymbol, 'function'); +assert.strictEqual(JSValueCast.valueAsSymbol(Symbol.iterator), "ok"); +assert.strictEqual(JSValueCast.valueAsSymbol({}), "failed"); +assert.strictEqual(JSValueCast.valueIsSymbol(Symbol.iterator), "ok"); +assert.strictEqual(JSValueCast.valueIsSymbol({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToSymbol(Symbol.iterator), "ok"); +assert.strictEqual(JSValueCast.valueCastToSymbol({}), "failed"); + +assert.strictEqual(typeof JSValueCast.valueAsTypedArrayInt8, 'function'); +assert.strictEqual(typeof JSValueCast.valueIsTypedArrayInt8, 'function'); +assert.strictEqual(typeof JSValueCast.valueCastToTypedArrayInt8, 'function'); +assert.strictEqual(JSValueCast.valueAsTypedArrayInt8(new Int8Array(8)), "ok"); +assert.strictEqual(JSValueCast.valueAsTypedArrayInt8({}), "failed"); +assert.strictEqual(JSValueCast.valueIsTypedArrayInt8(new Int8Array(8)), "ok"); +assert.strictEqual(JSValueCast.valueIsTypedArrayInt8({}), "failed"); +assert.strictEqual(JSValueCast.valueCastToTypedArrayInt8(new Int8Array(8)), "ok"); +assert.strictEqual(JSValueCast.valueCastToTypedArrayInt8({}), "failed");