Skip to content

Commit 6b07c4f

Browse files
committed
Revert "Revert "Add support for Symbol objects via JSSymbol (#179)""
This reverts commit 990479e
1 parent ca19e88 commit 6b07c4f

File tree

9 files changed

+163
-47
lines changed

9 files changed

+163
-47
lines changed

IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift

+23
Original file line numberDiff line numberDiff line change
@@ -804,5 +804,28 @@ try test("Hashable Conformance") {
804804
try expectEqual(firstHash, secondHash)
805805
}
806806

807+
try test("Symbols") {
808+
let symbol1 = JSSymbol("abc")
809+
let symbol2 = JSSymbol("abc")
810+
try expectNotEqual(symbol1, symbol2)
811+
try expectEqual(symbol1.name, symbol2.name)
812+
try expectEqual(symbol1.name, "abc")
813+
814+
try expectEqual(JSSymbol.iterator, JSSymbol.iterator)
815+
816+
// let hasInstanceClass = {
817+
// prop: Object.assign(function () {}, {
818+
// [Symbol.hasInstance]() { return true }
819+
// })
820+
// }.prop
821+
let hasInstanceObject = JSObject.global.Object.function!.new()
822+
hasInstanceObject.prop = JSClosure { _ in .undefined }.jsValue
823+
let hasInstanceClass = hasInstanceObject.prop.function!
824+
hasInstanceClass[JSSymbol.hasInstance] = JSClosure { _ in
825+
return .boolean(true)
826+
}.jsValue
827+
try expectEqual(hasInstanceClass[JSSymbol.hasInstance].function!().boolean, true)
828+
try expectEqual(JSObject.global.Object.isInstanceOf(hasInstanceClass), true)
829+
}
807830

808831
Expectation.wait(expectations)

Runtime/src/js-value.ts

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export enum Kind {
1010
Null = 4,
1111
Undefined = 5,
1212
Function = 6,
13+
Symbol = 7,
1314
}
1415

1516
export const decode = (
@@ -102,6 +103,11 @@ export const write = (
102103
memory.writeUint32(payload1_ptr, memory.retain(value));
103104
break;
104105
}
106+
case "symbol": {
107+
memory.writeUint32(kind_ptr, exceptionBit | Kind.Symbol);
108+
memory.writeUint32(payload1_ptr, memory.retain(value));
109+
break;
110+
}
105111
default:
106112
throw new Error(`Type "${typeof value}" is not supported yet`);
107113
}

Runtime/src/object-heap.ts

+8-16
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,25 @@ export class SwiftRuntimeHeap {
2222
}
2323

2424
retain(value: any) {
25-
const isObject = typeof value == "object";
2625
const entry = this._heapEntryByValue.get(value);
27-
if (isObject && entry) {
26+
if (entry) {
2827
entry.rc++;
2928
return entry.id;
3029
}
3130
const id = this._heapNextKey++;
3231
this._heapValueById.set(id, value);
33-
if (isObject) {
34-
this._heapEntryByValue.set(value, { id: id, rc: 1 });
35-
}
32+
this._heapEntryByValue.set(value, { id: id, rc: 1 });
3633
return id;
3734
}
3835

3936
release(ref: ref) {
4037
const value = this._heapValueById.get(ref);
41-
const isObject = typeof value == "object";
42-
if (isObject) {
43-
const entry = this._heapEntryByValue.get(value)!;
44-
entry.rc--;
45-
if (entry.rc != 0) return;
46-
47-
this._heapEntryByValue.delete(value);
48-
this._heapValueById.delete(ref);
49-
} else {
50-
this._heapValueById.delete(ref);
51-
}
38+
const entry = this._heapEntryByValue.get(value)!;
39+
entry.rc--;
40+
if (entry.rc != 0) return;
41+
42+
this._heapEntryByValue.delete(value);
43+
this._heapValueById.delete(ref);
5244
}
5345

5446
referenceHeap(ref: ref) {

Sources/JavaScriptKit/ConvertibleToJSValue.swift

+5
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ extension RawJSValue: ConvertibleToJSValue {
207207
return .undefined
208208
case .function:
209209
return .function(JSFunction(id: UInt32(payload1)))
210+
case .symbol:
211+
return .symbol(JSSymbol(id: UInt32(payload1)))
210212
}
211213
}
212214
}
@@ -238,6 +240,9 @@ extension JSValue {
238240
case let .function(functionRef):
239241
kind = .function
240242
payload1 = JavaScriptPayload1(functionRef.id)
243+
case let .symbol(symbolRef):
244+
kind = .symbol
245+
payload1 = JavaScriptPayload1(symbolRef.id)
241246
}
242247
let rawValue = RawJSValue(kind: kind, payload1: payload1, payload2: payload2)
243248
return body(rawValue)

Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift

+8-9
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ import _CJavaScriptKit
1111
/// ```
1212
///
1313
public class JSFunction: JSObject {
14-
1514
/// Call this function with given `arguments` and binding given `this` as context.
1615
/// - Parameters:
1716
/// - this: The value to be passed as the `this` parameter to this function.
1817
/// - arguments: Arguments to be passed to this function.
1918
/// - Returns: The result of this call.
2019
@discardableResult
2120
public func callAsFunction(this: JSObject? = nil, arguments: [ConvertibleToJSValue]) -> JSValue {
22-
invokeNonThrowingJSFunction(self, arguments: arguments, this: this)
21+
invokeNonThrowingJSFunction(self, arguments: arguments, this: this).jsValue
2322
}
2423

2524
/// A variadic arguments version of `callAsFunction`.
@@ -41,7 +40,7 @@ public class JSFunction: JSObject {
4140
public func new(arguments: [ConvertibleToJSValue]) -> JSObject {
4241
arguments.withRawJSValues { rawValues in
4342
rawValues.withUnsafeBufferPointer { bufferPointer in
44-
return JSObject(id: _call_new(self.id, bufferPointer.baseAddress!, Int32(bufferPointer.count)))
43+
JSObject(id: _call_new(self.id, bufferPointer.baseAddress!, Int32(bufferPointer.count)))
4544
}
4645
}
4746
}
@@ -75,7 +74,7 @@ public class JSFunction: JSObject {
7574
fatalError("unavailable")
7675
}
7776

78-
public override class func construct(from value: JSValue) -> Self? {
77+
override public class func construct(from value: JSValue) -> Self? {
7978
return value.function as? Self
8079
}
8180

@@ -84,18 +83,18 @@ public class JSFunction: JSObject {
8483
}
8584
}
8685

87-
private func invokeNonThrowingJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSValue], this: JSObject?) -> JSValue {
86+
func invokeNonThrowingJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSValue], this: JSObject?) -> RawJSValue {
8887
arguments.withRawJSValues { rawValues in
89-
rawValues.withUnsafeBufferPointer { bufferPointer -> (JSValue) in
88+
rawValues.withUnsafeBufferPointer { bufferPointer in
9089
let argv = bufferPointer.baseAddress
9190
let argc = bufferPointer.count
9291
var kindAndFlags = JavaScriptValueKindAndFlags()
9392
var payload1 = JavaScriptPayload1()
9493
var payload2 = JavaScriptPayload2()
9594
if let thisId = this?.id {
9695
_call_function_with_this_no_catch(thisId,
97-
jsFunc.id, argv, Int32(argc),
98-
&kindAndFlags, &payload1, &payload2)
96+
jsFunc.id, argv, Int32(argc),
97+
&kindAndFlags, &payload1, &payload2)
9998
} else {
10099
_call_function_no_catch(
101100
jsFunc.id, argv, Int32(argc),
@@ -104,7 +103,7 @@ private func invokeNonThrowingJSFunction(_ jsFunc: JSFunction, arguments: [Conve
104103
}
105104
assert(!kindAndFlags.isException)
106105
let result = RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2)
107-
return result.jsValue
106+
return result
108107
}
109108
}
110109
}

Sources/JavaScriptKit/FundamentalObjects/JSObject.swift

+8
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ public class JSObject: Equatable {
9595
set { setJSValue(this: self, index: Int32(index), value: newValue) }
9696
}
9797

98+
/// Access the `symbol` member dynamically through JavaScript and Swift runtime bridge library.
99+
/// - Parameter symbol: The name of this object's member to access.
100+
/// - Returns: The value of the `name` member of this object.
101+
public subscript(_ name: JSSymbol) -> JSValue {
102+
get { getJSValue(this: self, symbol: name) }
103+
set { setJSValue(this: self, symbol: name, value: newValue) }
104+
}
105+
98106
/// A modifier to call methods as throwing methods capturing `this`
99107
///
100108
///
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import _CJavaScriptKit
2+
3+
private let Symbol = JSObject.global.Symbol.function!
4+
5+
public class JSSymbol: JSObject {
6+
public var name: String? { self["description"].string }
7+
8+
public init(_ description: JSString) {
9+
// can’t do `self =` so we have to get the ID manually
10+
let result = invokeNonThrowingJSFunction(Symbol, arguments: [description], this: nil)
11+
precondition(result.kind == .symbol)
12+
super.init(id: UInt32(result.payload1))
13+
}
14+
@_disfavoredOverload
15+
public convenience init(_ description: String) {
16+
self.init(JSString(description))
17+
}
18+
19+
override init(id: JavaScriptObjectRef) {
20+
super.init(id: id)
21+
}
22+
23+
public static func `for`(key: JSString) -> JSSymbol {
24+
Symbol.for!(key).symbol!
25+
}
26+
27+
@_disfavoredOverload
28+
public static func `for`(key: String) -> JSSymbol {
29+
Symbol.for!(key).symbol!
30+
}
31+
32+
public static func key(for symbol: JSSymbol) -> JSString? {
33+
Symbol.keyFor!(symbol).jsString
34+
}
35+
36+
@_disfavoredOverload
37+
public static func key(for symbol: JSSymbol) -> String? {
38+
Symbol.keyFor!(symbol).string
39+
}
40+
}
41+
42+
extension JSSymbol {
43+
public static let asyncIterator: JSSymbol! = Symbol.asyncIterator.symbol
44+
public static let hasInstance: JSSymbol! = Symbol.hasInstance.symbol
45+
public static let isConcatSpreadable: JSSymbol! = Symbol.isConcatSpreadable.symbol
46+
public static let iterator: JSSymbol! = Symbol.iterator.symbol
47+
public static let match: JSSymbol! = Symbol.match.symbol
48+
public static let matchAll: JSSymbol! = Symbol.matchAll.symbol
49+
public static let replace: JSSymbol! = Symbol.replace.symbol
50+
public static let search: JSSymbol! = Symbol.search.symbol
51+
public static let species: JSSymbol! = Symbol.species.symbol
52+
public static let split: JSSymbol! = Symbol.split.symbol
53+
public static let toPrimitive: JSSymbol! = Symbol.toPrimitive.symbol
54+
public static let toStringTag: JSSymbol! = Symbol.toStringTag.symbol
55+
public static let unscopables: JSSymbol! = Symbol.unscopables.symbol
56+
}

Sources/JavaScriptKit/JSValue.swift

+44-22
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public enum JSValue: Equatable {
1010
case null
1111
case undefined
1212
case function(JSFunction)
13+
case symbol(JSSymbol)
1314

1415
/// Returns the `Bool` value of this JS value if its type is boolean.
1516
/// If not, returns `nil`.
@@ -67,6 +68,13 @@ public enum JSValue: Equatable {
6768
}
6869
}
6970

71+
public var symbol: JSSymbol? {
72+
switch self {
73+
case let .symbol(symbol): return symbol
74+
default: return nil
75+
}
76+
}
77+
7078
/// Returns the `true` if this JS value is null.
7179
/// If not, returns `false`.
7280
public var isNull: Bool {
@@ -80,39 +88,38 @@ public enum JSValue: Equatable {
8088
}
8189
}
8290

83-
extension JSValue {
91+
public extension JSValue {
8492
/// An unsafe convenience method of `JSObject.subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)?`
8593
/// - Precondition: `self` must be a JavaScript Object and specified member should be a callable object.
86-
public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue) {
94+
subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue) {
8795
object![dynamicMember: name]!
8896
}
8997

9098
/// An unsafe convenience method of `JSObject.subscript(_ index: Int) -> JSValue`
9199
/// - Precondition: `self` must be a JavaScript Object.
92-
public subscript(dynamicMember name: String) -> JSValue {
100+
subscript(dynamicMember name: String) -> JSValue {
93101
get { self.object![name] }
94102
set { self.object![name] = newValue }
95103
}
96104

97105
/// An unsafe convenience method of `JSObject.subscript(_ index: Int) -> JSValue`
98106
/// - Precondition: `self` must be a JavaScript Object.
99-
public subscript(_ index: Int) -> JSValue {
107+
subscript(_ index: Int) -> JSValue {
100108
get { object![index] }
101109
set { object![index] = newValue }
102110
}
103111
}
104112

105113
extension JSValue: Swift.Error {}
106114

107-
extension JSValue {
108-
public func fromJSValue<Type>() -> Type? where Type: ConstructibleFromJSValue {
115+
public extension JSValue {
116+
func fromJSValue<Type>() -> Type? where Type: ConstructibleFromJSValue {
109117
return Type.construct(from: self)
110118
}
111119
}
112120

113-
extension JSValue {
114-
115-
public static func string(_ value: String) -> JSValue {
121+
public extension JSValue {
122+
static func string(_ value: String) -> JSValue {
116123
.string(JSString(value))
117124
}
118125

@@ -141,12 +148,12 @@ extension JSValue {
141148
/// eventListenter.release()
142149
/// ```
143150
@available(*, deprecated, message: "Please create JSClosure directly and manage its lifetime manually.")
144-
public static func function(_ body: @escaping ([JSValue]) -> JSValue) -> JSValue {
151+
static func function(_ body: @escaping ([JSValue]) -> JSValue) -> JSValue {
145152
.object(JSClosure(body))
146153
}
147154

148155
@available(*, deprecated, renamed: "object", message: "JSClosure is no longer a subclass of JSFunction. Use .object(closure) instead.")
149-
public static func function(_ closure: JSClosure) -> JSValue {
156+
static func function(_ closure: JSClosure) -> JSValue {
150157
.object(closure)
151158
}
152159
}
@@ -170,7 +177,7 @@ extension JSValue: ExpressibleByFloatLiteral {
170177
}
171178

172179
extension JSValue: ExpressibleByNilLiteral {
173-
public init(nilLiteral: ()) {
180+
public init(nilLiteral _: ()) {
174181
self = .null
175182
}
176183
}
@@ -205,14 +212,28 @@ public func setJSValue(this: JSObject, index: Int32, value: JSValue) {
205212
}
206213
}
207214

208-
extension JSValue {
209-
/// Return `true` if this value is an instance of the passed `constructor` function.
210-
/// Returns `false` for everything except objects and functions.
211-
/// - Parameter constructor: The constructor function to check.
212-
/// - Returns: The result of `instanceof` in the JavaScript environment.
213-
public func isInstanceOf(_ constructor: JSFunction) -> Bool {
215+
public func getJSValue(this: JSObject, symbol: JSSymbol) -> JSValue {
216+
var rawValue = RawJSValue()
217+
_get_prop(this.id, symbol.id,
218+
&rawValue.kind,
219+
&rawValue.payload1, &rawValue.payload2)
220+
return rawValue.jsValue
221+
}
222+
223+
public func setJSValue(this: JSObject, symbol: JSSymbol, value: JSValue) {
224+
value.withRawJSValue { rawValue in
225+
_set_prop(this.id, symbol.id, rawValue.kind, rawValue.payload1, rawValue.payload2)
226+
}
227+
}
228+
229+
public extension JSValue {
230+
/// Return `true` if this value is an instance of the passed `constructor` function.
231+
/// Returns `false` for everything except objects and functions.
232+
/// - Parameter constructor: The constructor function to check.
233+
/// - Returns: The result of `instanceof` in the JavaScript environment.
234+
func isInstanceOf(_ constructor: JSFunction) -> Bool {
214235
switch self {
215-
case .boolean, .string, .number, .null, .undefined:
236+
case .boolean, .string, .number, .null, .undefined, .symbol:
216237
return false
217238
case let .object(ref):
218239
return ref.isInstanceOf(constructor)
@@ -227,11 +248,12 @@ extension JSValue: CustomStringConvertible {
227248
switch self {
228249
case let .boolean(boolean):
229250
return boolean.description
230-
case .string(let string):
251+
case let .string(string):
231252
return string.description
232-
case .number(let number):
253+
case let .number(number):
233254
return number.description
234-
case .object(let object), .function(let object as JSObject):
255+
case let .object(object), let .function(object as JSObject),
256+
.symbol(let object as JSObject):
235257
return object.toString!().fromJSValue()!
236258
case .null:
237259
return "null"

0 commit comments

Comments
 (0)