Skip to content

Commit 8c20a89

Browse files
authored
Re-add support for Symbol objects via JSSymbol (#183)
* Revert "Revert "Revert "Revert "Add support for Symbol objects via JSSymbol (#179)"""" This reverts commit 85e1a11. * Ignore .vscode * Drop Reflect.get/set in favor of subscript syntax * Also drop Reflect.{apply,construct} * Fix bad test
1 parent 7b0e0d2 commit 8c20a89

File tree

11 files changed

+187
-87
lines changed

11 files changed

+187
-87
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ node_modules
66
/*.xcodeproj
77
xcuserdata/
88
.swiftpm
9+
.vscode

IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift

+26
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,32 @@ 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: function () {}
818+
// }.prop
819+
// Object.defineProperty(hasInstanceClass, Symbol.hasInstance, { value: () => true })
820+
let hasInstanceObject = JSObject.global.Object.function!.new()
821+
hasInstanceObject.prop = JSClosure { _ in .undefined }.jsValue
822+
let hasInstanceClass = hasInstanceObject.prop.function!
823+
let propertyDescriptor = JSObject.global.Object.function!.new()
824+
propertyDescriptor.value = JSClosure { _ in .boolean(true) }.jsValue
825+
_ = JSObject.global.Object.function!.defineProperty!(
826+
hasInstanceClass, JSSymbol.hasInstance,
827+
propertyDescriptor
828+
)
829+
try expectEqual(hasInstanceClass[JSSymbol.hasInstance].function!().boolean, true)
830+
try expectEqual(JSObject.global.Object.isInstanceOf(hasInstanceClass), true)
831+
}
832+
807833
struct AnimalStruct: Decodable {
808834
let name: String
809835
let age: Int

Runtime/src/index.ts

+20-40
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,9 @@ export class SwiftRuntime {
110110
payload2: number
111111
) => {
112112
const obj = this.memory.getObject(ref);
113-
Reflect.set(
114-
obj,
115-
this.memory.getObject(name),
116-
JSValue.decode(kind, payload1, payload2, this.memory)
117-
);
113+
const key = this.memory.getObject(name);
114+
const value = JSValue.decode(kind, payload1, payload2, this.memory);
115+
obj[key] = value;
118116
},
119117

120118
swjs_get_prop: (
@@ -125,7 +123,8 @@ export class SwiftRuntime {
125123
payload2_ptr: pointer
126124
) => {
127125
const obj = this.memory.getObject(ref);
128-
const result = Reflect.get(obj, this.memory.getObject(name));
126+
const key = this.memory.getObject(name);
127+
const result = obj[key];
129128
JSValue.write(
130129
result,
131130
kind_ptr,
@@ -144,11 +143,8 @@ export class SwiftRuntime {
144143
payload2: number
145144
) => {
146145
const obj = this.memory.getObject(ref);
147-
Reflect.set(
148-
obj,
149-
index,
150-
JSValue.decode(kind, payload1, payload2, this.memory)
151-
);
146+
const value = JSValue.decode(kind, payload1, payload2, this.memory);
147+
obj[index] = value;
152148
},
153149

154150
swjs_get_subscript: (
@@ -159,7 +155,7 @@ export class SwiftRuntime {
159155
payload2_ptr: pointer
160156
) => {
161157
const obj = this.memory.getObject(ref);
162-
const result = Reflect.get(obj, index);
158+
const result = obj[index];
163159
JSValue.write(
164160
result,
165161
kind_ptr,
@@ -201,11 +197,8 @@ export class SwiftRuntime {
201197
const func = this.memory.getObject(ref);
202198
let result: any;
203199
try {
204-
result = Reflect.apply(
205-
func,
206-
undefined,
207-
JSValue.decodeArray(argv, argc, this.memory)
208-
);
200+
const args = JSValue.decodeArray(argv, argc, this.memory);
201+
result = func(...args);
209202
} catch (error) {
210203
JSValue.write(
211204
error,
@@ -237,11 +230,8 @@ export class SwiftRuntime {
237230
const func = this.memory.getObject(ref);
238231
let isException = true;
239232
try {
240-
const result = Reflect.apply(
241-
func,
242-
undefined,
243-
JSValue.decodeArray(argv, argc, this.memory)
244-
);
233+
const args = JSValue.decodeArray(argv, argc, this.memory);
234+
const result = func(...args);
245235
JSValue.write(
246236
result,
247237
kind_ptr,
@@ -278,11 +268,8 @@ export class SwiftRuntime {
278268
const func = this.memory.getObject(func_ref);
279269
let result: any;
280270
try {
281-
result = Reflect.apply(
282-
func,
283-
obj,
284-
JSValue.decodeArray(argv, argc, this.memory)
285-
);
271+
const args = JSValue.decodeArray(argv, argc, this.memory);
272+
result = func.apply(obj, args);
286273
} catch (error) {
287274
JSValue.write(
288275
error,
@@ -317,11 +304,8 @@ export class SwiftRuntime {
317304
const func = this.memory.getObject(func_ref);
318305
let isException = true;
319306
try {
320-
const result = Reflect.apply(
321-
func,
322-
obj,
323-
JSValue.decodeArray(argv, argc, this.memory)
324-
);
307+
const args = JSValue.decodeArray(argv, argc, this.memory);
308+
const result = func.apply(obj, args);
325309
JSValue.write(
326310
result,
327311
kind_ptr,
@@ -346,10 +330,8 @@ export class SwiftRuntime {
346330
},
347331
swjs_call_new: (ref: ref, argv: pointer, argc: number) => {
348332
const constructor = this.memory.getObject(ref);
349-
const instance = Reflect.construct(
350-
constructor,
351-
JSValue.decodeArray(argv, argc, this.memory)
352-
);
333+
const args = JSValue.decodeArray(argv, argc, this.memory);
334+
const instance = new constructor(...args);
353335
return this.memory.retain(instance);
354336
},
355337

@@ -364,10 +346,8 @@ export class SwiftRuntime {
364346
const constructor = this.memory.getObject(ref);
365347
let result: any;
366348
try {
367-
result = Reflect.construct(
368-
constructor,
369-
JSValue.decodeArray(argv, argc, this.memory)
370-
);
349+
const args = JSValue.decodeArray(argv, argc, this.memory);
350+
result = new constructor(...args);
371351
} catch (error) {
372352
JSValue.write(
373353
error,

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+
}

0 commit comments

Comments
 (0)