Skip to content

Commit 7b1ee44

Browse files
Turn JSValue from enum to struct to have flexibility for future changes (#528)
1 parent d79a9be commit 7b1ee44

File tree

6 files changed

+71
-32
lines changed

6 files changed

+71
-32
lines changed

Sources/JavaScriptKit/BasicObjects/JSPromise.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public final class JSPromise: JSBridgedClass {
3131
/// is not an object and is not an instance of JavaScript `Promise`, this function will
3232
/// return `nil`.
3333
public static func construct(from value: JSValue) -> Self? {
34-
guard case .object(let jsObject) = value else { return nil }
34+
guard let jsObject = value.object else { return nil }
3535
return Self(jsObject)
3636
}
3737

Sources/JavaScriptKit/ConvertibleToJSValue.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ extension JSValue {
222222
let kind: JavaScriptValueKind
223223
let payload1: JavaScriptPayload1
224224
var payload2: JavaScriptPayload2 = 0
225-
switch self {
225+
switch self.storage {
226226
case .boolean(let boolValue):
227227
kind = .boolean
228228
payload1 = boolValue ? 1 : 0

Sources/JavaScriptKit/FundamentalObjects/JSObject.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ public class JSObject: Equatable, ExpressibleByDictionaryLiteral {
249249
}
250250

251251
public static func construct(from value: JSValue) -> Self? {
252-
switch value {
252+
switch value.storage {
253253
case .boolean,
254254
.string,
255255
.number,

Sources/JavaScriptKit/JSValue.swift

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,55 @@ import _CJavaScriptKit
22

33
/// `JSValue` represents a value in JavaScript.
44
@dynamicMemberLookup
5-
public enum JSValue: Equatable {
6-
case boolean(Bool)
7-
case string(JSString)
8-
case number(Double)
9-
case object(JSObject)
10-
case null
11-
case undefined
12-
case symbol(JSSymbol)
13-
case bigInt(JSBigInt)
5+
public struct JSValue: Equatable {
6+
/// The internal storage of the JSValue, which is intentionally not public
7+
/// to leave the flexibility to change the storage.
8+
internal enum Storage: Equatable {
9+
case boolean(Bool)
10+
case string(JSString)
11+
case number(Double)
12+
case object(JSObject)
13+
case null
14+
case undefined
15+
case symbol(JSSymbol)
16+
case bigInt(JSBigInt)
17+
}
18+
19+
internal var storage: Storage
20+
21+
internal init(storage: Storage) {
22+
self.storage = storage
23+
}
24+
25+
public static func boolean(_ value: Bool) -> JSValue {
26+
.init(storage: .boolean(value))
27+
}
28+
public static func string(_ value: JSString) -> JSValue {
29+
.init(storage: .string(value))
30+
}
31+
public static func number(_ value: Double) -> JSValue {
32+
.init(storage: .number(value))
33+
}
34+
public static func object(_ value: JSObject) -> JSValue {
35+
.init(storage: .object(value))
36+
}
37+
public static var null: JSValue {
38+
.init(storage: .null)
39+
}
40+
public static var undefined: JSValue {
41+
.init(storage: .undefined)
42+
}
43+
public static func symbol(_ value: JSSymbol) -> JSValue {
44+
.init(storage: .symbol(value))
45+
}
46+
public static func bigInt(_ value: JSBigInt) -> JSValue {
47+
.init(storage: .bigInt(value))
48+
}
1449

1550
/// Returns the `Bool` value of this JS value if its type is boolean.
1651
/// If not, returns `nil`.
1752
public var boolean: Bool? {
18-
switch self {
53+
switch storage {
1954
case .boolean(let boolean): return boolean
2055
default: return nil
2156
}
@@ -35,7 +70,7 @@ public enum JSValue: Equatable {
3570
/// If not, returns `nil`.
3671
///
3772
public var jsString: JSString? {
38-
switch self {
73+
switch storage {
3974
case .string(let string): return string
4075
default: return nil
4176
}
@@ -44,7 +79,7 @@ public enum JSValue: Equatable {
4479
/// Returns the `Double` value of this JS value if the type is number.
4580
/// If not, returns `nil`.
4681
public var number: Double? {
47-
switch self {
82+
switch storage {
4883
case .number(let number): return number
4984
default: return nil
5085
}
@@ -53,7 +88,7 @@ public enum JSValue: Equatable {
5388
/// Returns the `JSObject` of this JS value if its type is object.
5489
/// If not, returns `nil`.
5590
public var object: JSObject? {
56-
switch self {
91+
switch storage {
5792
case .object(let object): return object
5893
default: return nil
5994
}
@@ -65,7 +100,7 @@ public enum JSValue: Equatable {
65100
/// Returns the `JSSymbol` of this JS value if its type is function.
66101
/// If not, returns `nil`.
67102
public var symbol: JSSymbol? {
68-
switch self {
103+
switch storage {
69104
case .symbol(let symbol): return symbol
70105
default: return nil
71106
}
@@ -74,7 +109,7 @@ public enum JSValue: Equatable {
74109
/// Returns the `JSBigInt` of this JS value if its type is function.
75110
/// If not, returns `nil`.
76111
public var bigInt: JSBigInt? {
77-
switch self {
112+
switch storage {
78113
case .bigInt(let bigInt): return bigInt
79114
default: return nil
80115
}
@@ -83,13 +118,13 @@ public enum JSValue: Equatable {
83118
/// Returns the `true` if this JS value is null.
84119
/// If not, returns `false`.
85120
public var isNull: Bool {
86-
return self == .null
121+
return storage == .null
87122
}
88123

89124
/// Returns the `true` if this JS value is undefined.
90125
/// If not, returns `false`.
91126
public var isUndefined: Bool {
92-
return self == .undefined
127+
return storage == .undefined
93128
}
94129
}
95130

@@ -132,7 +167,7 @@ extension JSValue {
132167

133168
extension JSValue {
134169
public static func string(_ value: String) -> JSValue {
135-
.string(JSString(value))
170+
.init(storage: .string(JSString(value)))
136171
}
137172

138173
/// Deprecated: Please create `JSClosure` directly and manage its lifetime manually.
@@ -161,7 +196,7 @@ extension JSValue {
161196
/// ```
162197
@available(*, deprecated, message: "Please create JSClosure directly and manage its lifetime manually.")
163198
public static func function(_ body: @escaping ([JSValue]) -> JSValue) -> JSValue {
164-
.object(JSClosure(body))
199+
.init(storage: .object(JSClosure(body)))
165200
}
166201

167202
@available(
@@ -171,34 +206,34 @@ extension JSValue {
171206
message: "JSClosure is no longer a subclass of JSFunction. Use .object(closure) instead."
172207
)
173208
public static func function(_ closure: JSClosure) -> JSValue {
174-
.object(closure)
209+
.init(storage: .object(closure))
175210
}
176211

177212
@available(*, deprecated, renamed: "object", message: "Use .object(function) instead")
178-
public static func function(_ function: JSObject) -> JSValue { .object(function) }
213+
public static func function(_ function: JSObject) -> JSValue { .init(storage: .object(function)) }
179214
}
180215

181216
extension JSValue: ExpressibleByStringLiteral {
182217
public init(stringLiteral value: String) {
183-
self = .string(JSString(value))
218+
self = .init(storage: .string(JSString(value)))
184219
}
185220
}
186221

187222
extension JSValue: ExpressibleByIntegerLiteral {
188223
public init(integerLiteral value: Int32) {
189-
self = .number(Double(value))
224+
self = .init(storage: .number(Double(value)))
190225
}
191226
}
192227

193228
extension JSValue: ExpressibleByFloatLiteral {
194229
public init(floatLiteral value: Double) {
195-
self = .number(value)
230+
self = .init(storage: .number(value))
196231
}
197232
}
198233

199234
extension JSValue: ExpressibleByNilLiteral {
200235
public init(nilLiteral _: ()) {
201-
self = .null
236+
self = .init(storage: .null)
202237
}
203238
}
204239

@@ -268,7 +303,7 @@ extension JSValue {
268303
/// - Parameter constructor: The constructor function to check.
269304
/// - Returns: The result of `instanceof` in the JavaScript environment.
270305
public func isInstanceOf(_ constructor: JSObject) -> Bool {
271-
switch self {
306+
switch storage {
272307
case .boolean, .string, .number, .null, .undefined, .symbol, .bigInt:
273308
return false
274309
case .object(let ref):

Sources/JavaScriptKit/JSValueDecoder.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,11 @@ private struct _UnkeyedDecodingContainer: UnkeyedDecodingContainer {
159159

160160
init(decoder: _Decoder, ref: JSObject) {
161161
self.decoder = decoder
162-
count = ref.length.number.map(Int.init)
162+
if let count = ref.length.number {
163+
self.count = Int(count)
164+
} else {
165+
self.count = nil
166+
}
163167
self.ref = ref
164168
}
165169

Tests/JavaScriptKitTests/JavaScriptKitTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ class JavaScriptKitTests: XCTestCase {
2121
let prop = JSString("prop_\(index)")
2222
setJSValue(this: global, name: prop, value: input)
2323
let got = getJSValue(this: global, name: prop)
24-
switch (got, input) {
25-
case (.number(let lhs), .number(let rhs)):
24+
switch (got.number, input.number) {
25+
case (let lhs?, let rhs?):
2626
// Compare bitPattern because nan == nan is always false
2727
XCTAssertEqual(lhs.bitPattern, rhs.bitPattern)
2828
default:

0 commit comments

Comments
 (0)