Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make JSObject conform to ExpressibleByDictionaryLiteral #312

Merged
merged 4 commits into from
Mar 27, 2025
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: 2 additions & 0 deletions Runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@ export class SwiftRuntime {
return this.memory.retain(array.slice());
},

swjs_create_object: () => { return this.memory.retain({}); },

swjs_load_typed_array: (ref: ref, buffer: pointer) => {
const memory = this.memory;
const typedArray = memory.getObject(ref);
Expand Down
1 change: 1 addition & 0 deletions Runtime/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export interface ImportedFunctions {
elementsPtr: pointer,
length: number
): number;
swjs_create_object(): number;
swjs_load_typed_array(ref: ref, buffer: pointer): void;
swjs_release(ref: number): void;
swjs_release_remote(tid: number, ref: number): void;
Expand Down
10 changes: 10 additions & 0 deletions Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
)
}

@available(*, unavailable, message: "JSOneshotClosure does not support dictionary literal initialization")
public required init(dictionaryLiteral elements: (String, JSValue)...) {
fatalError("JSOneshotClosure does not support dictionary literal initialization")
}

#if compiler(>=5.5) && !hasFeature(Embedded)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public static func async(_ body: sending @escaping (sending [JSValue]) async throws -> JSValue) -> JSOneshotClosure
Expand Down Expand Up @@ -122,6 +127,11 @@ public class JSClosure: JSFunction, JSClosureProtocol {
Self.sharedClosures.wrappedValue[hostFuncRef] = (self, body)
}

@available(*, unavailable, message: "JSClosure does not support dictionary literal initialization")
public required init(dictionaryLiteral elements: (String, JSValue)...) {
fatalError("JSClosure does not support dictionary literal initialization")
}

#if compiler(>=5.5) && !hasFeature(Embedded)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public static func async(_ body: @Sendable @escaping (sending [JSValue]) async throws -> JSValue) -> JSClosure {
Expand Down
15 changes: 14 additions & 1 deletion Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import _CJavaScriptKit
/// The lifetime of this object is managed by the JavaScript and Swift runtime bridge library with
/// reference counting system.
@dynamicMemberLookup
public class JSObject: Equatable {
public class JSObject: Equatable, ExpressibleByDictionaryLiteral {
internal static var constructor: JSFunction { _constructor.wrappedValue }
private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Object.function! })

Expand All @@ -38,6 +38,19 @@ public class JSObject: Equatable {
#endif
}

/// Creates an empty JavaScript object.
public convenience init() {
self.init(id: swjs_create_object())
}

/// Creates a new object with the key-value pairs in the dictionary literal.
///
/// - Parameter elements: A variadic list of key-value pairs where all keys are strings
public convenience required init(dictionaryLiteral elements: (String, JSValue)...) {
self.init()
for (key, value) in elements { self[key] = value }
}

/// Asserts that the object is being accessed from the owner thread.
///
/// - Parameter hint: A string to provide additional context for debugging.
Expand Down
5 changes: 5 additions & 0 deletions Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public class JSSymbol: JSObject {
super.init(id: id)
}

@available(*, unavailable, message: "JSSymbol does not support dictionary literal initialization")
public required init(dictionaryLiteral elements: (String, JSValue)...) {
fatalError("JSSymbol does not support dictionary literal initialization")
}

public static func `for`(key: JSString) -> JSSymbol {
Symbol.for!(key).symbol!
}
Expand Down
1 change: 1 addition & 0 deletions Sources/JavaScriptKit/Runtime/index.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Sources/JavaScriptKit/Runtime/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Sources/JavaScriptKit/Runtime/index.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ IMPORT_JS_FUNCTION(swjs_terminate_worker_thread, void, (int tid))

IMPORT_JS_FUNCTION(swjs_get_worker_thread_id, int, (void))

IMPORT_JS_FUNCTION(swjs_create_object, JavaScriptObjectRef, (void))

int swjs_get_worker_thread_id_cached(void);

/// Requests sending a JavaScript object to another worker thread.
Expand Down
28 changes: 28 additions & 0 deletions Tests/JavaScriptKitTests/JSObjectTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import JavaScriptKit
import XCTest

final class JSObjectTests: XCTestCase {
func testEmptyObject() {
let object = JSObject()
let keys = JSObject.global.Object.function!.keys.function!(object)
XCTAssertEqual(keys.array?.count, 0)
}

func testInitWithDictionaryLiteral() {
let object: JSObject = [
"key1": 1,
"key2": "value2",
"key3": .boolean(true),
"key4": .object(JSObject()),
"key5": [1, 2, 3].jsValue,
"key6": ["key": "value"].jsValue,
]
XCTAssertEqual(object.key1, .number(1))
XCTAssertEqual(object.key2, "value2")
XCTAssertEqual(object.key3, .boolean(true))
let getKeys = JSObject.global.Object.function!.keys.function!
XCTAssertEqual(getKeys(object.key4).array?.count, 0)
XCTAssertEqual(object.key5.array.map(Array.init), [1, 2, 3])
XCTAssertEqual(object.key6.object?.key, "value")
}
}