From c6a0bf4786199ba7f29f6fd6e89a5055f8c4bd2e Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 23 Apr 2025 11:27:56 +0100 Subject: [PATCH] Improve compatibility with Embedded Swift The change adds a conditional import excluding the JavaScriptEventLoop module that's not yet fully compatible with Embedded Swift. With the `JSObject` type from the JavaScriptKit dependency, which is also now compatible with Embedded Swift, record types are now mapped to `JSObject`. As for fields marked as optional in originating IDL, these are now correctly mapped as optional in Swift. --- Sources/WebIDLToSwift/IDLBuilder.swift | 8 +++++++- Sources/WebIDLToSwift/MergeDeclarations.swift | 6 ++++++ Sources/WebIDLToSwift/ModuleState.swift | 3 +++ .../UnionType+SwiftRepresentable.swift | 10 ++++------ .../WebIDL+SwiftRepresentation.swift | 20 ++++++++++++++----- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Sources/WebIDLToSwift/IDLBuilder.swift b/Sources/WebIDLToSwift/IDLBuilder.swift index 0a1374f3..21429dbb 100644 --- a/Sources/WebIDLToSwift/IDLBuilder.swift +++ b/Sources/WebIDLToSwift/IDLBuilder.swift @@ -3,6 +3,7 @@ import WebIDL enum IDLBuilder { static let basicDependencies = ["ECMAScript", "JavaScriptKit", "JavaScriptEventLoop"] + static let optionalDependencies = ["JavaScriptEventLoop"] static let preamble = """ // This file was auto-generated by WebIDLToSwift. DO NOT EDIT! @@ -42,7 +43,11 @@ enum IDLBuilder { dependencies.append("JavaScriptBigIntSupport") } - let formedPreamble = preamble + dependencies.map { "import \($0)" }.joined(separator: "\n") + let formedPreamble = preamble + (optionalDependencies.map { """ + #if canImport\($0) + import \($0) + #endif + """ } + dependencies.map { "import \($0)" }).joined(separator: "\n") try (formedPreamble + "\n\n" + content).write(toFile: path, atomically: true, encoding: .utf8) } @@ -54,6 +59,7 @@ enum IDLBuilder { var contents: [SwiftSource] = [] var state = ScopedState.root( + dictionaries: merged.dictionaries, interfaces: merged.interfaces, ignored: [ // variadic callbacks are unsupported diff --git a/Sources/WebIDLToSwift/MergeDeclarations.swift b/Sources/WebIDLToSwift/MergeDeclarations.swift index dfd9d0ee..5b042cbe 100644 --- a/Sources/WebIDLToSwift/MergeDeclarations.swift +++ b/Sources/WebIDLToSwift/MergeDeclarations.swift @@ -18,6 +18,10 @@ enum DeclarationMerger { private static func enhanceMembers(_ members: [IDLNode]) -> [IDLNode] { members.flatMap { member -> [IDLNode] in + if let named = member as? IDLNamed, named.name.contains("-") { + return [] + } + if let operation = member as? IDLOperation, case .generic("Promise", _) = operation.idlType?.value { @@ -223,6 +227,7 @@ enum DeclarationMerger { + [Typedefs(typedefs: allTypes)] + allNodes(ofType: IDLEnum.self) + allNodes(ofType: IDLCallbackInterface.self), + dictionaries: mergedDictionaries, interfaces: mergedInterfaces, types: mergedTypes // unions: unions @@ -231,6 +236,7 @@ enum DeclarationMerger { struct MergeResult { let declarations: [DeclarationFile] + let dictionaries: [String: MergedDictionary] let interfaces: [String: MergedInterface] let types: [String: IDLTypealias] // let unions: Set diff --git a/Sources/WebIDLToSwift/ModuleState.swift b/Sources/WebIDLToSwift/ModuleState.swift index b65f1536..86f38078 100644 --- a/Sources/WebIDLToSwift/ModuleState.swift +++ b/Sources/WebIDLToSwift/ModuleState.swift @@ -63,6 +63,7 @@ struct ScopedState { private(set) var this: SwiftSource! private(set) var className: SwiftSource! private(set) var interfaces: [String: MergedInterface]! + private(set) var dictionaries: [String: MergedDictionary]! private(set) var ignored: [String: Set]! private(set) var types: [String: IDLTypealias]! private(set) var override = false @@ -106,12 +107,14 @@ struct ScopedState { } static func root( + dictionaries: [String: MergedDictionary], interfaces: [String: MergedInterface], ignored: [String: Set], types: [String: IDLTypealias] ) -> Self { var newState = ModuleState.current newState.interfaces = interfaces + newState.dictionaries = dictionaries newState.ignored = ignored newState.types = types return newState diff --git a/Sources/WebIDLToSwift/UnionType+SwiftRepresentable.swift b/Sources/WebIDLToSwift/UnionType+SwiftRepresentable.swift index 70d21e0e..f4c4f7bd 100644 --- a/Sources/WebIDLToSwift/UnionType+SwiftRepresentable.swift +++ b/Sources/WebIDLToSwift/UnionType+SwiftRepresentable.swift @@ -74,7 +74,7 @@ extension UnionType: SwiftRepresentable { var initializers: [SwiftSource] { zip(sortedTypes, sortedNames).flatMap { (type, name) -> [SwiftSource] in let basicInitializer: [SwiftSource] = [""" - init(_ \(name): \(type)) { + public init(_ \(name): \(type)) { let val: \(self.name) = .\(name)(\(name)) self = val } @@ -162,10 +162,8 @@ extension SlimIDLType: SwiftRepresentable { case "FrozenArray", "ObservableArray": // ??? return ["Element == \(args[0])"] - case "Promise": + case "Promise", "record": return [] - case "record": - return ["Key == \(args[0])", "Value == \(args[1])"] default: fatalError("Unsupported generic type: \(name)") } @@ -190,7 +188,7 @@ extension SlimIDLType.TypeValue: SwiftRepresentable { case "Promise": return "JSPromise" case "record": - return "Dictionary" + return "JSObject" default: fatalError("Unsupported generic type: \(name)") } @@ -220,7 +218,7 @@ extension SlimIDLType.TypeValue: SwiftRepresentable { case "Promise": return "JSPromise" case "record": - return "[\(args[0]): \(args[1])]" + return "JSObject" default: fatalError("Unsupported generic type: \(name)") } diff --git a/Sources/WebIDLToSwift/WebIDL+SwiftRepresentation.swift b/Sources/WebIDLToSwift/WebIDL+SwiftRepresentation.swift index afe8dcf0..c1a0b5dd 100644 --- a/Sources/WebIDLToSwift/WebIDL+SwiftRepresentation.swift +++ b/Sources/WebIDLToSwift/WebIDL+SwiftRepresentation.swift @@ -88,6 +88,16 @@ extension IDLAttribute: SwiftRepresentable, Initializable { } } +extension IDLDictionary.Member { + var isOptional: Bool { + !required && !idlType.nullable && !idlType.isFunction + } + + var optionalSuffix: String { + isOptional ? "?" : "" + } +} + extension MergedDictionary: SwiftRepresentable { var swiftRepresentation: SwiftSource { """ @@ -98,7 +108,7 @@ extension MergedDictionary: SwiftRepresentable { """ } - private var membersWithPropertyWrapper: [(IDLDictionary.Member, SwiftSource)] { + private func membersWithPropertyWrapper(_ members: [IDLDictionary.Member]) -> [(IDLDictionary.Member, SwiftSource)] { members.map { ($0, $0.idlType.propertyWrapper(readonly: false)) } @@ -111,7 +121,7 @@ extension MergedDictionary: SwiftRepresentable { return """ public convenience init(\(sequence: params)) { let object = JSObject.global[\(ModuleState.source(for: "Object"))].function!.new() - \(lines: membersWithPropertyWrapper.map { member, wrapper in + \(lines: membersWithPropertyWrapper(members).map { member, wrapper in if member.idlType.isFunction { return """ \(wrapper)[\(ModuleState.source(for: member.name)), in: object] = \(member.name) @@ -126,7 +136,7 @@ extension MergedDictionary: SwiftRepresentable { } public required init(unsafelyWrapping object: JSObject) { - \(lines: membersWithPropertyWrapper.map { member, wrapper in + \(lines: membersWithPropertyWrapper(members).map { member, wrapper in "_\(raw: member.name) = \(wrapper)(jsObject: object, name: \(ModuleState.source(for: member.name)))" }) super.init(unsafelyWrapping: object) @@ -135,10 +145,10 @@ extension MergedDictionary: SwiftRepresentable { } private var swiftMembers: [SwiftSource] { - membersWithPropertyWrapper.map { member, wrapper in + self.membersWithPropertyWrapper(members).map { member, wrapper in """ @\(wrapper) - public var \(member.name): \(member.idlType) + public var \(member.name): \(member.idlType)\(member.optionalSuffix) """ } }