Skip to content

Commit 6b05d07

Browse files
Merge pull request #286 from swiftwasm/yt/swift6-mode
Swift 6 language mode compatibility
2 parents 5148e4f + 3f3b494 commit 6b05d07

File tree

22 files changed

+173
-120
lines changed

22 files changed

+173
-120
lines changed

.github/workflows/test.yml

+3-5
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ jobs:
1515
wasi-backend: Node
1616
- os: ubuntu-22.04
1717
toolchain:
18-
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a-ubuntu22.04.tar.gz
18+
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a-ubuntu22.04.tar.gz
1919
wasi-backend: Node
2020
- os: ubuntu-22.04
2121
toolchain:
22-
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a-ubuntu22.04.tar.gz
22+
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a-ubuntu22.04.tar.gz
2323
wasi-backend: Node
2424

2525
runs-on: ${{ matrix.entry.os }}
@@ -52,8 +52,6 @@ jobs:
5252
strategy:
5353
matrix:
5454
include:
55-
- os: macos-14
56-
xcode: Xcode_15.2
5755
- os: macos-15
5856
xcode: Xcode_16
5957
runs-on: ${{ matrix.os }}
@@ -71,7 +69,7 @@ jobs:
7169
entry:
7270
- os: ubuntu-22.04
7371
toolchain:
74-
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a/swift-DEVELOPMENT-SNAPSHOT-2024-10-30-a-ubuntu22.04.tar.gz
72+
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a/swift-DEVELOPMENT-SNAPSHOT-2025-02-26-a-ubuntu22.04.tar.gz
7573
steps:
7674
- uses: actions/checkout@v4
7775
- uses: ./.github/actions/install-swift

