diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 716151034..67a51aa2e 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -81,6 +81,22 @@ try test("Value Construction") { let prop_7 = getJSValue(this: globalObject1Ref, name: "prop_7") try expectEqual(Double.construct(from: prop_7), 3.14) try expectEqual(Float.construct(from: prop_7), 3.14) + + for source: JSValue in [ + .number(.infinity), .number(.nan), + .number(Double(UInt64.max).nextUp), .number(Double(Int64.min).nextDown) + ] { + try expectNil(Int.construct(from: source)) + try expectNil(Int8.construct(from: source)) + try expectNil(Int16.construct(from: source)) + try expectNil(Int32.construct(from: source)) + try expectNil(Int64.construct(from: source)) + try expectNil(UInt.construct(from: source)) + try expectNil(UInt8.construct(from: source)) + try expectNil(UInt16.construct(from: source)) + try expectNil(UInt32.construct(from: source)) + try expectNil(UInt64.construct(from: source)) + } } try test("Array Iterator") { @@ -244,10 +260,11 @@ try test("Closure Lifetime") { #if JAVASCRIPTKIT_WITHOUT_WEAKREFS // Check diagnostics of use-after-free do { + let c1Line = #line + 1 let c1 = JSClosure { $0[0] } c1.release() let error = try expectThrow(try evalClosure.throws(c1, JSValue.number(42.0))) as! JSValue - try expect("Error message should contains definition location", error.description.hasSuffix("PrimaryTests/main.swift:247")) + try expect("Error message should contains definition location", error.description.hasSuffix("PrimaryTests/main.swift:\(c1Line)")) } #endif diff --git a/Sources/JavaScriptKit/ConstructibleFromJSValue.swift b/Sources/JavaScriptKit/ConstructibleFromJSValue.swift index 1f43658f0..ce1e1c25f 100644 --- a/Sources/JavaScriptKit/ConstructibleFromJSValue.swift +++ b/Sources/JavaScriptKit/ConstructibleFromJSValue.swift @@ -35,15 +35,41 @@ extension Double: ConstructibleFromJSValue {} extension Float: ConstructibleFromJSValue {} extension SignedInteger where Self: ConstructibleFromJSValue { + /// Construct an instance of `SignedInteger` from the given `JSBigIntExtended`. + /// + /// If the value is too large to fit in the `Self` type, `nil` is returned. + /// + /// - Parameter bigInt: The `JSBigIntExtended` to decode + public init?(exactly bigInt: JSBigIntExtended) { + self.init(exactly: bigInt.int64Value) + } + + /// Construct an instance of `SignedInteger` from the given `JSBigIntExtended`. + /// + /// Crash if the value is too large to fit in the `Self` type. + /// + /// - Parameter bigInt: The `JSBigIntExtended` to decode public init(_ bigInt: JSBigIntExtended) { self.init(bigInt.int64Value) } + + /// Construct an instance of `SignedInteger` from the given `JSValue`. + /// + /// Returns `nil` if one of the following conditions is met: + /// - The value is not a number or a bigint. + /// - The value is a number that does not fit or cannot be represented + /// in the `Self` type (e.g. NaN, Infinity). + /// - The value is a bigint that does not fit in the `Self` type. + /// + /// If the value is a number, it is rounded towards zero before conversion. + /// + /// - Parameter value: The `JSValue` to decode public static func construct(from value: JSValue) -> Self? { if let number = value.number { - return Self(number) + return Self(exactly: number.rounded(.towardZero)) } if let bigInt = value.bigInt as? JSBigIntExtended { - return Self(bigInt) + return Self(exactly: bigInt) } return nil } @@ -55,15 +81,41 @@ extension Int32: ConstructibleFromJSValue {} extension Int64: ConstructibleFromJSValue {} extension UnsignedInteger where Self: ConstructibleFromJSValue { + + /// Construct an instance of `UnsignedInteger` from the given `JSBigIntExtended`. + /// + /// Returns `nil` if the value is negative or too large to fit in the `Self` type. + /// + /// - Parameter bigInt: The `JSBigIntExtended` to decode + public init?(exactly bigInt: JSBigIntExtended) { + self.init(exactly: bigInt.uInt64Value) + } + + /// Construct an instance of `UnsignedInteger` from the given `JSBigIntExtended`. + /// + /// Crash if the value is negative or too large to fit in the `Self` type. + /// + /// - Parameter bigInt: The `JSBigIntExtended` to decode public init(_ bigInt: JSBigIntExtended) { self.init(bigInt.uInt64Value) } + + /// Construct an instance of `UnsignedInteger` from the given `JSValue`. + /// + /// Returns `nil` if one of the following conditions is met: + /// - The value is not a number or a bigint. + /// - The value is a number that does not fit or cannot be represented + /// in the `Self` type (e.g. NaN, Infinity). + /// - The value is a bigint that does not fit in the `Self` type. + /// - The value is negative. + /// + /// - Parameter value: The `JSValue` to decode public static func construct(from value: JSValue) -> Self? { if let number = value.number { - return Self(number) + return Self(exactly: number.rounded(.towardZero)) } if let bigInt = value.bigInt as? JSBigIntExtended { - return Self(bigInt) + return Self(exactly: bigInt) } return nil }