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
2 changes: 1 addition & 1 deletion Sources/JavaScriptPersistence/Array (ext).swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ extension Array: ConvertibleToJSValue where Element: ConvertibleToJSValue {
}
extension Array: ConstructibleFromJSValue where Element: ConstructibleFromJSValue {
@inlinable public static func construct(from value: JSValue) -> [Element]? {
guard case .object(let object) = value, object.isArray else {
guard case .object(let object) = value.storage, object.isArray else {
return nil
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
extension BinaryFloatingPoint where Self: ConstructibleFromJSValue {
@inlinable public static func construct(from value: JSValue) -> Self? {
switch value {
switch value.storage {
case .number(let value):
return Self.init(value)
case .bigInt(let value):
Expand Down
4 changes: 2 additions & 2 deletions Sources/JavaScriptPersistence/ConstructibleFromJSValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ public protocol ConstructibleFromJSValue {
}
extension ConstructibleFromJSValue where Self: SignedInteger {
@inlinable public static func construct(from value: JSValue) -> Self? {
switch value {
switch value.storage {
case .number(let value): .init(exactly: value)
case .bigInt(let value): .init(exactly: value.int128)
default: nil
Expand All @@ -12,7 +12,7 @@ extension ConstructibleFromJSValue where Self: SignedInteger {
}
extension ConstructibleFromJSValue where Self: UnsignedInteger {
@inlinable public static func construct(from value: JSValue) -> Self? {
switch value {
switch value.storage {
case .number(let number): Self.init(exactly: number)
case .bigInt(let bigInt): Self.init(exactly: bigInt.int128)
default: nil
Expand Down
2 changes: 1 addition & 1 deletion Sources/JavaScriptPersistence/JSString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extension JSString: ConvertibleToJSValue {
}
extension JSString: ConstructibleFromJSValue {
@inlinable public static func construct(from value: JSValue) -> JSString? {
guard case .string(let string) = value else {
guard case .string(let string) = value.storage else {
return nil
}
return string
Expand Down
12 changes: 12 additions & 0 deletions Sources/JavaScriptPersistence/JSValue.Storage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
extension JSValue {
@frozen @usableFromInline enum Storage {
case boolean(Bool)
case string(JSString)
case number(Double)
case object(JSObject)
case null
case undefined
case symbol(JSSymbol)
case bigInt(JSBigInt)
}
}
76 changes: 50 additions & 26 deletions Sources/JavaScriptPersistence/JSValue.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
import JSON

@frozen public enum JSValue {
case boolean(Bool)
case string(JSString)
case number(Double)
case object(JSObject)
case null
case undefined
case symbol(JSSymbol)
case bigInt(JSBigInt)
@frozen public struct JSValue {
@usableFromInline let storage: Storage
@inlinable init(storage: Storage) {
self.storage = storage
}
}
extension JSValue {
@inlinable public static func boolean(_ value: Bool) -> Self {
.init(storage: .boolean(value))
}
@inlinable public static func string(_ value: JSString) -> Self {
.init(storage: .string(value))
}
@inlinable public static func number(_ value: Double) -> Self {
.init(storage: .number(value))
}
@inlinable public static func object(_ value: JSObject) -> Self {
.init(storage: .object(value))
}
@inlinable public static var null: Self {
.init(storage: .null)
}
@inlinable public static var undefined: Self {
.init(storage: .undefined)
}
@inlinable public static func symbol(_ value: JSSymbol) -> Self {
.init(storage: .symbol(value))
}
@inlinable public static func bigInt(_ value: JSBigInt) -> Self {
.init(storage: .bigInt(value))
}
}
extension JSValue: JSONEncodable {
public func encode(to json: inout JSON) {
switch self {
switch self.storage {
case .null:
(nil as Never?).encode(to: &json)
case .boolean(let js):
Expand Down Expand Up @@ -108,64 +130,66 @@ extension JSValue: ConvertibleToJSValue {
@inlinable public var jsValue: JSValue { self }
}
extension JSValue {
@available(
*, unavailable,
message: "code that expects a numeric value should check for 'BigInt' as well"
) @inlinable public var number: Double? {
guard case .number(let value) = self else {
@inlinable public var number: Double? {
guard case .number(let value) = self.storage else {
return nil
}
return value
}

@available(
*, unavailable,
message: "code that expects a numeric value should check for 'Double' as well"
) @inlinable public var bigInt: JSBigInt? {
guard case .bigInt(let value) = self else {
@inlinable public var bigInt: JSBigInt? {
guard case .bigInt(let value) = self.storage else {
return nil
}
return value
}
Comment on lines +133 to +145

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The @available(*, unavailable, ...) attributes on the number and bigInt properties have been removed. These attributes served an important purpose: they forced developers to consciously handle both Double and JSBigInt representations for numeric values, preventing potential bugs with large numbers that can't be represented as Double. Removing these checks makes the API easier to misuse. If this change was intentional, it would be beneficial to document the rationale and guide users on correct handling of numeric types. If it was an oversight, please consider reintroducing a mechanism to ensure both numeric types are handled correctly.

}
extension JSValue {
@inlinable public var jsString: JSString? {
guard case .string(let value) = self.storage else {
return nil
}
return value
}
}
extension JSValue {
@inlinable public var boolean: Bool? {
guard case .boolean(let value) = self else {
guard case .boolean(let value) = self.storage else {
return nil
}
return value
}

@inlinable public var string: String? {
guard case .string(let value) = self else {
guard case .string(let value) = self.storage else {
return nil
}
return value.string
}

@inlinable public var object: JSObject? {
guard case .object(let value) = self else {
guard case .object(let value) = self.storage else {
return nil
}
return value
}

@inlinable public var symbol: JSSymbol? {
guard case .symbol(let value) = self else {
guard case .symbol(let value) = self.storage else {
return nil
}
return value
}

@inlinable public var isNull: Bool {
guard case .null = self else {
guard case .null = self.storage else {
return false
}
return true
}
Comment on lines 184 to 189

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The implementation of isNull can be made more concise and idiomatic by using a switch statement. While the guard statement is functionally correct, a switch is often clearer for checking enum cases.

    @inlinable public var isNull: Bool {
        switch self.storage {
        case .null:
            return true
        default:
            return false
        }
    }


@inlinable public var isUndefined: Bool {
guard case .undefined = self else {
guard case .undefined = self.storage else {
return false
}
return true
Expand Down
2 changes: 1 addition & 1 deletion Sources/JavaScriptPersistence/Optional (ext).swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
extension Optional: ConstructibleFromJSValue where Wrapped: ConstructibleFromJSValue {
@inlinable public static func construct(from value: JSValue) -> Self? {
switch value {
switch value.storage {
case .null, .undefined:
return .some(nil)
default:
Expand Down
Loading