IntegrationTests/TestSuites/Sources/ConcurrencyTests/main.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func entrypoint() async throws {
4848
resolve(.failure(.number(3)))
4949
})
5050
let error = try await expectAsyncThrow(await p.value)
51-
let jsValue = try expectCast(error, to: JSValue.self)
51+
let jsValue = try expectCast(error, to: JSException.self).thrownValue
5252
try expectEqual(jsValue, 3)
5353
try await expectEqual(p.result, .failure(.number(3)))
5454
}
@@ -157,7 +157,7 @@ func entrypoint() async throws {
157157
)
158158
}
159159
let promise2 = promise.then { _ in
160-
throw JSError(message: "should not succeed")
160+
throw MessageError("Should not be called", file: #file, line: #line, column: #column)
161161
} failure: { err in
162162
return err
163163
}

IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func expectThrow<T>(_ body: @autoclosure () throws -> T, file: StaticString = #f
110110
throw MessageError("Expect to throw an exception", file: file, line: line, column: column)
111111
}
112112

113-
func wrapUnsafeThrowableFunction(_ body: @escaping () -> Void, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Error {
113+
func wrapUnsafeThrowableFunction(_ body: @escaping () -> Void, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSValue {
114114
JSObject.global.callThrowingClosure.function!(JSClosure { _ in
115115
body()
116116
return .undefined

IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift

+17-20
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,8 @@ try test("Closure Lifetime") {
263263
let c1Line = #line + 1
264264
let c1 = JSClosure { $0[0] }
265265
c1.release()
266-
let error = try expectThrow(try evalClosure.throws(c1, JSValue.number(42.0))) as! JSValue
267-
try expect("Error message should contains definition location", error.description.hasSuffix("PrimaryTests/main.swift:\(c1Line)"))
266+
let error = try expectThrow(try evalClosure.throws(c1, JSValue.number(42.0))) as! JSException
267+
try expect("Error message should contains definition location", error.thrownValue.description.hasSuffix("PrimaryTests/main.swift:\(c1Line)"))
268268
}
269269
#endif
270270

@@ -275,8 +275,8 @@ try test("Closure Lifetime") {
275275

276276
do {
277277
let c1 = JSClosure { _ in fatalError("Crash while closure evaluation") }
278-
let error = try expectThrow(try evalClosure.throws(c1)) as! JSValue
279-
try expectEqual(error.description, "RuntimeError: unreachable")
278+
let error = try expectThrow(try evalClosure.throws(c1)) as! JSException
279+
try expectEqual(error.thrownValue.description, "RuntimeError: unreachable")
280280
}
281281
}
282282

@@ -770,32 +770,32 @@ try test("Exception") {
770770

771771
// MARK: Throwing method calls
772772
let error1 = try expectThrow(try prop_9.object!.throwing.func1!())
773-
try expectEqual(error1 is JSValue, true)
774-
let errorObject = JSError(from: error1 as! JSValue)
773+
try expectEqual(error1 is JSException, true)
774+
let errorObject = JSError(from: (error1 as! JSException).thrownValue)
775775
try expectNotNil(errorObject)
776776

777777
let error2 = try expectThrow(try prop_9.object!.throwing.func2!())
778-
try expectEqual(error2 is JSValue, true)
779-
let errorString = try expectString(error2 as! JSValue)
778+
try expectEqual(error2 is JSException, true)
779+
let errorString = try expectString((error2 as! JSException).thrownValue)
780780
try expectEqual(errorString, "String Error")
781781

782782
let error3 = try expectThrow(try prop_9.object!.throwing.func3!())
783-
try expectEqual(error3 is JSValue, true)
784-
let errorNumber = try expectNumber(error3 as! JSValue)
783+
try expectEqual(error3 is JSException, true)
784+
let errorNumber = try expectNumber((error3 as! JSException).thrownValue)
785785
try expectEqual(errorNumber, 3.0)
786786

787787
// MARK: Simple function calls
788788
let error4 = try expectThrow(try prop_9.func1.function!.throws())
789-
try expectEqual(error4 is JSValue, true)
790-
let errorObject2 = JSError(from: error4 as! JSValue)
789+
try expectEqual(error4 is JSException, true)
790+
let errorObject2 = JSError(from: (error4 as! JSException).thrownValue)
791791
try expectNotNil(errorObject2)
792792

793793
// MARK: Throwing constructor call
794794
let Animal = JSObject.global.Animal.function!
795795
_ = try Animal.throws.new("Tama", 3, true)
796796
let ageError = try expectThrow(try Animal.throws.new("Tama", -3, true))
797-
try expectEqual(ageError is JSValue, true)
798-
let errorObject3 = JSError(from: ageError as! JSValue)
797+
try expectEqual(ageError is JSException, true)
798+
let errorObject3 = JSError(from: (ageError as! JSException).thrownValue)
799799
try expectNotNil(errorObject3)
800800
}
801801

@@ -824,18 +824,15 @@ try test("Unhandled Exception") {
824824

825825
// MARK: Throwing method calls
826826
let error1 = try wrapUnsafeThrowableFunction { _ = prop_9.object!.func1!() }
827-
try expectEqual(error1 is JSValue, true)
828-
let errorObject = JSError(from: error1 as! JSValue)
827+
let errorObject = JSError(from: error1)
829828
try expectNotNil(errorObject)
830829

831830
let error2 = try wrapUnsafeThrowableFunction { _ = prop_9.object!.func2!() }
832-
try expectEqual(error2 is JSValue, true)
833-
let errorString = try expectString(error2 as! JSValue)
831+
let errorString = try expectString(error2)
834832
try expectEqual(errorString, "String Error")
835833

836834
let error3 = try wrapUnsafeThrowableFunction { _ = prop_9.object!.func3!() }
837-
try expectEqual(error3 is JSValue, true)
838-
let errorNumber = try expectNumber(error3 as! JSValue)
835+
let errorNumber = try expectNumber(error3)
839836
try expectEqual(errorNumber, 3.0)
840837
}
841838

Makefile

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,18 @@ test:
2121
CONFIGURATION=release SWIFT_BUILD_FLAGS="$(SWIFT_BUILD_FLAGS)" $(MAKE) test && \
2222
CONFIGURATION=release SWIFT_BUILD_FLAGS="$(SWIFT_BUILD_FLAGS) -Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" $(MAKE) test
2323

24+
TEST_RUNNER := node --experimental-wasi-unstable-preview1 scripts/test-harness.mjs
2425
.PHONY: unittest
2526
unittest:
2627
@echo Running unit tests
2728
swift build --build-tests -Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor -Xlinker --export-if-defined=main -Xlinker --export-if-defined=__main_argc_argv --static-swift-stdlib -Xswiftc -static-stdlib $(SWIFT_BUILD_FLAGS)
28-
node --experimental-wasi-unstable-preview1 scripts/test-harness.mjs ./.build/debug/JavaScriptKitPackageTests.wasm
29+
# Swift 6.1 and later uses .xctest for XCTest bundle but earliers used .wasm
30+
# See https://github.com/swiftlang/swift-package-manager/pull/8254
31+
if [ -f .build/debug/JavaScriptKitPackageTests.xctest ]; then \
32+
$(TEST_RUNNER) .build/debug/JavaScriptKitPackageTests.xctest; \
33+
else \
34+
$(TEST_RUNNER) .build/debug/JavaScriptKitPackageTests.wasm; \
35+
fi
2936

3037
.PHONY: benchmark_setup
3138
benchmark_setup:

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.8
1+
// swift-tools-version:6.0
22

33
import PackageDescription
44

Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift

+13-7
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,17 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
4040

4141
/// A function that queues a given closure as a microtask into JavaScript event loop.
4242
/// See also: https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
43-
public var queueMicrotask: @Sendable (@escaping () -> Void) -> Void
43+
public var queueMicrotask: (@escaping () -> Void) -> Void
4444
/// A function that invokes a given closure after a specified number of milliseconds.
45-
public var setTimeout: @Sendable (Double, @escaping () -> Void) -> Void
45+
public var setTimeout: (Double, @escaping () -> Void) -> Void
4646

4747
/// A mutable state to manage internal job queue
4848
/// Note that this should be guarded atomically when supporting multi-threaded environment.
4949
var queueState = QueueState()
5050

5151
private init(
52-
queueTask: @Sendable @escaping (@escaping () -> Void) -> Void,
53-
setTimeout: @Sendable @escaping (Double, @escaping () -> Void) -> Void
52+
queueTask: @escaping (@escaping () -> Void) -> Void,
53+
setTimeout: @escaping (Double, @escaping () -> Void) -> Void
5454
) {
5555
self.queueMicrotask = queueTask
5656
self.setTimeout = setTimeout
@@ -102,14 +102,20 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
102102
return eventLoop
103103
}
104104

105-
private static var didInstallGlobalExecutor = false
105+
@MainActor private static var didInstallGlobalExecutor = false
106106

107107
/// Set JavaScript event loop based executor to be the global executor
108108
/// Note that this should be called before any of the jobs are created.
109109
/// This installation step will be unnecessary after custom executor are
110110
/// introduced officially. See also [a draft proposal for custom
111111
/// executors](https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md#the-default-global-concurrent-executor)
112112
public static func installGlobalExecutor() {
113+
MainActor.assumeIsolated {
114+
Self.installGlobalExecutorIsolated()
115+
}
116+
}
117+
118+
@MainActor private static func installGlobalExecutorIsolated() {
113119
guard !didInstallGlobalExecutor else { return }
114120

115121
#if compiler(>=5.9)
@@ -218,7 +224,7 @@ public extension JSPromise {
218224
return JSValue.undefined
219225
},
220226
failure: {
221-
continuation.resume(throwing: $0)
227+
continuation.resume(throwing: JSException($0))
222228
return JSValue.undefined
223229
}
224230
)
@@ -227,7 +233,7 @@ public extension JSPromise {
227233
}
228234

229235
/// Wait for the promise to complete, returning its result or exception as a Result.
230-
var result: Result<JSValue, JSValue> {
236+
var result: JSPromise.Result {
231237
get async {
232238
await withUnsafeContinuation { [self] continuation in
233239
self.then(

Sources/JavaScriptEventLoop/WebWorkerTaskExecutor.swift

+5-4
Original file line numberDiff line numberDiff line change
@@ -426,14 +426,15 @@ public final class WebWorkerTaskExecutor: TaskExecutor {
426426

427427
// MARK: Global Executor hack
428428

429-
private static var _mainThread: pthread_t?
430-
private static var _swift_task_enqueueGlobal_hook_original: UnsafeMutableRawPointer?
431-
private static var _swift_task_enqueueGlobalWithDelay_hook_original: UnsafeMutableRawPointer?
432-
private static var _swift_task_enqueueGlobalWithDeadline_hook_original: UnsafeMutableRawPointer?
429+
@MainActor private static var _mainThread: pthread_t?
430+
@MainActor private static var _swift_task_enqueueGlobal_hook_original: UnsafeMutableRawPointer?
431+
@MainActor private static var _swift_task_enqueueGlobalWithDelay_hook_original: UnsafeMutableRawPointer?
432+
@MainActor private static var _swift_task_enqueueGlobalWithDeadline_hook_original: UnsafeMutableRawPointer?
433433

434434
/// Install a global executor that forwards jobs from Web Worker threads to the main thread.
435435
///
436436
/// This function must be called once before using the Web Worker task executor.
437+
@MainActor
437438
public static func installGlobalExecutor() {
438439
#if canImport(wasi_pthread) && compiler(>=6.1) && _runtime(_multithreaded)
439440
// Ensure this function is called only once.

Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ import JavaScriptEventLoop
2525
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
2626
@_cdecl("swift_javascriptkit_activate_js_executor_impl")
2727
func swift_javascriptkit_activate_js_executor_impl() {
28-
JavaScriptEventLoop.installGlobalExecutor()
28+
MainActor.assumeIsolated {
29+
JavaScriptEventLoop.installGlobalExecutor()
30+
}
2931
}
3032

3133
#endif

Sources/JavaScriptKit/BasicObjects/JSArray.swift

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
/// class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)
33
/// that exposes its properties in a type-safe and Swifty way.
44
public class JSArray: JSBridgedClass {
5-
public static var constructor: JSFunction? { _constructor }
6-
@LazyThreadLocal(initialize: { JSObject.global.Array.function })
7-
private static var _constructor: JSFunction?
5+
public static var constructor: JSFunction? { _constructor.wrappedValue }
6+
private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Array.function })
87

98
static func isArray(_ object: JSObject) -> Bool {
109
constructor!.isArray!(object).boolean!

Sources/JavaScriptKit/BasicObjects/JSDate.swift

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
*/
99
public final class JSDate: JSBridgedClass {
1010
/// The constructor function used to create new `Date` objects.
11-
public static var constructor: JSFunction? { _constructor }
12-
@LazyThreadLocal(initialize: { JSObject.global.Date.function })
13-
private static var _constructor: JSFunction?
11+
public static var constructor: JSFunction? { _constructor.wrappedValue }
12+
private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Date.function })
1413

1514
/// The underlying JavaScript `Date` object.
1615
public let jsObject: JSObject

Sources/JavaScriptKit/BasicObjects/JSError.swift

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) that
33
exposes its properties in a type-safe way.
44
*/
5-
public final class JSError: Error, JSBridgedClass {
5+
public final class JSError: JSBridgedClass {
66
/// The constructor function used to create new JavaScript `Error` objects.
7-
public static var constructor: JSFunction? { _constructor }
8-
@LazyThreadLocal(initialize: { JSObject.global.Error.function })
9-
private static var _constructor: JSFunction?
7+
public static var constructor: JSFunction? { _constructor.wrappedValue }
8+
private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Error.function })
109

1110
/// The underlying JavaScript `Error` object.
1211
public let jsObject: JSObject

Sources/JavaScriptKit/BasicObjects/JSPromise.swift

+16-8
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,22 @@ public final class JSPromise: JSBridgedClass {
3131
return Self(jsObject)
3232
}
3333

34+
/// The result of a promise.
35+
public enum Result: Equatable {
36+
/// The promise resolved with a value.
37+
case success(JSValue)
38+
/// The promise rejected with a value.
39+
case failure(JSValue)
40+
}
41+
3442
/// Creates a new `JSPromise` instance from a given `resolver` closure.
3543
/// The closure is passed a completion handler. Passing a successful
3644
/// `Result` to the completion handler will cause the promise to resolve
3745
/// with the corresponding value; passing a failure `Result` will cause the
3846
/// promise to reject with the corresponding value.
3947
/// Calling the completion handler more than once will have no effect
4048
/// (per the JavaScript specification).
41-
public convenience init(resolver: @escaping (@escaping (Result<JSValue, JSValue>) -> Void) -> Void) {
49+
public convenience init(resolver: @escaping (@escaping (Result) -> Void) -> Void) {
4250
let closure = JSOneshotClosure { arguments in
4351
// The arguments are always coming from the `Promise` constructor, so we should be
4452
// safe to assume their type here
@@ -90,7 +98,7 @@ public final class JSPromise: JSBridgedClass {
9098
/// Schedules the `success` closure to be invoked on successful completion of `self`.
9199
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
92100
@discardableResult
93-
public func then(success: @escaping (JSValue) async throws -> ConvertibleToJSValue) -> JSPromise {
101+
public func then(success: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise {
94102
let closure = JSOneshotClosure.async {
95103
try await success($0[0]).jsValue
96104
}
@@ -101,8 +109,8 @@ public final class JSPromise: JSBridgedClass {
101109
/// Schedules the `success` closure to be invoked on successful completion of `self`.
102110
@discardableResult
103111
public func then(
104-
success: @escaping (JSValue) -> ConvertibleToJSValue,
105-
failure: @escaping (JSValue) -> ConvertibleToJSValue
112+
success: @escaping (sending JSValue) -> ConvertibleToJSValue,
113+
failure: @escaping (sending JSValue) -> ConvertibleToJSValue
106114
) -> JSPromise {
107115
let successClosure = JSOneshotClosure {
108116
success($0[0]).jsValue
@@ -117,8 +125,8 @@ public final class JSPromise: JSBridgedClass {
117125
/// Schedules the `success` closure to be invoked on successful completion of `self`.
118126
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
119127
@discardableResult
120-
public func then(success: @escaping (JSValue) async throws -> ConvertibleToJSValue,
121-
failure: @escaping (JSValue) async throws -> ConvertibleToJSValue) -> JSPromise
128+
public func then(success: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue,
129+
failure: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise
122130
{
123131
let successClosure = JSOneshotClosure.async {
124132
try await success($0[0]).jsValue
@@ -132,7 +140,7 @@ public final class JSPromise: JSBridgedClass {
132140

133141
/// Schedules the `failure` closure to be invoked on rejected completion of `self`.
134142
@discardableResult
135-
public func `catch`(failure: @escaping (JSValue) -> ConvertibleToJSValue) -> JSPromise {
143+
public func `catch`(failure: @escaping (sending JSValue) -> ConvertibleToJSValue) -> JSPromise {
136144
let closure = JSOneshotClosure {
137145
failure($0[0]).jsValue
138146
}
@@ -143,7 +151,7 @@ public final class JSPromise: JSBridgedClass {
143151
/// Schedules the `failure` closure to be invoked on rejected completion of `self`.
144152
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
145153
@discardableResult
146-
public func `catch`(failure: @escaping (JSValue) async throws -> ConvertibleToJSValue) -> JSPromise {
154+
public func `catch`(failure: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise {
147155
let closure = JSOneshotClosure.async {
148156
try await failure($0[0]).jsValue
149157
}

0 commit comments

Comments
 (0)