Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ node_modules
/*.xcodeproj
xcuserdata/
.swiftpm
.vscode
25 changes: 25 additions & 0 deletions IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -804,5 +804,30 @@ try test("Hashable Conformance") {
try expectEqual(firstHash, secondHash)
}

try test("Symbols") {
let symbol1 = JSSymbol("abc")
let symbol2 = JSSymbol("abc")
try expectNotEqual(symbol1, symbol2)
try expectEqual(symbol1.name, symbol2.name)
try expectEqual(symbol1.name, "abc")

try expectEqual(JSSymbol.iterator, JSSymbol.iterator)

// let hasInstanceClass = {
// prop: function () {}
// }.prop
// Object.defineProperty(hasInstanceClass, Symbol.hasInstance, { value: () => true })
let hasInstanceObject = JSObject.global.Object.function!.new()
hasInstanceObject.prop = JSClosure { _ in .undefined }.jsValue
let hasInstanceClass = hasInstanceObject.prop.function!
let propertyDescriptor = JSObject.global.Object.function!.new()
propertyDescriptor.value = JSClosure { _ in .boolean(true) }.jsValue
_ = JSObject.global.Object.function!.defineProperty!(
hasInstanceClass, JSSymbol.hasInstance,
propertyDescriptor
)
try expectEqual(hasInstanceClass[JSSymbol.hasInstance].function!().boolean, true)
try expectEqual(JSObject.global.Object.isInstanceOf(hasInstanceClass), true)
}

Expectation.wait(expectations)
60 changes: 20 additions & 40 deletions Runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,9 @@ export class SwiftRuntime {
payload2: number
) => {
const obj = this.memory.getObject(ref);
Reflect.set(
obj,
this.memory.getObject(name),
JSValue.decode(kind, payload1, payload2, this.memory)
);
const key = this.memory.getObject(name);
const value = JSValue.decode(kind, payload1, payload2, this.memory);
obj[key] = value;
},

swjs_get_prop: (
Expand All @@ -125,7 +123,8 @@ export class SwiftRuntime {
payload2_ptr: pointer
) => {
const obj = this.memory.getObject(ref);
const result = Reflect.get(obj, this.memory.getObject(name));
const key = this.memory.getObject(name);
const result = obj[key];
JSValue.write(
result,
kind_ptr,
Expand All @@ -144,11 +143,8 @@ export class SwiftRuntime {
payload2: number
) => {
const obj = this.memory.getObject(ref);
Reflect.set(
obj,
index,
JSValue.decode(kind, payload1, payload2, this.memory)
);
const value = JSValue.decode(kind, payload1, payload2, this.memory);
obj[index] = value;
},

swjs_get_subscript: (
Expand All @@ -159,7 +155,7 @@ export class SwiftRuntime {
payload2_ptr: pointer
) => {
const obj = this.memory.getObject(ref);
const result = Reflect.get(obj, index);
const result = obj[index];
JSValue.write(
result,
kind_ptr,
Expand Down Expand Up @@ -201,11 +197,8 @@ export class SwiftRuntime {
const func = this.memory.getObject(ref);
let result: any;
try {
result = Reflect.apply(
func,
undefined,
JSValue.decodeArray(argv, argc, this.memory)
);
const args = JSValue.decodeArray(argv, argc, this.memory);
result = func(...args);
} catch (error) {
JSValue.write(
error,
Expand Down Expand Up @@ -237,11 +230,8 @@ export class SwiftRuntime {
const func = this.memory.getObject(ref);
let isException = true;
try {
const result = Reflect.apply(
func,
undefined,
JSValue.decodeArray(argv, argc, this.memory)
);
const args = JSValue.decodeArray(argv, argc, this.memory);
const result = func(...args);
JSValue.write(
result,
kind_ptr,
Expand Down Expand Up @@ -278,11 +268,8 @@ export class SwiftRuntime {
const func = this.memory.getObject(func_ref);
let result: any;
try {
result = Reflect.apply(
func,
obj,
JSValue.decodeArray(argv, argc, this.memory)
);
const args = JSValue.decodeArray(argv, argc, this.memory);
result = func.apply(obj, args);
} catch (error) {
JSValue.write(
error,
Expand Down Expand Up @@ -317,11 +304,8 @@ export class SwiftRuntime {
const func = this.memory.getObject(func_ref);
let isException = true;
try {
const result = Reflect.apply(
func,
obj,
JSValue.decodeArray(argv, argc, this.memory)
);
const args = JSValue.decodeArray(argv, argc, this.memory);
const result = func.apply(obj, args);
JSValue.write(
result,
kind_ptr,
Expand All @@ -346,10 +330,8 @@ export class SwiftRuntime {
},
swjs_call_new: (ref: ref, argv: pointer, argc: number) => {
const constructor = this.memory.getObject(ref);
const instance = Reflect.construct(
constructor,
JSValue.decodeArray(argv, argc, this.memory)
);
const args = JSValue.decodeArray(argv, argc, this.memory);
const instance = new constructor(...args);
return this.memory.retain(instance);
},

Expand All @@ -364,10 +346,8 @@ export class SwiftRuntime {
const constructor = this.memory.getObject(ref);
let result: any;
try {
result = Reflect.construct(
constructor,
JSValue.decodeArray(argv, argc, this.memory)
);
const args = JSValue.decodeArray(argv, argc, this.memory);
result = new constructor(...args);
} catch (error) {
JSValue.write(
error,
Expand Down
6 changes: 6 additions & 0 deletions Runtime/src/js-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum Kind {
Null = 4,
Undefined = 5,
Function = 6,
Symbol = 7,
}

export const decode = (
Expand Down Expand Up @@ -102,6 +103,11 @@ export const write = (
memory.writeUint32(payload1_ptr, memory.retain(value));
break;
}
case "symbol": {
memory.writeUint32(kind_ptr, exceptionBit | Kind.Symbol);
memory.writeUint32(payload1_ptr, memory.retain(value));
break;
}
default:
throw new Error(`Type "${typeof value}" is not supported yet`);
}
Expand Down
24 changes: 8 additions & 16 deletions Runtime/src/object-heap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,25 @@ export class SwiftRuntimeHeap {
}

retain(value: any) {
const isObject = typeof value == "object";
const entry = this._heapEntryByValue.get(value);
if (isObject && entry) {
if (entry) {
entry.rc++;
return entry.id;
}
const id = this._heapNextKey++;
this._heapValueById.set(id, value);
if (isObject) {
this._heapEntryByValue.set(value, { id: id, rc: 1 });
}
this._heapEntryByValue.set(value, { id: id, rc: 1 });
return id;
}

release(ref: ref) {
const value = this._heapValueById.get(ref);
const isObject = typeof value == "object";
if (isObject) {
const entry = this._heapEntryByValue.get(value)!;
entry.rc--;
if (entry.rc != 0) return;

this._heapEntryByValue.delete(value);
this._heapValueById.delete(ref);
} else {
this._heapValueById.delete(ref);
}
const entry = this._heapEntryByValue.get(value)!;
entry.rc--;
if (entry.rc != 0) return;

this._heapEntryByValue.delete(value);
this._heapValueById.delete(ref);
}

referenceHeap(ref: ref) {
Expand Down
5 changes: 5 additions & 0 deletions Sources/JavaScriptKit/ConvertibleToJSValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ extension RawJSValue: ConvertibleToJSValue {
return .undefined
case .function:
return .function(JSFunction(id: UInt32(payload1)))
case .symbol:
return .symbol(JSSymbol(id: UInt32(payload1)))
}
}
}
Expand Down Expand Up @@ -238,6 +240,9 @@ extension JSValue {
case let .function(functionRef):
kind = .function
payload1 = JavaScriptPayload1(functionRef.id)
case let .symbol(symbolRef):
kind = .symbol
payload1 = JavaScriptPayload1(symbolRef.id)
}
let rawValue = RawJSValue(kind: kind, payload1: payload1, payload2: payload2)
return body(rawValue)
Expand Down
17 changes: 8 additions & 9 deletions Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import _CJavaScriptKit
/// ```
///
public class JSFunction: JSObject {

/// Call this function with given `arguments` and binding given `this` as context.
/// - Parameters:
/// - this: The value to be passed as the `this` parameter to this function.
/// - arguments: Arguments to be passed to this function.
/// - Returns: The result of this call.
@discardableResult
public func callAsFunction(this: JSObject? = nil, arguments: [ConvertibleToJSValue]) -> JSValue {
invokeNonThrowingJSFunction(self, arguments: arguments, this: this)
invokeNonThrowingJSFunction(self, arguments: arguments, this: this).jsValue
}

/// A variadic arguments version of `callAsFunction`.
Expand All @@ -41,7 +40,7 @@ public class JSFunction: JSObject {
public func new(arguments: [ConvertibleToJSValue]) -> JSObject {
arguments.withRawJSValues { rawValues in
rawValues.withUnsafeBufferPointer { bufferPointer in
return JSObject(id: _call_new(self.id, bufferPointer.baseAddress!, Int32(bufferPointer.count)))
JSObject(id: _call_new(self.id, bufferPointer.baseAddress!, Int32(bufferPointer.count)))
}
}
}
Expand Down Expand Up @@ -75,7 +74,7 @@ public class JSFunction: JSObject {
fatalError("unavailable")
}

public override class func construct(from value: JSValue) -> Self? {
override public class func construct(from value: JSValue) -> Self? {
return value.function as? Self
}

Expand All @@ -84,18 +83,18 @@ public class JSFunction: JSObject {
}
}

private func invokeNonThrowingJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSValue], this: JSObject?) -> JSValue {
func invokeNonThrowingJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSValue], this: JSObject?) -> RawJSValue {
arguments.withRawJSValues { rawValues in
rawValues.withUnsafeBufferPointer { bufferPointer -> (JSValue) in
rawValues.withUnsafeBufferPointer { bufferPointer in
let argv = bufferPointer.baseAddress
let argc = bufferPointer.count
var kindAndFlags = JavaScriptValueKindAndFlags()
var payload1 = JavaScriptPayload1()
var payload2 = JavaScriptPayload2()
if let thisId = this?.id {
_call_function_with_this_no_catch(thisId,
jsFunc.id, argv, Int32(argc),
&kindAndFlags, &payload1, &payload2)
jsFunc.id, argv, Int32(argc),
&kindAndFlags, &payload1, &payload2)
} else {
_call_function_no_catch(
jsFunc.id, argv, Int32(argc),
Expand All @@ -104,7 +103,7 @@ private func invokeNonThrowingJSFunction(_ jsFunc: JSFunction, arguments: [Conve
}
assert(!kindAndFlags.isException)
let result = RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2)
return result.jsValue
return result
}
}
}
8 changes: 8 additions & 0 deletions Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ public class JSObject: Equatable {
set { setJSValue(this: self, index: Int32(index), value: newValue) }
}

/// Access the `symbol` member dynamically through JavaScript and Swift runtime bridge library.
/// - Parameter symbol: The name of this object's member to access.
/// - Returns: The value of the `name` member of this object.
public subscript(_ name: JSSymbol) -> JSValue {
get { getJSValue(this: self, symbol: name) }
set { setJSValue(this: self, symbol: name, value: newValue) }
}

/// A modifier to call methods as throwing methods capturing `this`
///
///
Expand Down
56 changes: 56 additions & 0 deletions Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import _CJavaScriptKit

private let Symbol = JSObject.global.Symbol.function!

public class JSSymbol: JSObject {
public var name: String? { self["description"].string }

public init(_ description: JSString) {
// can’t do `self =` so we have to get the ID manually
let result = invokeNonThrowingJSFunction(Symbol, arguments: [description], this: nil)
precondition(result.kind == .symbol)
super.init(id: UInt32(result.payload1))
}
@_disfavoredOverload
public convenience init(_ description: String) {
self.init(JSString(description))
}

override init(id: JavaScriptObjectRef) {
super.init(id: id)
}

public static func `for`(key: JSString) -> JSSymbol {
Symbol.for!(key).symbol!
}

@_disfavoredOverload
public static func `for`(key: String) -> JSSymbol {
Symbol.for!(key).symbol!
}

public static func key(for symbol: JSSymbol) -> JSString? {
Symbol.keyFor!(symbol).jsString
}

@_disfavoredOverload
public static func key(for symbol: JSSymbol) -> String? {
Symbol.keyFor!(symbol).string
}
}

extension JSSymbol {
public static let asyncIterator: JSSymbol! = Symbol.asyncIterator.symbol
public static let hasInstance: JSSymbol! = Symbol.hasInstance.symbol
public static let isConcatSpreadable: JSSymbol! = Symbol.isConcatSpreadable.symbol
public static let iterator: JSSymbol! = Symbol.iterator.symbol
public static let match: JSSymbol! = Symbol.match.symbol
public static let matchAll: JSSymbol! = Symbol.matchAll.symbol
public static let replace: JSSymbol! = Symbol.replace.symbol
public static let search: JSSymbol! = Symbol.search.symbol
public static let species: JSSymbol! = Symbol.species.symbol
public static let split: JSSymbol! = Symbol.split.symbol
public static let toPrimitive: JSSymbol! = Symbol.toPrimitive.symbol
public static let toStringTag: JSSymbol! = Symbol.toStringTag.symbol
public static let unscopables: JSSymbol! = Symbol.unscopables.symbol
}
Loading