From d1cefa2ad690ed4efbfcad8c8e8806ea06ddf4c2 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 24 Apr 2025 10:56:39 +0100 Subject: [PATCH] Clean up use of closure property wrappers This changes improves compatibility of the WebAPIKit library with Embedded Swift. Unsupported concurrency code is excluded when concurrency is not available, including the use of async sequences. Use of closure patterns is cleaned up to use getters and setters directly instead of property wrappers, which reduces binary code size when Embedded Swift is used. --- Sources/WebIDLToSwift/ClosurePattern.swift | 16 +++++++------- Sources/WebIDLToSwift/IDLBuilder.swift | 5 ++--- Sources/WebIDLToSwift/MergeDeclarations.swift | 3 --- .../WebIDL+SwiftRepresentation.swift | 21 ++++++++++++++++--- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Sources/WebIDLToSwift/ClosurePattern.swift b/Sources/WebIDLToSwift/ClosurePattern.swift index d06c2d9f..e6e93d94 100644 --- a/Sources/WebIDLToSwift/ClosurePattern.swift +++ b/Sources/WebIDLToSwift/ClosurePattern.swift @@ -1,6 +1,6 @@ struct ClosurePattern: SwiftRepresentable, Equatable, Hashable, Comparable { static func < (lhs: ClosurePattern, rhs: ClosurePattern) -> Bool { - lhs.name.source < rhs.name.source + lhs.closureType.source < rhs.closureType.source } let nullable: Bool @@ -22,16 +22,16 @@ struct ClosurePattern: SwiftRepresentable, Equatable, Hashable, Comparable { return nullable ? "(\(closure))?" : closure } - private var getter: SwiftSource { + func getter(name: SwiftSource) -> SwiftSource { let getFunction: SwiftSource if nullable { getFunction = """ - guard let function = jsObject[name].function else { + guard let function = jsObject[\(name)].function else { return nil } """ } else { - getFunction = "let function = jsObject[name].function!" + getFunction = "let function = jsObject[\(name)].function!" } let call: SwiftSource = "function(\(sequence: indexes.map { "_toJSValue($\($0))" }))" let closureBody: SwiftSource @@ -66,15 +66,15 @@ struct ClosurePattern: SwiftRepresentable, Equatable, Hashable, Comparable { """ } - private var setter: SwiftSource { - let setClosure: SwiftSource = "jsObject[name] = \(jsClosureWrapper(name: "newValue"))" + func setter(name: SwiftSource) -> SwiftSource { + let setClosure: SwiftSource = "jsObject[\(name)] = \(jsClosureWrapper(name: "newValue"))" if nullable { return """ if let newValue = newValue { \(setClosure) } else { - jsObject[name] = .null + jsObject[\(name)] = .null } """ } else { @@ -121,6 +121,7 @@ struct ClosurePattern: SwiftRepresentable, Equatable, Hashable, Comparable { """ } + var toJSValue: SwiftSource { let escaping: SwiftSource = nullable ? "" : "@escaping" return """ @@ -132,7 +133,6 @@ struct ClosurePattern: SwiftRepresentable, Equatable, Hashable, Comparable { var swiftRepresentation: SwiftSource { """ - \(propertyWrapper) \(toJSValue) """ } diff --git a/Sources/WebIDLToSwift/IDLBuilder.swift b/Sources/WebIDLToSwift/IDLBuilder.swift index 21429dbb..9cb8ac64 100644 --- a/Sources/WebIDLToSwift/IDLBuilder.swift +++ b/Sources/WebIDLToSwift/IDLBuilder.swift @@ -2,7 +2,7 @@ import Foundation import WebIDL enum IDLBuilder { - static let basicDependencies = ["ECMAScript", "JavaScriptKit", "JavaScriptEventLoop"] + static let basicDependencies = ["ECMAScript", "JavaScriptKit"] static let optionalDependencies = ["JavaScriptEventLoop"] static let preamble = """ @@ -95,9 +95,8 @@ enum IDLBuilder { } static func generateClosureTypes() throws -> SwiftSource { - print("Generating closure property wrappers...") + print("Generating closure wrappers...") return """ - /* variadic generics please */ \(lines: ModuleState.closurePatterns.sorted().map(\.swiftRepresentation)) """ } diff --git a/Sources/WebIDLToSwift/MergeDeclarations.swift b/Sources/WebIDLToSwift/MergeDeclarations.swift index 5b042cbe..48b7b2d9 100644 --- a/Sources/WebIDLToSwift/MergeDeclarations.swift +++ b/Sources/WebIDLToSwift/MergeDeclarations.swift @@ -7,9 +7,6 @@ enum DeclarationMerger { "CustomElementConstructor", "ArrayBufferView", "RotationMatrixType", - // Mapped to `Int32` manually. This can't be represented as `Int64` due to `BigInt` representation on JS side, - // but as a pointer it can't be represented as floating point number either. - "GLintptr", ] static let ignoredIncludeTargets: Set = ["WorkerNavigator"] static let validExposures: Set = ["Window"] diff --git a/Sources/WebIDLToSwift/WebIDL+SwiftRepresentation.swift b/Sources/WebIDLToSwift/WebIDL+SwiftRepresentation.swift index c1a0b5dd..07ec10ed 100644 --- a/Sources/WebIDLToSwift/WebIDL+SwiftRepresentation.swift +++ b/Sources/WebIDLToSwift/WebIDL+SwiftRepresentation.swift @@ -5,7 +5,7 @@ extension IDLArgument: SwiftRepresentable { let type: SwiftSource if variadic { type = "\(idlType)..." - } else if idlType.isFunction, !optional, !idlType.nullable { + } else if idlType.closurePattern != nil && !optional && !idlType.nullable { type = "@escaping \(idlType)" } else { type = "\(idlType)" @@ -23,7 +23,7 @@ extension IDLArgument: SwiftRepresentable { } } -extension IDLAttribute: SwiftRepresentable, Initializable { +extension IDLAttribute: SwiftRepresentable { private var wrapperName: SwiftSource { "_\(raw: name)" } @@ -247,12 +247,16 @@ extension MergedInterface: SwiftRepresentable { ] let access: SwiftSource = openClasses.contains(name) ? "open" : "public" + let hasAsyncSequence: Bool let header: SwiftSource if partial { header = "public extension \(name)" + hasAsyncSequence = false } else { let inheritance = (parentClasses.isEmpty ? ["JSBridgedClass"] : parentClasses) + mixins + .filter { $0 != "AsyncSequence" } header = "\(access) class \(name): \(sequence: inheritance.map(SwiftSource.init(_:)))" + hasAsyncSequence = mixins.contains { $0 == "AsyncSequence" } } return """ @@ -269,13 +273,22 @@ extension MergedInterface: SwiftRepresentable { """ : "") public required init(unsafelyWrapping jsObject: JSObject) { - \(memberInits.joined(separator: "\n")) \(parentClasses.isEmpty ? "self.jsObject = jsObject" : "super.init(unsafelyWrapping: jsObject)") } """) \(body) } + + \(hasAsyncSequence ? + """ + #if canImport(JavaScriptEventLoop) + public extension \(name): AsyncSequence {} + #endif + """ : + "" + ) + """ } @@ -380,11 +393,13 @@ extension IDLIterableDeclaration: SwiftRepresentable, Initializable { var swiftRepresentation: SwiftSource { if async { return """ + #if canImport(JavaScriptEventLoop) public typealias Element = \(idlType[0]) @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public func makeAsyncIterator() -> ValueIterableAsyncIterator<\(ModuleState.className)> { ValueIterableAsyncIterator(sequence: self) } + #endif """ } else { return """