From 128b4279866ccd094c94a0744c8aa4916f76e316 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Thu, 27 Oct 2022 11:12:44 -0400 Subject: [PATCH 1/4] Refactored Reader/Writer on Swift to make external types work External types require that one swift source file can use the FfiConverter from another source file. This means that the read/write functions can't input a Reader/Writer class: - If the class is fileprivate, then this means the read/write methods must also be fileprivate since they're inputting that type, but this would totally prevent external types from working - If the class is internal/public, we'll get compile errors since both source files will try define the type. Instead, read/write now input standard swift types, which allow them to be shared between different source files. --- .../swift/templates/BooleanHelper.swift | 8 +- .../templates/CallbackInterfaceTemplate.swift | 18 +- .../bindings/swift/templates/CustomType.swift | 8 +- .../swift/templates/DurationHelper.swift | 12 +- .../swift/templates/EnumTemplate.swift | 14 +- .../swift/templates/ErrorTemplate.swift | 20 +- .../swift/templates/Float32Helper.swift | 8 +- .../swift/templates/Float64Helper.swift | 8 +- .../swift/templates/Int16Helper.swift | 8 +- .../swift/templates/Int32Helper.swift | 8 +- .../swift/templates/Int64Helper.swift | 8 +- .../bindings/swift/templates/Int8Helper.swift | 8 +- .../swift/templates/MapTemplate.swift | 16 +- .../swift/templates/ObjectTemplate.swift | 8 +- .../swift/templates/OptionalTemplate.swift | 14 +- .../swift/templates/RecordTemplate.swift | 8 +- .../swift/templates/RustBufferTemplate.swift | 181 +++++++++--------- .../swift/templates/SequenceTemplate.swift | 12 +- .../swift/templates/StringHelper.swift | 12 +- .../swift/templates/TimestampHelper.swift | 12 +- .../swift/templates/UInt16Helper.swift | 8 +- .../swift/templates/UInt32Helper.swift | 8 +- .../swift/templates/UInt64Helper.swift | 8 +- .../swift/templates/UInt8Helper.swift | 8 +- 24 files changed, 211 insertions(+), 212 deletions(-) diff --git a/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift b/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift index 231ca258ff..ad0856f2b5 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift @@ -10,11 +10,11 @@ fileprivate struct FfiConverterBool : FfiConverter { return value ? 1 : 0 } - static func read(from buf: Reader) throws -> Bool { - return try lift(buf.readInt()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { + return try lift(readInt(&buf)) } - static func write(_ value: Bool, into buf: Writer) { - buf.writeInt(lower(value)) + static func write(_ value: Bool, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift index 4acbf0599d..7b09c537b9 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -26,12 +26,12 @@ fileprivate let {{ foreign_callback }} : ForeignCallback = {%- if meth.arguments().len() != 0 -%} {#- Calling the concrete callback object #} - let reader = Reader(data: Data(rustBuffer: args)) + var reader = createReader(data: Data(rustBuffer: args)) {% if meth.return_type().is_some() %}let result = {% endif -%} {% if meth.throws() %}try {% endif -%} swiftCallbackInterface.{{ meth.name()|fn_name }}( {% for arg in meth.arguments() -%} - {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: reader) + {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader) {%- if !loop.last %}, {% endif %} {% endfor -%} ) @@ -44,9 +44,9 @@ fileprivate let {{ foreign_callback }} : ForeignCallback = {#- Packing up the return value into a RustBuffer #} {%- match meth.return_type() -%} {%- when Some with (return_type) -%} - let writer = Writer() - {{ return_type|write_fn }}(result, into: writer) - return RustBuffer(bytes: writer.bytes) + var writer = [UInt8]() + {{ return_type|write_fn }}(result, into: &writer) + return RustBuffer(bytes: writer) {%- else -%} return RustBuffer() {% endmatch -%} @@ -136,9 +136,9 @@ extension {{ ffi_converter_name }} : FfiConverter { return callback } - static func read(from buf: Reader) throws -> SwiftType { + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { ensureCallbackinitialized(); - let handle: Handle = try buf.readInt() + let handle: Handle = try readInt(&buf) return try lift(handle) } @@ -147,8 +147,8 @@ extension {{ ffi_converter_name }} : FfiConverter { return handleMap.insert(obj: v) } - static func write(_ v: SwiftType, into buf: Writer) { + static func write(_ v: SwiftType, into buf: inout [UInt8]) { ensureCallbackinitialized(); - buf.writeInt(lower(v)) + writeInt(&buf, lower(v)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift b/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift index 741d8d631c..af26c1adaa 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift @@ -33,14 +33,14 @@ public typealias {{ name }} = {{ concrete_type_name }} fileprivate struct FfiConverterType{{ name }} { {#- Custom type config supplied, use it to convert the builtin type #} - static func read(from buf: Reader) throws -> {{ name }} { - let builtinValue = try {{ builtin|read_fn }}(from: buf) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ name }} { + let builtinValue = try {{ builtin|read_fn }}(from: &buf) return {{ config.into_custom.render("builtinValue") }} } - static func write(_ value: {{ name }}, into buf: Writer) { + static func write(_ value: {{ name }}, into buf: inout [UInt8]) { let builtinValue = {{ config.from_custom.render("value") }} - return {{ builtin|write_fn }}(builtinValue, into: buf) + return {{ builtin|write_fn }}(builtinValue, into: &buf) } static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} { diff --git a/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift b/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift index f4371a079c..9a9eb61168 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift @@ -1,13 +1,13 @@ fileprivate struct FfiConverterDuration: FfiConverterRustBuffer { typealias SwiftType = TimeInterval - static func read(from buf: Reader) throws -> TimeInterval { - let seconds: UInt64 = try buf.readInt() - let nanoseconds: UInt32 = try buf.readInt() + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TimeInterval { + let seconds: UInt64 = try readInt(&buf) + let nanoseconds: UInt32 = try readInt(&buf) return Double(seconds) + (Double(nanoseconds) / 1.0e9) } - static func write(_ value: TimeInterval, into buf: Writer) { + static func write(_ value: TimeInterval, into buf: inout [UInt8]) { if value.rounded(.down) > Double(Int64.max) { fatalError("Duration overflow, exceeds max bounds supported by Uniffi") } @@ -18,7 +18,7 @@ fileprivate struct FfiConverterDuration: FfiConverterRustBuffer { let seconds = UInt64(value) let nanoseconds = UInt32((value - Double(seconds)) * 1.0e9) - buf.writeInt(seconds) - buf.writeInt(nanoseconds) + writeInt(&buf, seconds) + writeInt(&buf, nanoseconds) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift index 2feca61b1f..650d9b4419 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift @@ -10,13 +10,13 @@ public enum {{ type_name }} { fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { typealias SwiftType = {{ type_name }} - static func read(from buf: Reader) throws -> {{ type_name }} { - let variant: Int32 = try buf.readInt() + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let variant: Int32 = try readInt(&buf) switch variant { {% for variant in e.variants() %} case {{ loop.index }}: return .{{ variant.name()|enum_variant_swift }}{% if variant.has_fields() %}( {%- for field in variant.fields() %} - {{ field.name()|var_name }}: try {{ field|read_fn }}(from: buf) + {{ field.name()|var_name }}: try {{ field|read_fn }}(from: &buf) {%- if !loop.last %}, {% endif %} {%- endfor %} ){%- endif %} @@ -25,18 +25,18 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { } } - static func write(_ value: {{ type_name }}, into buf: Writer) { + static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { switch value { {% for variant in e.variants() %} {% if variant.has_fields() %} case let .{{ variant.name()|enum_variant_swift }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}): - buf.writeInt(Int32({{ loop.index }})) + writeInt(&buf, Int32({{ loop.index }})) {% for field in variant.fields() -%} - {{ field|write_fn }}({{ field.name()|var_name }}, into: buf) + {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf) {% endfor -%} {% else %} case .{{ variant.name()|enum_variant_swift }}: - buf.writeInt(Int32({{ loop.index }})) + writeInt(&buf, Int32({{ loop.index }})) {% endif %} {%- endfor %} } diff --git a/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift index 2a1c298686..880bba726c 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift @@ -18,15 +18,15 @@ public enum {{ type_name }} { fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { typealias SwiftType = {{ type_name }} - static func read(from buf: Reader) throws -> {{ type_name }} { - let variant: Int32 = try buf.readInt() + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let variant: Int32 = try readInt(&buf) switch variant { {% if e.is_flat() %} {% for variant in e.variants() %} case {{ loop.index }}: return .{{ variant.name()|class_name }}( - message: try {{ Type::String.borrow()|read_fn }}(from: buf) + message: try {{ Type::String.borrow()|read_fn }}(from: &buf) ) {% endfor %} @@ -35,7 +35,7 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { {% for variant in e.variants() %} case {{ loop.index }}: return .{{ variant.name()|class_name }}{% if variant.has_fields() -%}( {% for field in variant.fields() -%} - {{ field.name()|var_name }}: try {{ field|read_fn }}(from: buf) + {{ field.name()|var_name }}: try {{ field|read_fn }}(from: &buf) {%- if !loop.last %}, {% endif %} {% endfor -%} ){% endif -%} @@ -46,15 +46,15 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { } } - static func write(_ value: {{ type_name }}, into buf: Writer) { + static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { switch value { {% if e.is_flat() %} {% for variant in e.variants() %} case let .{{ variant.name()|class_name }}(message): - buf.writeInt(Int32({{ loop.index }})) - {{ Type::String.borrow()|write_fn }}(message, into: buf) + writeInt(&buf, Int32({{ loop.index }})) + {{ Type::String.borrow()|write_fn }}(message, into: &buf) {%- endfor %} {% else %} @@ -62,13 +62,13 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { {% for variant in e.variants() %} {% if variant.has_fields() %} case let .{{ variant.name()|class_name }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}): - buf.writeInt(Int32({{ loop.index }})) + writeInt(&buf, Int32({{ loop.index }})) {% for field in variant.fields() -%} - {{ field|write_fn }}({{ field.name()|var_name }}, into: buf) + {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf) {% endfor -%} {% else %} case .{{ variant.name()|class_name }}: - buf.writeInt(Int32({{ loop.index }})) + writeInt(&buf, Int32({{ loop.index }})) {% endif %} {%- endfor %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift index 5800ca6754..6f734c44a6 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterFloat: FfiConverterPrimitive { typealias FfiType = Float typealias SwiftType = Float - static func read(from buf: Reader) throws -> Float { - return try lift(buf.readFloat()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Float { + return try lift(readFloat(&buf)) } - static func write(_ value: Float, into buf: Writer) { - buf.writeFloat(lower(value)) + static func write(_ value: Float, into buf: inout [UInt8]) { + writeFloat(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift index b96dcd24a0..0fa2be63fc 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterDouble: FfiConverterPrimitive { typealias FfiType = Double typealias SwiftType = Double - static func read(from buf: Reader) throws -> Double { - return try lift(buf.readDouble()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Double { + return try lift(readDouble(&buf)) } - static func write(_ value: Double, into buf: Writer) { - buf.writeDouble(lower(value)) + static func write(_ value: Double, into buf: inout [UInt8]) { + writeDouble(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift index e924b3fa45..1e8bdd2bca 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterInt16: FfiConverterPrimitive { typealias FfiType = Int16 typealias SwiftType = Int16 - static func read(from buf: Reader) throws -> Int16 { - return try lift(buf.readInt()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int16 { + return try lift(readInt(&buf)) } - static func write(_ value: Int16, into buf: Writer) { - buf.writeInt(lower(value)) + static func write(_ value: Int16, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift index e6f20773ba..472b613401 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterInt32: FfiConverterPrimitive { typealias FfiType = Int32 typealias SwiftType = Int32 - static func read(from buf: Reader) throws -> Int32 { - return try lift(buf.readInt()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int32 { + return try lift(readInt(&buf)) } - static func write(_ value: Int32, into buf: Writer) { - buf.writeInt(lower(value)) + static func write(_ value: Int32, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift index ef60f5224a..dacb1d287b 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterInt64: FfiConverterPrimitive { typealias FfiType = Int64 typealias SwiftType = Int64 - static func read(from buf: Reader) throws -> Int64 { - return try lift(buf.readInt()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int64 { + return try lift(readInt(&buf)) } - static func write(_ value: Int64, into buf: Writer) { - buf.writeInt(lower(value)) + static func write(_ value: Int64, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift index 5990068c48..1a9627b48d 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterInt8: FfiConverterPrimitive { typealias FfiType = Int8 typealias SwiftType = Int8 - static func read(from buf: Reader) throws -> Int8 { - return try lift(buf.readInt()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int8 { + return try lift(readInt(&buf)) } - static func write(_ value: Int8, into buf: Writer) { - buf.writeInt(lower(value)) + static func write(_ value: Int8, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift index 20a99a11a9..f62f0c8244 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift @@ -1,20 +1,20 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { - fileprivate static func write(_ value: {{ type_name }}, into buf: Writer) { + fileprivate static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { let len = Int32(value.count) - buf.writeInt(len) + writeInt(&buf, len) for (key, value) in value { - {{ key_type|write_fn }}(key, into: buf) - {{ value_type|write_fn }}(value, into: buf) + {{ key_type|write_fn }}(key, into: &buf) + {{ value_type|write_fn }}(value, into: &buf) } } - fileprivate static func read(from buf: Reader) throws -> {{ type_name }} { - let len: Int32 = try buf.readInt() + fileprivate static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let len: Int32 = try readInt(&buf) var dict = {{ type_name }}() dict.reserveCapacity(Int(len)) for _ in 0.. {{ type_name }} { - let v: UInt64 = try buf.readInt() + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let v: UInt64 = try readInt(&buf) // The Rust code won't compile if a pointer won't fit in a UInt64. // We have to go via `UInt` because that's the thing that's the size of a pointer. let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) @@ -72,10 +72,10 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverter { return try lift(ptr!) } - static func write(_ value: {{ type_name }}, into buf: Writer) { + static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { // This fiddling is because `Int` is the thing that's the same size as a pointer. // The Rust code won't compile if a pointer won't fit in a `UInt64`. - buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) } static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { diff --git a/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift index b4cd751d9d..92acad8d0a 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift @@ -1,19 +1,19 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { typealias SwiftType = {{ type_name }} - static func write(_ value: SwiftType, into buf: Writer) { + static func write(_ value: SwiftType, into buf: inout [UInt8]) { guard let value = value else { - buf.writeInt(Int8(0)) + writeInt(&buf, Int8(0)) return } - buf.writeInt(Int8(1)) - {{ inner_type|write_fn }}(value, into: buf) + writeInt(&buf, Int8(1)) + {{ inner_type|write_fn }}(value, into: &buf) } - static func read(from buf: Reader) throws -> SwiftType { - switch try buf.readInt() as Int8 { + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { case 0: return nil - case 1: return try {{ inner_type|read_fn }}(from: buf) + case 1: return try {{ inner_type|read_fn }}(from: &buf) default: throw UniffiInternalError.unexpectedOptionalTag } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift index d7ed69d272..f8b664bd0c 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift @@ -33,18 +33,18 @@ extension {{ type_name }}: Equatable, Hashable { {% endif %} fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { - fileprivate static func read(from buf: Reader) throws -> {{ type_name }} { + fileprivate static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { return try {{ type_name }}( {%- for field in rec.fields() %} - {{ field.name()|var_name }}: {{ field|read_fn }}(from: buf) + {{ field.name()|var_name }}: {{ field|read_fn }}(from: &buf) {%- if !loop.last %}, {% endif %} {%- endfor %} ) } - fileprivate static func write(_ value: {{ type_name }}, into buf: Writer) { + fileprivate static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { {%- for field in rec.fields() %} - {{ field|write_fn }}(value.{{ field.name()|var_name }}, into: buf) + {{ field|write_fn }}(value.{{ field.name()|var_name }}, into: &buf) {%- endfor %} } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift index 79ed74c50a..1c14dce9cc 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift @@ -39,101 +39,100 @@ fileprivate extension Data { } } -// A helper class to read values out of a byte buffer. -fileprivate class Reader { - let data: Data - var offset: Data.Index - - init(data: Data) { - self.data = data - self.offset = 0 - } +// Define reader functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. +// +// With external types, one swift source file needs to be able to call the read +// method on another source file's FfiConverter, but then what visibility +// should Reader have? +// - If Reader is fileprivate, then this means the read() must also +// be fileprivate, which doesn't work with external types. +// - If Reader is internal/public, we'll get compile errors since both source +// files will try define the same type. +// +// Instead, the read() method and these helper functions input a tuple of data + +fileprivate func createReader(data: Data) -> (data: Data, offset: Data.Index) { + (data: data, offset: 0) +} - // Reads an integer at the current offset, in big-endian order, and advances - // the offset on success. Throws if reading the integer would move the - // offset past the end of the buffer. - func readInt() throws -> T { - let range = offset...size - guard data.count >= range.upperBound else { - throw UniffiInternalError.bufferOverflow - } - if T.self == UInt8.self { - let value = data[offset] - offset += 1 - return value as! T - } - var value: T = 0 - let _ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0, from: range)}) - offset = range.upperBound - return value.bigEndian - } +// Reads an integer at the current offset, in big-endian order, and advances +// the offset on success. Throws if reading the integer would move the +// offset past the end of the buffer. +fileprivate func readInt(_ reader: inout (data: Data, offset: Data.Index)) throws -> T { + let range = reader.offset...size + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = reader.data[reader.offset] + reader.offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value, { reader.data.copyBytes(to: $0, from: range)}) + reader.offset = range.upperBound + return value.bigEndian +} - // Reads an arbitrary number of bytes, to be used to read - // raw bytes, this is useful when lifting strings - func readBytes(count: Int) throws -> Array { - let range = offset..<(offset+count) - guard data.count >= range.upperBound else { - throw UniffiInternalError.bufferOverflow - } - var value = [UInt8](repeating: 0, count: count) - value.withUnsafeMutableBufferPointer({ buffer in - data.copyBytes(to: buffer, from: range) - }) - offset = range.upperBound - return value - } +// Reads an arbitrary number of bytes, to be used to read +// raw bytes, this is useful when lifting strings +fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array { + let range = reader.offset..<(reader.offset+count) + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer({ buffer in + reader.data.copyBytes(to: buffer, from: range) + }) + reader.offset = range.upperBound + return value +} - // Reads a float at the current offset. - @inlinable - func readFloat() throws -> Float { - return Float(bitPattern: try readInt()) - } +// Reads a float at the current offset. +fileprivate func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float { + return Float(bitPattern: try readInt(&reader)) +} - // Reads a float at the current offset. - @inlinable - func readDouble() throws -> Double { - return Double(bitPattern: try readInt()) - } +// Reads a float at the current offset. +fileprivate func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double { + return Double(bitPattern: try readInt(&reader)) +} - // Indicates if the offset has reached the end of the buffer. - @inlinable - func hasRemaining() -> Bool { - return offset < data.count - } +// Indicates if the offset has reached the end of the buffer. +fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool { + return reader.offset < reader.data.count } -// A helper class to write values into a byte buffer. -fileprivate class Writer { - var bytes: [UInt8] - var offset: Array.Index +// Define writer functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. See the above discussion on Readers for details. - init() { - self.bytes = [] - self.offset = 0 - } +fileprivate func createWriter() -> [UInt8] { + return [] +} - func writeBytes(_ byteArr: S) where S: Sequence, S.Element == UInt8 { - bytes.append(contentsOf: byteArr) - } +fileprivate func writeBytes(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 { + writer.append(contentsOf: byteArr) +} - // Writes an integer in big-endian order. - // - // Warning: make sure what you are trying to write - // is in the correct type! - func writeInt(_ value: T) { - var value = value.bigEndian - withUnsafeBytes(of: &value) { bytes.append(contentsOf: $0) } - } +// Writes an integer in big-endian order. +// +// Warning: make sure what you are trying to write +// is in the correct type! +fileprivate func writeInt(_ writer: inout [UInt8], _ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) } +} - @inlinable - func writeFloat(_ value: Float) { - writeInt(value.bitPattern) - } +fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) { + writeInt(&writer, value.bitPattern) +} - @inlinable - func writeDouble(_ value: Double) { - writeInt(value.bitPattern) - } +fileprivate func writeDouble(_ writer: inout [UInt8], _ value: Double) { + writeInt(&writer, value.bitPattern) } // Protocol for types that transfer other types across the FFI. This is @@ -144,8 +143,8 @@ fileprivate protocol FfiConverter { static func lift(_ value: FfiType) throws -> SwiftType static func lower(_ value: SwiftType) -> FfiType - static func read(from buf: Reader) throws -> SwiftType - static func write(_ value: SwiftType, into buf: Writer) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType + static func write(_ value: SwiftType, into buf: inout [UInt8]) } // Types conforming to `Primitive` pass themselves directly over the FFI. @@ -167,9 +166,9 @@ fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustB extension FfiConverterRustBuffer { static func lift(_ buf: RustBuffer) throws -> SwiftType { - let reader = Reader(data: Data(rustBuffer: buf)) - let value = try read(from: reader) - if reader.hasRemaining() { + var reader = createReader(data: Data(rustBuffer: buf)) + let value = try read(from: &reader) + if hasRemaining(reader) { throw UniffiInternalError.incompleteData } buf.deallocate() @@ -177,8 +176,8 @@ extension FfiConverterRustBuffer { } static func lower(_ value: SwiftType) -> RustBuffer { - let writer = Writer() - write(value, into: writer) - return RustBuffer(bytes: writer.bytes) + var writer = createWriter() + write(value, into: &writer) + return RustBuffer(bytes: writer) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift index 4cd104f5dd..cdf85f83c4 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift @@ -1,20 +1,20 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { typealias SwiftType = {{ type_name }} - static func write(_ value: {{ type_name }}, into buf: Writer) { + static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { let len = Int32(value.count) - buf.writeInt(len) + writeInt(&buf, len) for item in value { - {{ inner_type|write_fn }}(item, into: buf) + {{ inner_type|write_fn }}(item, into: &buf) } } - static func read(from buf: Reader) throws -> {{ type_name }} { - let len: Int32 = try buf.readInt() + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let len: Int32 = try readInt(&buf) var seq = {{ type_name }}() seq.reserveCapacity(Int(len)) for _ in 0 ..< len { - seq.append(try {{ inner_type|read_fn }}(from: buf)) + seq.append(try {{ inner_type|read_fn }}(from: &buf)) } return seq } diff --git a/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift b/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift index c6ca0ad981..dfb9ece6b7 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift @@ -24,14 +24,14 @@ fileprivate struct FfiConverterString: FfiConverter { } } - static func read(from buf: Reader) throws -> String { - let len: Int32 = try buf.readInt() - return String(bytes: try buf.readBytes(count: Int(len)), encoding: String.Encoding.utf8)! + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { + let len: Int32 = try readInt(&buf) + return String(bytes: try readBytes(&buf, count: Int(len)), encoding: String.Encoding.utf8)! } - static func write(_ value: String, into buf: Writer) { + static func write(_ value: String, into buf: inout [UInt8]) { let len = Int32(value.utf8.count) - buf.writeInt(len) - buf.writeBytes(value.utf8) + writeInt(&buf, len) + writeBytes(&buf, value.utf8) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift b/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift index 05aaade2d2..075a4b8139 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift @@ -1,9 +1,9 @@ fileprivate struct FfiConverterTimestamp: FfiConverterRustBuffer { typealias SwiftType = Date - static func read(from buf: Reader) throws -> Date { - let seconds: Int64 = try buf.readInt() - let nanoseconds: UInt32 = try buf.readInt() + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Date { + let seconds: Int64 = try readInt(&buf) + let nanoseconds: UInt32 = try readInt(&buf) if seconds >= 0 { let delta = Double(seconds) + (Double(nanoseconds) / 1.0e9) return Date.init(timeIntervalSince1970: delta) @@ -13,7 +13,7 @@ fileprivate struct FfiConverterTimestamp: FfiConverterRustBuffer { } } - static func write(_ value: Date, into buf: Writer) { + static func write(_ value: Date, into buf: inout [UInt8]) { var delta = value.timeIntervalSince1970 var sign: Int64 = 1 if delta < 0 { @@ -28,7 +28,7 @@ fileprivate struct FfiConverterTimestamp: FfiConverterRustBuffer { } let seconds = Int64(delta) let nanoseconds = UInt32((delta - Double(seconds)) * 1.0e9) - buf.writeInt(sign * seconds) - buf.writeInt(nanoseconds) + writeInt(&buf, sign * seconds) + writeInt(&buf, nanoseconds) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift index cab25cb354..e9c811fc19 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterUInt16: FfiConverterPrimitive { typealias FfiType = UInt16 typealias SwiftType = UInt16 - static func read(from buf: Reader) throws -> UInt16 { - return try lift(buf.readInt()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt16 { + return try lift(readInt(&buf)) } - static func write(_ value: SwiftType, into buf: Writer) { - buf.writeInt(lower(value)) + static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift index 829f3e498e..073595f4c2 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterUInt32: FfiConverterPrimitive { typealias FfiType = UInt32 typealias SwiftType = UInt32 - static func read(from buf: Reader) throws -> UInt32 { - return try lift(buf.readInt()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 { + return try lift(readInt(&buf)) } - static func write(_ value: SwiftType, into buf: Writer) { - buf.writeInt(lower(value)) + static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift index 64019d5191..c0e2490abe 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterUInt64: FfiConverterPrimitive { typealias FfiType = UInt64 typealias SwiftType = UInt64 - static func read(from buf: Reader) throws -> UInt64 { - return try lift(buf.readInt()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt64 { + return try lift(readInt(&buf)) } - static func write(_ value: SwiftType, into buf: Writer) { - buf.writeInt(lower(value)) + static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift index 84aa33473d..f27d921b19 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterUInt8: FfiConverterPrimitive { typealias FfiType = UInt8 typealias SwiftType = UInt8 - static func read(from buf: Reader) throws -> UInt8 { - return try lift(buf.readInt()) + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 { + return try lift(readInt(&buf)) } - static func write(_ value: UInt8, into buf: Writer) { - buf.writeInt(lower(value)) + static func write(_ value: UInt8, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) } } From 415f881f5d6c5948e124812670e244c4423d9446 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Thu, 27 Oct 2022 12:54:29 -0400 Subject: [PATCH 2/4] Renamed callback interface types to avoid conflicts "Handle" is a common name that's likely to cause name conflicts with user-defined types. This is exactly what happened when I tried to run the ext-types tests, which include a type named "Handle". --- .../templates/CallbackInterfaceRuntime.swift | 22 +++++++++---------- .../templates/CallbackInterfaceTemplate.swift | 14 ++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift index 9db2ee4f4f..9ab9bdbe48 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift @@ -6,17 +6,17 @@ fileprivate extension NSLock { } } -fileprivate typealias Handle = UInt64 -fileprivate class ConcurrentHandleMap { - private var leftMap: [Handle: T] = [:] - private var counter: [Handle: UInt64] = [:] - private var rightMap: [ObjectIdentifier: Handle] = [:] +fileprivate typealias UniFFICallbackHandle = UInt64 +fileprivate class UniFFICallbackHandleMap { + private var leftMap: [UniFFICallbackHandle: T] = [:] + private var counter: [UniFFICallbackHandle: UInt64] = [:] + private var rightMap: [ObjectIdentifier: UniFFICallbackHandle] = [:] private let lock = NSLock() - private var currentHandle: Handle = 0 - private let stride: Handle = 1 + private var currentHandle: UniFFICallbackHandle = 0 + private let stride: UniFFICallbackHandle = 1 - func insert(obj: T) -> Handle { + func insert(obj: T) -> UniFFICallbackHandle { lock.withLock { let id = ObjectIdentifier(obj as AnyObject) let handle = rightMap[id] ?? { @@ -31,18 +31,18 @@ fileprivate class ConcurrentHandleMap { } } - func get(handle: Handle) -> T? { + func get(handle: UniFFICallbackHandle) -> T? { lock.withLock { leftMap[handle] } } - func delete(handle: Handle) { + func delete(handle: UniFFICallbackHandle) { remove(handle: handle) } @discardableResult - func remove(handle: Handle) -> T? { + func remove(handle: UniFFICallbackHandle) -> T? { lock.withLock { defer { counter[handle] = (counter[handle] ?? 1) - 1 } guard counter[handle] == 1 else { return leftMap[handle] } diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift index 7b09c537b9..763eae2c18 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -16,7 +16,7 @@ public protocol {{ type_name }} : AnyObject { // The ForeignCallback that is passed to Rust. fileprivate let {{ foreign_callback }} : ForeignCallback = - { (handle: Handle, method: Int32, args: RustBuffer, out_buf: UnsafeMutablePointer) -> Int32 in + { (handle: UniFFICallbackHandle, method: Int32, args: RustBuffer, out_buf: UnsafeMutablePointer) -> Int32 in {% for meth in cbi.methods() -%} {%- let method_name = format!("invoke_{}", meth.name())|fn_name -%} @@ -116,19 +116,19 @@ fileprivate struct {{ ffi_converter_name }} { } } - static func drop(handle: Handle) { + static func drop(handle: UniFFICallbackHandle) { handleMap.remove(handle: handle) } - private static var handleMap = ConcurrentHandleMap<{{ type_name }}>() + private static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>() } extension {{ ffi_converter_name }} : FfiConverter { typealias SwiftType = {{ type_name }} // We can use Handle as the FFIType because it's a typealias to UInt64 - typealias FfiType = Handle + typealias FfiType = UniFFICallbackHandle - static func lift(_ handle: Handle) throws -> SwiftType { + static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType { ensureCallbackinitialized(); guard let callback = handleMap.get(handle: handle) else { throw UniffiInternalError.unexpectedStaleHandle @@ -138,11 +138,11 @@ extension {{ ffi_converter_name }} : FfiConverter { static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { ensureCallbackinitialized(); - let handle: Handle = try readInt(&buf) + let handle: UniFFICallbackHandle = try readInt(&buf) return try lift(handle) } - static func lower(_ v: SwiftType) -> Handle { + static func lower(_ v: SwiftType) -> UniFFICallbackHandle { ensureCallbackinitialized(); return handleMap.insert(obj: v) } From 1891a52045c89ecb9331e43038b82e4b9495efb8 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Thu, 27 Oct 2022 13:03:11 -0400 Subject: [PATCH 3/4] Added support for swift external types This code assumes that all generate files will be compiled into a single module, which makes external type support quite trivial. - Updated the swift testing code to handle multiple UDL files. - Made the custom type, enum, object, and record, FfiConverters public to allow them to be used with external types. - Made the FfiConverter methods public. I didn't make the protocols themselves public, since that would result in duplicate definitions when different UniFFI-generated swift files were linked together, but I did make each individual method public. - Added Swift external types tests. - Fixed a bug in the CustomType template. I'm pretty sure we should be using `type_ffi_lowered` instead of `ffi_type_name` there since it's Swift code rather than a C header file. - Added docs for configuring external types --- CHANGELOG.md | 4 + docs/manual/src/udl/ext_types_external.md | 24 ++ .../tests/bindings/test_imported_types.swift | 14 ++ .../lib/tests/test_generated_bindings.rs | 1 + .../src/bindings/swift/gen_swift/external.rs | 25 +++ .../src/bindings/swift/gen_swift/mod.rs | 3 +- .../swift/templates/BooleanHelper.swift | 8 +- .../templates/CallbackInterfaceTemplate.swift | 8 +- .../bindings/swift/templates/CustomType.swift | 31 ++- .../swift/templates/DurationHelper.swift | 4 +- .../swift/templates/EnumTemplate.swift | 6 +- .../swift/templates/ErrorTemplate.swift | 6 +- .../swift/templates/Float32Helper.swift | 4 +- .../swift/templates/Float64Helper.swift | 4 +- .../swift/templates/Int16Helper.swift | 4 +- .../swift/templates/Int32Helper.swift | 4 +- .../swift/templates/Int64Helper.swift | 4 +- .../bindings/swift/templates/Int8Helper.swift | 4 +- .../swift/templates/MapTemplate.swift | 4 +- .../swift/templates/ObjectTemplate.swift | 10 +- .../swift/templates/OptionalTemplate.swift | 4 +- .../swift/templates/RecordTemplate.swift | 6 +- .../swift/templates/RustBufferTemplate.swift | 8 +- .../swift/templates/SequenceTemplate.swift | 4 +- .../swift/templates/StringHelper.swift | 8 +- .../swift/templates/TimestampHelper.swift | 4 +- .../src/bindings/swift/templates/Types.swift | 1 - .../swift/templates/UInt16Helper.swift | 4 +- .../swift/templates/UInt32Helper.swift | 4 +- .../swift/templates/UInt64Helper.swift | 4 +- .../swift/templates/UInt8Helper.swift | 4 +- uniffi_bindgen/src/bindings/swift/test.rs | 212 ++++++++++++------ uniffi_macros/src/util.rs | 1 - uniffi_testing/src/lib.rs | 19 +- 34 files changed, 313 insertions(+), 142 deletions(-) create mode 100644 fixtures/ext-types/lib/tests/bindings/test_imported_types.swift create mode 100644 uniffi_bindgen/src/bindings/swift/gen_swift/external.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f4db6dc5b1..941a6aaeaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ [All changes in [[UnreleasedVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.21.0...HEAD). +### What's changed + +- Added support for Swift external types + ## v0.21.0 - (_2022-10-14_) [All changes in v0.21.0](https://github.com/mozilla/uniffi-rs/compare/v0.20.0...v0.21.0). diff --git a/docs/manual/src/udl/ext_types_external.md b/docs/manual/src/udl/ext_types_external.md index 80f985203d..73b325427f 100644 --- a/docs/manual/src/udl/ext_types_external.md +++ b/docs/manual/src/udl/ext_types_external.md @@ -49,3 +49,27 @@ include!(concat!(env!("OUT_DIR"), "/consuming_crate.uniffi.rs")); Your `Cargo.toml` must reference the external crate as normal. The `External` attribute can be specified on dictionaries, enums and errors. + +## Foreign bindings + +The foreign bindings will also need to know how to access the external type, +which varies slightly for each language: + +### Kotlin + +For Kotlin, the generated code needs to import the external types from the +Kotlin module that corresponds to the Rust crate. By default, UniFFI assumes +that the Kotlin module name matches the Rust crate name, but this can be +configured in `uniffi.toml` with an entry like this: + +``` +[bindings.kotlin.external_packages] +# Map the crate names from [External={name}] into Kotlin package names +rust-crate-name = "kotlin.package.name" +``` + +### Swift + +For Swift, you must compile all generated `.swift` files together in a single +module since the generate code expects that it can access external types +without importing them. diff --git a/fixtures/ext-types/lib/tests/bindings/test_imported_types.swift b/fixtures/ext-types/lib/tests/bindings/test_imported_types.swift new file mode 100644 index 0000000000..bdb326529d --- /dev/null +++ b/fixtures/ext-types/lib/tests/bindings/test_imported_types.swift @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import imported_types_lib +import Foundation + +let ct = getCombinedType(value: nil) +assert(ct.uot.sval == "hello") +assert(ct.guid == "a-guid") +assert(ct.url == URL(string: "http://example.com/")) + +let ct2 = getCombinedType(value: ct) +assert(ct == ct2) diff --git a/fixtures/ext-types/lib/tests/test_generated_bindings.rs b/fixtures/ext-types/lib/tests/test_generated_bindings.rs index fed9ee73d7..70c1eb0142 100644 --- a/fixtures/ext-types/lib/tests/test_generated_bindings.rs +++ b/fixtures/ext-types/lib/tests/test_generated_bindings.rs @@ -1,4 +1,5 @@ uniffi_macros::build_foreign_language_testcases!( "tests/bindings/test_imported_types.kts", "tests/bindings/test_imported_types.py", + "tests/bindings/test_imported_types.swift", ); diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs new file mode 100644 index 0000000000..c39236bcb3 --- /dev/null +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::backend::{CodeOracle, CodeType}; + +pub struct ExternalCodeType { + name: String, +} + +impl ExternalCodeType { + pub fn new(name: String) -> Self { + ExternalCodeType { name } + } +} + +impl CodeType for ExternalCodeType { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + self.name.clone() + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.name) + } +} diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index b76354b2f0..d309187376 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -21,6 +21,7 @@ mod compounds; mod custom; mod enum_; mod error; +mod external; mod miscellany; mod object; mod primitives; @@ -323,7 +324,7 @@ impl SwiftCodeOracle { Type::Optional(inner) => Box::new(compounds::OptionalCodeType::new(*inner)), Type::Sequence(inner) => Box::new(compounds::SequenceCodeType::new(*inner)), Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)), - Type::External { .. } => panic!("no support for external types yet"), + Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), Type::Unresolved { name } => { unreachable!("Type `{name}` must be resolved before calling create_code_type") diff --git a/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift b/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift index ad0856f2b5..465e519628 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift @@ -2,19 +2,19 @@ fileprivate struct FfiConverterBool : FfiConverter { typealias FfiType = Int8 typealias SwiftType = Bool - static func lift(_ value: Int8) throws -> Bool { + public static func lift(_ value: Int8) throws -> Bool { return value != 0 } - static func lower(_ value: Bool) -> Int8 { + public static func lower(_ value: Bool) -> Int8 { return value ? 1 : 0 } - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { return try lift(readInt(&buf)) } - static func write(_ value: Bool, into buf: inout [UInt8]) { + public static func write(_ value: Bool, into buf: inout [UInt8]) { writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift index 763eae2c18..d061b48e0b 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -128,7 +128,7 @@ extension {{ ffi_converter_name }} : FfiConverter { // We can use Handle as the FFIType because it's a typealias to UInt64 typealias FfiType = UniFFICallbackHandle - static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType { + public static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType { ensureCallbackinitialized(); guard let callback = handleMap.get(handle: handle) else { throw UniffiInternalError.unexpectedStaleHandle @@ -136,18 +136,18 @@ extension {{ ffi_converter_name }} : FfiConverter { return callback } - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { ensureCallbackinitialized(); let handle: UniFFICallbackHandle = try readInt(&buf) return try lift(handle) } - static func lower(_ v: SwiftType) -> UniFFICallbackHandle { + public static func lower(_ v: SwiftType) -> UniFFICallbackHandle { ensureCallbackinitialized(); return handleMap.insert(obj: v) } - static func write(_ v: SwiftType, into buf: inout [UInt8]) { + public static func write(_ v: SwiftType, into buf: inout [UInt8]) { ensureCallbackinitialized(); writeInt(&buf, lower(v)) } diff --git a/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift b/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift index af26c1adaa..b6f7542ab8 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift @@ -1,4 +1,4 @@ -{%- let ffi_type_name=builtin.ffi_type().borrow()|ffi_type_name %} +{%- let ffi_type_name=builtin.ffi_type().borrow()|type_ffi_lowered %} {%- match config.custom_types.get(name.as_str()) %} {%- when None %} {#- No config, just forward all methods to our builtin type #} @@ -7,7 +7,23 @@ * is needed because the UDL type name is used in function/method signatures. */ public typealias {{ name }} = {{ builtin|type_name }} -fileprivate typealias FfiConverterType{{ name }} = {{ builtin|ffi_converter_name }} +public struct FfiConverterType{{ name }}: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ name }} { + return try {{ builtin|read_fn }}(from: &buf) + } + + public static func write(_ value: {{ name }}, into buf: inout [UInt8]) { + return {{ builtin|write_fn }}(value, into: &buf) + } + + public static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} { + return try {{ builtin|lift_fn }}(value) + } + + public static func lower(_ value: {{ name }}) -> {{ ffi_type_name }} { + return {{ builtin|lower_fn }}(value) + } +} {%- when Some with (config) %} @@ -30,29 +46,28 @@ public typealias {{ name }} = {{ concrete_type_name }} {%- else %} {%- endmatch %} -fileprivate struct FfiConverterType{{ name }} { +public struct FfiConverterType{{ name }}: FfiConverter { {#- Custom type config supplied, use it to convert the builtin type #} - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ name }} { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ name }} { let builtinValue = try {{ builtin|read_fn }}(from: &buf) return {{ config.into_custom.render("builtinValue") }} } - static func write(_ value: {{ name }}, into buf: inout [UInt8]) { + public static func write(_ value: {{ name }}, into buf: inout [UInt8]) { let builtinValue = {{ config.from_custom.render("value") }} return {{ builtin|write_fn }}(builtinValue, into: &buf) } - static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} { + public static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} { let builtinValue = try {{ builtin|lift_fn }}(value) return {{ config.into_custom.render("builtinValue") }} } - static func lower(_ value: {{ name }}) -> {{ ffi_type_name }} { + public static func lower(_ value: {{ name }}) -> {{ ffi_type_name }} { let builtinValue = {{ config.from_custom.render("value") }} return {{ builtin|lower_fn }}(builtinValue) } - } {%- endmatch %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift b/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift index 9a9eb61168..c2aa49e9d1 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift @@ -1,13 +1,13 @@ fileprivate struct FfiConverterDuration: FfiConverterRustBuffer { typealias SwiftType = TimeInterval - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TimeInterval { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TimeInterval { let seconds: UInt64 = try readInt(&buf) let nanoseconds: UInt32 = try readInt(&buf) return Double(seconds) + (Double(nanoseconds) / 1.0e9) } - static func write(_ value: TimeInterval, into buf: inout [UInt8]) { + public static func write(_ value: TimeInterval, into buf: inout [UInt8]) { if value.rounded(.down) > Double(Int64.max) { fatalError("Duration overflow, exceeds max bounds supported by Uniffi") } diff --git a/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift index 650d9b4419..e23b107c32 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift @@ -7,10 +7,10 @@ public enum {{ type_name }} { {% endfor %} } -fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { +public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { typealias SwiftType = {{ type_name }} - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { let variant: Int32 = try readInt(&buf) switch variant { {% for variant in e.variants() %} @@ -25,7 +25,7 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { } } - static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { switch value { {% for variant in e.variants() %} {% if variant.has_fields() %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift index 880bba726c..b2c4bfe492 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift @@ -15,10 +15,10 @@ public enum {{ type_name }} { {%- endif %} } -fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { +public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { typealias SwiftType = {{ type_name }} - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { let variant: Int32 = try readInt(&buf) switch variant { @@ -46,7 +46,7 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { } } - static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { switch value { {% if e.is_flat() %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift index 6f734c44a6..fb986beab6 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterFloat: FfiConverterPrimitive { typealias FfiType = Float typealias SwiftType = Float - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Float { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Float { return try lift(readFloat(&buf)) } - static func write(_ value: Float, into buf: inout [UInt8]) { + public static func write(_ value: Float, into buf: inout [UInt8]) { writeFloat(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift index 0fa2be63fc..74421c045c 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterDouble: FfiConverterPrimitive { typealias FfiType = Double typealias SwiftType = Double - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Double { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Double { return try lift(readDouble(&buf)) } - static func write(_ value: Double, into buf: inout [UInt8]) { + public static func write(_ value: Double, into buf: inout [UInt8]) { writeDouble(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift index 1e8bdd2bca..ac57fc5e58 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterInt16: FfiConverterPrimitive { typealias FfiType = Int16 typealias SwiftType = Int16 - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int16 { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int16 { return try lift(readInt(&buf)) } - static func write(_ value: Int16, into buf: inout [UInt8]) { + public static func write(_ value: Int16, into buf: inout [UInt8]) { writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift index 472b613401..0ccfc13e4e 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterInt32: FfiConverterPrimitive { typealias FfiType = Int32 typealias SwiftType = Int32 - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int32 { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int32 { return try lift(readInt(&buf)) } - static func write(_ value: Int32, into buf: inout [UInt8]) { + public static func write(_ value: Int32, into buf: inout [UInt8]) { writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift index dacb1d287b..d7d4082933 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterInt64: FfiConverterPrimitive { typealias FfiType = Int64 typealias SwiftType = Int64 - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int64 { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int64 { return try lift(readInt(&buf)) } - static func write(_ value: Int64, into buf: inout [UInt8]) { + public static func write(_ value: Int64, into buf: inout [UInt8]) { writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift index 1a9627b48d..f2387e4340 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterInt8: FfiConverterPrimitive { typealias FfiType = Int8 typealias SwiftType = Int8 - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int8 { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int8 { return try lift(readInt(&buf)) } - static func write(_ value: Int8, into buf: inout [UInt8]) { + public static func write(_ value: Int8, into buf: inout [UInt8]) { writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift index f62f0c8244..05713aca26 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift @@ -1,5 +1,5 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { - fileprivate static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { let len = Int32(value.count) writeInt(&buf, len) for (key, value) in value { @@ -8,7 +8,7 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { } } - fileprivate static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { let len: Int32 = try readInt(&buf) var dict = {{ type_name }}() dict.reserveCapacity(Int(len)) diff --git a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index 2811d9ca0f..ab0b22b1d8 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -57,11 +57,11 @@ public class {{ type_name }}: {{ obj.name() }}Protocol { } -fileprivate struct {{ ffi_converter_name }}: FfiConverter { +public struct {{ ffi_converter_name }}: FfiConverter { typealias FfiType = UnsafeMutableRawPointer typealias SwiftType = {{ type_name }} - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { let v: UInt64 = try readInt(&buf) // The Rust code won't compile if a pointer won't fit in a UInt64. // We have to go via `UInt` because that's the thing that's the size of a pointer. @@ -72,17 +72,17 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverter { return try lift(ptr!) } - static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { // This fiddling is because `Int` is the thing that's the same size as a pointer. // The Rust code won't compile if a pointer won't fit in a `UInt64`. writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) } - static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { return {{ type_name}}(unsafeFromRawPointer: pointer) } - static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { + public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { return value.pointer } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift index 92acad8d0a..1dac65be63 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift @@ -1,7 +1,7 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { typealias SwiftType = {{ type_name }} - static func write(_ value: SwiftType, into buf: inout [UInt8]) { + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { guard let value = value else { writeInt(&buf, Int8(0)) return @@ -10,7 +10,7 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { {{ inner_type|write_fn }}(value, into: &buf) } - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { switch try readInt(&buf) as Int8 { case 0: return nil case 1: return try {{ inner_type|read_fn }}(from: &buf) diff --git a/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift index f8b664bd0c..261e049ad7 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift @@ -32,8 +32,8 @@ extension {{ type_name }}: Equatable, Hashable { } {% endif %} -fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { - fileprivate static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { +public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { return try {{ type_name }}( {%- for field in rec.fields() %} {{ field.name()|var_name }}: {{ field|read_fn }}(from: &buf) @@ -42,7 +42,7 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { ) } - fileprivate static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { {%- for field in rec.fields() %} {{ field|write_fn }}(value.{{ field.name()|var_name }}, into: &buf) {%- endfor %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift index 1c14dce9cc..b8927543cb 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift @@ -151,11 +151,11 @@ fileprivate protocol FfiConverter { fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { } extension FfiConverterPrimitive { - static func lift(_ value: FfiType) throws -> SwiftType { + public static func lift(_ value: FfiType) throws -> SwiftType { return value } - static func lower(_ value: SwiftType) -> FfiType { + public static func lower(_ value: SwiftType) -> FfiType { return value } } @@ -165,7 +165,7 @@ extension FfiConverterPrimitive { fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {} extension FfiConverterRustBuffer { - static func lift(_ buf: RustBuffer) throws -> SwiftType { + public static func lift(_ buf: RustBuffer) throws -> SwiftType { var reader = createReader(data: Data(rustBuffer: buf)) let value = try read(from: &reader) if hasRemaining(reader) { @@ -175,7 +175,7 @@ extension FfiConverterRustBuffer { return value } - static func lower(_ value: SwiftType) -> RustBuffer { + public static func lower(_ value: SwiftType) -> RustBuffer { var writer = createWriter() write(value, into: &writer) return RustBuffer(bytes: writer) diff --git a/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift index cdf85f83c4..bf664f6411 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift @@ -1,7 +1,7 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { typealias SwiftType = {{ type_name }} - static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { let len = Int32(value.count) writeInt(&buf, len) for item in value { @@ -9,7 +9,7 @@ fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { } } - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { let len: Int32 = try readInt(&buf) var seq = {{ type_name }}() seq.reserveCapacity(Int(len)) diff --git a/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift b/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift index dfb9ece6b7..b7d3466bdd 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift @@ -2,7 +2,7 @@ fileprivate struct FfiConverterString: FfiConverter { typealias SwiftType = String typealias FfiType = RustBuffer - static func lift(_ value: RustBuffer) throws -> String { + public static func lift(_ value: RustBuffer) throws -> String { defer { value.deallocate() } @@ -13,7 +13,7 @@ fileprivate struct FfiConverterString: FfiConverter { return String(bytes: bytes, encoding: String.Encoding.utf8)! } - static func lower(_ value: String) -> RustBuffer { + public static func lower(_ value: String) -> RustBuffer { return value.utf8CString.withUnsafeBufferPointer { ptr in // The swift string gives us int8_t, we want uint8_t. ptr.withMemoryRebound(to: UInt8.self) { ptr in @@ -24,12 +24,12 @@ fileprivate struct FfiConverterString: FfiConverter { } } - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { let len: Int32 = try readInt(&buf) return String(bytes: try readBytes(&buf, count: Int(len)), encoding: String.Encoding.utf8)! } - static func write(_ value: String, into buf: inout [UInt8]) { + public static func write(_ value: String, into buf: inout [UInt8]) { let len = Int32(value.utf8.count) writeInt(&buf, len) writeBytes(&buf, value.utf8) diff --git a/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift b/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift index 075a4b8139..3cd472fa0e 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift @@ -1,7 +1,7 @@ fileprivate struct FfiConverterTimestamp: FfiConverterRustBuffer { typealias SwiftType = Date - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Date { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Date { let seconds: Int64 = try readInt(&buf) let nanoseconds: UInt32 = try readInt(&buf) if seconds >= 0 { @@ -13,7 +13,7 @@ fileprivate struct FfiConverterTimestamp: FfiConverterRustBuffer { } } - static func write(_ value: Date, into buf: inout [UInt8]) { + public static func write(_ value: Date, into buf: inout [UInt8]) { var delta = value.timeIntervalSince1970 var sign: Int64 = 1 if delta < 0 { diff --git a/uniffi_bindgen/src/bindings/swift/templates/Types.swift b/uniffi_bindgen/src/bindings/swift/templates/Types.swift index 0940413b2c..36d98c2e91 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Types.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Types.swift @@ -85,7 +85,6 @@ {%- when Type::Map(key_type, value_type) %} {%- include "MapTemplate.swift" %} - {%- else %} {%- endmatch %} {%- endfor %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift index e9c811fc19..b7fc0942a5 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterUInt16: FfiConverterPrimitive { typealias FfiType = UInt16 typealias SwiftType = UInt16 - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt16 { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt16 { return try lift(readInt(&buf)) } - static func write(_ value: SwiftType, into buf: inout [UInt8]) { + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift index 073595f4c2..e7a64aab93 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterUInt32: FfiConverterPrimitive { typealias FfiType = UInt32 typealias SwiftType = UInt32 - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 { return try lift(readInt(&buf)) } - static func write(_ value: SwiftType, into buf: inout [UInt8]) { + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift index c0e2490abe..eb674a2c53 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterUInt64: FfiConverterPrimitive { typealias FfiType = UInt64 typealias SwiftType = UInt64 - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt64 { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt64 { return try lift(readInt(&buf)) } - static func write(_ value: SwiftType, into buf: inout [UInt8]) { + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift b/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift index f27d921b19..4baf613494 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift @@ -2,11 +2,11 @@ fileprivate struct FfiConverterUInt8: FfiConverterPrimitive { typealias FfiType = UInt8 typealias SwiftType = UInt8 - static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 { return try lift(readInt(&buf)) } - static func write(_ value: UInt8, into buf: inout [UInt8]) { + public static func write(_ value: UInt8, into buf: inout [UInt8]) { writeInt(&buf, lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/swift/test.rs b/uniffi_bindgen/src/bindings/swift/test.rs index a15200265c..e51c4a9c05 100644 --- a/uniffi_bindgen/src/bindings/swift/test.rs +++ b/uniffi_bindgen/src/bindings/swift/test.rs @@ -6,8 +6,11 @@ use anyhow::{bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use heck::ToSnakeCase; use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; +use std::ffi::OsStr; +use std::fs::{read_to_string, File}; +use std::io::Write; use std::process::Command; -use uniffi_testing::UniFFITestHelper; +use uniffi_testing::{CompileSource, UniFFITestHelper}; /// Run Swift tests for a UniFFI test fixture pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { @@ -19,40 +22,17 @@ pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result< test_helper .copy_cdylibs_to_out_dir(&out_dir) .context("copy_fixture_library_to_out_dir()")?; - let library_name = calc_library_name(&out_dir)?; let generated_sources = GeneratedSources::new(&test_helper.cdylib_path()?, &out_dir, &test_helper) .context("generate_sources()")?; - // Compile the generated sources together to create the swift module as a .so file - let bindings_mod = generated_sources.bindings_module_name()?; - let bindings_filename = format!("{}testmod_{}{}", DLL_PREFIX, bindings_mod, DLL_SUFFIX); - let mut command = Command::new("swiftc"); - command - .current_dir(&out_dir) - .arg("-emit-module") - .arg("-module-name") - .arg(&bindings_mod) - .arg("-o") - .arg(&bindings_filename) - .arg("-emit-library") - .arg("-Xcc") - .arg(format!( - "-fmodule-map-file={}", - generated_sources.module_map - )) - .arg(generated_sources.swift_module); - let status = command - .spawn() - .context("Failed to spawn `swiftc` when compiling bindings")? - .wait() - .context("Failed to wait for `swiftc` when compiling bindings")?; - if !status.success() { - bail!( - "running `swiftc` to compile bindings failed ({:?})", - command - ) - } + // Compile the generated sources together to create a single swift module + compile_swift_module( + &out_dir, + &calc_module_name(&generated_sources.main_source_filename), + &generated_sources.generated_swift_files, + &generated_sources.module_map, + )?; // Run the test script against compiled bindings @@ -63,8 +43,7 @@ pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result< .arg(&out_dir) .arg("-L") .arg(&out_dir) - .arg(format!("-l{}", &bindings_filename)) - .arg(format!("-l{}", &library_name)) + .args(calc_library_args(&out_dir)?) .arg("-Xcc") .arg(format!( "-fmodule-map-file={}", @@ -82,10 +61,53 @@ pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result< Ok(()) } +fn calc_module_name(filename: &str) -> String { + filename.strip_suffix(".swift").unwrap().to_snake_case() +} + +fn compile_swift_module>( + out_dir: &Utf8Path, + module_name: &str, + sources: impl IntoIterator, + module_map: &Utf8Path, +) -> Result<()> { + let output_filename = format!("{DLL_PREFIX}testmod_{module_name}{DLL_SUFFIX}"); + let mut command = Command::new("swiftc"); + command + .current_dir(out_dir) + .arg("-emit-module") + .arg("-module-name") + .arg(module_name) + .arg("-o") + .arg(output_filename) + .arg("-emit-library") + .arg("-Xcc") + .arg(format!("-fmodule-map-file={}", module_map)) + .arg("-I") + .arg(out_dir) + .arg("-L") + .arg(out_dir) + .args(calc_library_args(out_dir)?) + .args(sources); + let status = command + .spawn() + .context("Failed to spawn `swiftc` when compiling bindings")? + .wait() + .context("Failed to wait for `swiftc` when compiling bindings")?; + if !status.success() { + bail!( + "running `swiftc` to compile bindings failed ({:?})", + command + ) + }; + Ok(()) +} + // Stores sources generated by `uniffi-bindgen-swift` struct GeneratedSources { - swift_module: Utf8PathBuf, + generated_swift_files: Vec, module_map: Utf8PathBuf, + main_source_filename: String, } impl GeneratedSources { @@ -94,7 +116,30 @@ impl GeneratedSources { out_dir: &Utf8Path, test_helper: &UniFFITestHelper, ) -> Result { - for source in test_helper.get_compile_sources()? { + // Generate the bindings for the main compile source, and use that for the swift module name + let main_compile_source = test_helper.get_main_compile_source()?; + Self::run_generate_bindings(&main_compile_source, library_path, out_dir)?; + let generated_files = glob(&out_dir.join("*.swift"))?; + let main_source_filename = match generated_files.len() { + 0 => bail!( + "No .swift file generated for {}", + main_compile_source.udl_path + ), + 1 => generated_files + .into_iter() + .next() + .unwrap() + .file_name() + .unwrap() + .to_string(), + n => bail!( + "{n} .swift files generated for {}", + main_compile_source.udl_path + ), + }; + + // Generate the bindings for other compile sources (crates used by external types) + for source in test_helper.get_external_compile_sources()? { crate::generate_bindings( &source.udl_path, source.config_path.as_deref(), @@ -104,46 +149,77 @@ impl GeneratedSources { false, )?; } + + let generated_module_maps = glob(&out_dir.join("*.modulemap"))?; + Ok(GeneratedSources { - swift_module: glob1(out_dir.join("*.swift"))?, - module_map: glob1(out_dir.join("*.modulemap"))?, + main_source_filename, + generated_swift_files: glob(&out_dir.join("*.swift"))?, + module_map: match generated_module_maps.len() { + 0 => bail!("No modulemap files found in {out_dir}"), + // Normally we only generate 1 module map and can return it directly + 1 => generated_module_maps.into_iter().next().unwrap(), + // When we use multiple UDL files in a test, for example the ext-types fixture, + // then we get multiple module maps and need to combine them + _ => { + let path = out_dir.join("combined.modulemap"); + let mut f = File::create(&path)?; + write!( + f, + "{}", + generated_module_maps + .into_iter() + .map(|path| Ok(read_to_string(path)?)) + .collect::>>()? + .join("\n") + )?; + path + } + }, }) } - pub fn bindings_module_name(&self) -> Result { - Ok(self - .swift_module - .file_name() - .unwrap() - .strip_suffix(".swift") - .context("bindings_module_name()")? - .to_snake_case()) + fn run_generate_bindings( + source: &CompileSource, + library_path: &Utf8Path, + out_dir: &Utf8Path, + ) -> Result<()> { + crate::generate_bindings( + &source.udl_path, + source.config_path.as_deref(), + vec!["swift"], + Some(out_dir), + Some(library_path), + false, + ) } } -// Get a single path from a globspec -fn glob1(globspec: Utf8PathBuf) -> Result { - let mut paths = glob::glob(globspec.as_str())?; - let first = paths - .next() - .unwrap_or_else(|| panic!("globspec {:?} returned 0 results", &globspec)); - if paths.next().is_some() { - bail!(format!( - "globspec {:?} returned multiple results", - &globspec - )); - } - Ok(Utf8PathBuf::try_from(first?.canonicalize()?)?) +// Wraps glob to use Utf8Paths and flattens errors +fn glob(globspec: &Utf8Path) -> Result> { + glob::glob(globspec.as_str())? + .map(|globresult| Ok(Utf8PathBuf::try_from(globresult?)?)) + .collect() } -fn calc_library_name(out_dir: &Utf8Path) -> Result { - let library_path = glob1(out_dir.join(format!("{}*{}", DLL_PREFIX, DLL_SUFFIX)))?; - Ok(library_path - .file_name() - .unwrap() - .strip_prefix(DLL_PREFIX) - .unwrap() - .strip_suffix(DLL_SUFFIX) - .unwrap() - .to_string()) +fn calc_library_args(out_dir: &Utf8Path) -> Result> { + let results = glob::glob( + out_dir + .join(format!("{}*{}", DLL_PREFIX, DLL_SUFFIX)) + .as_str(), + )?; + results + .map(|globresult| { + let path = Utf8PathBuf::try_from(globresult.unwrap())?; + Ok(format!( + "-l{}", + path.file_name() + .unwrap() + .strip_prefix(DLL_PREFIX) + .unwrap() + .strip_suffix(DLL_SUFFIX) + .unwrap() + )) + }) + .collect() } diff --git a/uniffi_macros/src/util.rs b/uniffi_macros/src/util.rs index 21415e8d31..242e63e8bf 100644 --- a/uniffi_macros/src/util.rs +++ b/uniffi_macros/src/util.rs @@ -59,7 +59,6 @@ pub fn mod_path() -> syn::Result> { #[cfg(feature = "nightly")] pub fn mod_path() -> syn::Result> { use proc_macro::TokenStream; - use quote::quote; let module_path_invoc = TokenStream::from(quote! { ::core::module_path!() }); // We ask the compiler what `module_path!()` expands to here. diff --git a/uniffi_testing/src/lib.rs b/uniffi_testing/src/lib.rs index 182d044b4a..cc08895214 100644 --- a/uniffi_testing/src/lib.rs +++ b/uniffi_testing/src/lib.rs @@ -16,8 +16,11 @@ use std::{ process::{Command, Stdio}, }; -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] struct UniFFITestingMetadata { + /// Crates that hold external types used by this crate. When running the tests, we will build + /// the libraries and generate the source files for those crates and put them in the test + /// directory #[serde(rename = "external-crates")] external_crates: Option>, } @@ -188,8 +191,18 @@ impl UniFFITestHelper { /// Get paths to the UDL and config files for a fixture pub fn get_compile_sources(&self) -> Result> { - std::iter::once(self.package.clone()) - .chain(self.find_packages_for_external_crates()?) + Ok(std::iter::once(self.get_main_compile_source()?) + .chain(self.get_external_compile_sources()?) + .collect()) + } + + pub fn get_main_compile_source(&self) -> Result { + self.find_compile_source(&self.package.clone()) + } + + pub fn get_external_compile_sources(&self) -> Result> { + self.find_packages_for_external_crates()? + .into_iter() .map(|p| self.find_compile_source(&p)) .collect() } From a58a21779404532a9e72e3cc2f3831f8780b6c58 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Tue, 15 Nov 2022 12:18:54 -0500 Subject: [PATCH 4/4] Added support for separate Swift modules with external types Added support for build processes where each generated `.swift` file is compiled into its own module rather than compiling all files together. This means that we need imports to make external types work. - Added test fixture and updated the testing code to test this - Added config code to allow the user to choose their mode - Updated the docs to describe the config options --- Cargo.toml | 1 + docs/manual/src/udl/ext_types_external.md | 26 +++++++-- .../Cargo.toml | 41 ++++++++++++++ .../lib-with-separate-swift-modules/README.md | 3 ++ .../lib-with-separate-swift-modules/build.rs | 7 +++ .../src/ext-types-lib.udl | 40 ++++++++++++++ .../src/lib.rs | 53 +++++++++++++++++++ .../tests/bindings/test_imported_types.swift | 14 +++++ .../tests/test_generated_bindings.rs | 5 ++ .../uniffi.toml | 2 + .../src/bindings/swift/gen_swift/mod.rs | 37 ++++++++++++- .../swift/templates/ExternalType.swift | 4 ++ .../src/bindings/swift/templates/Types.swift | 3 ++ uniffi_bindgen/src/bindings/swift/test.rs | 40 +++++++++++--- uniffi_testing/src/lib.rs | 11 ++++ 15 files changed, 276 insertions(+), 11 deletions(-) create mode 100644 fixtures/ext-types/lib-with-separate-swift-modules/Cargo.toml create mode 100644 fixtures/ext-types/lib-with-separate-swift-modules/README.md create mode 100644 fixtures/ext-types/lib-with-separate-swift-modules/build.rs create mode 100644 fixtures/ext-types/lib-with-separate-swift-modules/src/ext-types-lib.udl create mode 100644 fixtures/ext-types/lib-with-separate-swift-modules/src/lib.rs create mode 100644 fixtures/ext-types/lib-with-separate-swift-modules/tests/bindings/test_imported_types.swift create mode 100644 fixtures/ext-types/lib-with-separate-swift-modules/tests/test_generated_bindings.rs create mode 100644 fixtures/ext-types/lib-with-separate-swift-modules/uniffi.toml create mode 100644 uniffi_bindgen/src/bindings/swift/templates/ExternalType.swift diff --git a/Cargo.toml b/Cargo.toml index 2214304a3b..0756f05956 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "fixtures/ext-types/guid", "fixtures/ext-types/uniffi-one", "fixtures/ext-types/lib", + "fixtures/ext-types/lib-with-separate-swift-modules/", # we should roll the above and below up somehow that makes sense... "fixtures/external-types/crate-one", diff --git a/docs/manual/src/udl/ext_types_external.md b/docs/manual/src/udl/ext_types_external.md index 73b325427f..e97bf6268b 100644 --- a/docs/manual/src/udl/ext_types_external.md +++ b/docs/manual/src/udl/ext_types_external.md @@ -70,6 +70,26 @@ rust-crate-name = "kotlin.package.name" ### Swift -For Swift, you must compile all generated `.swift` files together in a single -module since the generate code expects that it can access external types -without importing them. +For Swift, there are 2 ways to use the UniFFI-generated Swift code: + - Compile all source files together in the same module. + - Compile each source file into a different module. + +By default, UniFFI assumes that the source files will be compiled together, so +external types will be automatically available and nothing needs to be configured. + +If you compile each source file into a different module, then you need to +add configuration to `uniffi.toml`. + +``` +[bindings.swift.external_types] +in_different_modules = true +``` + +By default, UniFFI assumes the module name is the same as Rust crate name +specified in the UDL file (with hyphens replaced with underscores). You can +override this with the `bindings.swift.external_types.module_names` config key: + +``` +[bindings.swift.external_types.module_names] +my-rust-crate-name = "my_swift_module_name" +``` diff --git a/fixtures/ext-types/lib-with-separate-swift-modules/Cargo.toml b/fixtures/ext-types/lib-with-separate-swift-modules/Cargo.toml new file mode 100644 index 0000000000..18b847d66b --- /dev/null +++ b/fixtures/ext-types/lib-with-separate-swift-modules/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "uniffi-fixture-ext-types-swift-separate-modules" +edition = "2021" +version = "0.21.0" +authors = ["Firefox Sync Team "] +license = "MPL-2.0" +publish = false + +[package.metadata.uniffi.testing] +external-crates = [ + "uniffi-fixture-ext-types-guid", + "uniffi-fixture-ext-types-lib-one", + "uniffi-example-custom-types", +] +swift-use-separate-modules = true + +[lib] +crate-type = ["lib", "cdylib"] +name = "uniffi_ext_types_lib" + +[dependencies] +anyhow = "1" +bytes = "1.0" +uniffi = {path = "../../../uniffi", features=["builtin-bindgen"]} + +uniffi-fixture-ext-types-lib-one = {path = "../uniffi-one"} +uniffi-fixture-ext-types-guid = {path = "../guid"} + +# Reuse one of our examples. +uniffi-example-custom-types = {path = "../../../examples/custom-types"} + +url = "2.2" + +[build-dependencies] +uniffi_build = {path = "../../../uniffi_build", features=["builtin-bindgen"]} + + + +[dev-dependencies] +uniffi_bindgen = {path = "../../../uniffi_bindgen"} +uniffi_macros = {path = "../../../uniffi_macros"} diff --git a/fixtures/ext-types/lib-with-separate-swift-modules/README.md b/fixtures/ext-types/lib-with-separate-swift-modules/README.md new file mode 100644 index 0000000000..ff344d8129 --- /dev/null +++ b/fixtures/ext-types/lib-with-separate-swift-modules/README.md @@ -0,0 +1,3 @@ +This is basically the same as the uniffi-fixture-ext-types fixture found in +`../lib`. The only difference is that this crate configures the swift tests to +compile each source file into a separate module so that we can test that mode. diff --git a/fixtures/ext-types/lib-with-separate-swift-modules/build.rs b/fixtures/ext-types/lib-with-separate-swift-modules/build.rs new file mode 100644 index 0000000000..c22cd35025 --- /dev/null +++ b/fixtures/ext-types/lib-with-separate-swift-modules/build.rs @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +fn main() { + uniffi_build::generate_scaffolding("./src/ext-types-lib.udl").unwrap(); +} diff --git a/fixtures/ext-types/lib-with-separate-swift-modules/src/ext-types-lib.udl b/fixtures/ext-types/lib-with-separate-swift-modules/src/ext-types-lib.udl new file mode 100644 index 0000000000..c4635cd6e9 --- /dev/null +++ b/fixtures/ext-types/lib-with-separate-swift-modules/src/ext-types-lib.udl @@ -0,0 +1,40 @@ +namespace imported_types_lib { + CombinedType get_combined_type(optional CombinedType? value); +}; + +// A type defined in a .udl file in the `uniffi-one` crate (ie, in +// `../../uniffi-one/src/uniffi-one.udl`) +[External="uniffi-one"] +typedef extern UniffiOneType; + +// A "wrapped" type defined in the guid crate (ie, defined in `../../guid/src/lib.rs` and +// "declared" in `../../guid/src/guid.udl`). But it's still "external" from our POV, +// So same as the `.udl` type above! +[External="ext-types-guid"] +typedef extern Guid; + +// And re-use the `custom-types` example - this exposes `Url` and `Handle` +[External="custom-types"] +typedef extern Url; + +[External="custom-types"] +typedef extern Handle; + +// And a new type here to tie them all together. +dictionary CombinedType { + UniffiOneType uot; + sequence uots; + UniffiOneType? maybe_uot; + + Guid guid; + sequence guids; + Guid? maybe_guid; + + Url url; + sequence urls; + Url? maybe_url; + + Handle handle; + sequence handles; + Handle? maybe_handle; +}; diff --git a/fixtures/ext-types/lib-with-separate-swift-modules/src/lib.rs b/fixtures/ext-types/lib-with-separate-swift-modules/src/lib.rs new file mode 100644 index 0000000000..87b9217dfc --- /dev/null +++ b/fixtures/ext-types/lib-with-separate-swift-modules/src/lib.rs @@ -0,0 +1,53 @@ +use custom_types::Handle; +use ext_types_guid::Guid; +use uniffi_one::UniffiOneType; +use url::Url; + +pub struct CombinedType { + pub uot: UniffiOneType, + pub uots: Vec, + pub maybe_uot: Option, + + pub guid: Guid, + pub guids: Vec, + pub maybe_guid: Option, + + pub url: Url, + pub urls: Vec, + pub maybe_url: Option, + + pub handle: Handle, + pub handles: Vec, + pub maybe_handle: Option, +} + +fn get_combined_type(existing: Option) -> CombinedType { + existing.unwrap_or_else(|| CombinedType { + uot: UniffiOneType { + sval: "hello".to_string(), + }, + uots: vec![ + UniffiOneType { + sval: "first of many".to_string(), + }, + UniffiOneType { + sval: "second of many".to_string(), + }, + ], + maybe_uot: None, + + guid: Guid("a-guid".into()), + guids: vec![Guid("b-guid".into()), Guid("c-guid".into())], + maybe_guid: None, + + url: Url::parse("http://example.com/").unwrap(), + urls: vec![], + maybe_url: None, + + handle: Handle(123), + handles: vec![Handle(1), Handle(2), Handle(3)], + maybe_handle: Some(Handle(4)), + }) +} + +include!(concat!(env!("OUT_DIR"), "/ext-types-lib.uniffi.rs")); diff --git a/fixtures/ext-types/lib-with-separate-swift-modules/tests/bindings/test_imported_types.swift b/fixtures/ext-types/lib-with-separate-swift-modules/tests/bindings/test_imported_types.swift new file mode 100644 index 0000000000..bdb326529d --- /dev/null +++ b/fixtures/ext-types/lib-with-separate-swift-modules/tests/bindings/test_imported_types.swift @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import imported_types_lib +import Foundation + +let ct = getCombinedType(value: nil) +assert(ct.uot.sval == "hello") +assert(ct.guid == "a-guid") +assert(ct.url == URL(string: "http://example.com/")) + +let ct2 = getCombinedType(value: ct) +assert(ct == ct2) diff --git a/fixtures/ext-types/lib-with-separate-swift-modules/tests/test_generated_bindings.rs b/fixtures/ext-types/lib-with-separate-swift-modules/tests/test_generated_bindings.rs new file mode 100644 index 0000000000..620e54159e --- /dev/null +++ b/fixtures/ext-types/lib-with-separate-swift-modules/tests/test_generated_bindings.rs @@ -0,0 +1,5 @@ +uniffi_macros::build_foreign_language_testcases!( + "tests/bindings/test_imported_types.swift", + // No need to test other languages, since this crate only changes the Swift configuration + // (see ../README.md) +); diff --git a/fixtures/ext-types/lib-with-separate-swift-modules/uniffi.toml b/fixtures/ext-types/lib-with-separate-swift-modules/uniffi.toml new file mode 100644 index 0000000000..585c175763 --- /dev/null +++ b/fixtures/ext-types/lib-with-separate-swift-modules/uniffi.toml @@ -0,0 +1,2 @@ +[bindings.swift.external_types] +in_different_modules = true diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index d309187376..a8da188af9 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -8,7 +8,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use anyhow::{Context, Result}; use askama::Template; -use heck::{ToLowerCamelCase, ToUpperCamelCase}; +use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use super::Bindings; @@ -41,6 +41,8 @@ pub struct Config { omit_argument_labels: Option, #[serde(default)] custom_types: HashMap, + #[serde(default)] + external_types: ExternalTypesConfig, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -51,6 +53,14 @@ pub struct CustomTypeConfig { from_custom: TemplateExpression, } +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ExternalTypesConfig { + #[serde(default)] + in_different_modules: bool, + #[serde(default)] + module_names: HashMap, +} + impl Config { /// The name of the Swift module containing the high-level foreign-language bindings. pub fn module_name(&self) -> String { @@ -132,6 +142,16 @@ impl MergeWith for Config { .omit_argument_labels .merge_with(&other.omit_argument_labels), custom_types: self.custom_types.merge_with(&other.custom_types), + external_types: self.external_types.merge_with(&other.external_types), + } + } +} + +impl MergeWith for ExternalTypesConfig { + fn merge_with(&self, other: &Self) -> Self { + ExternalTypesConfig { + in_different_modules: self.in_different_modules || other.in_different_modules, + module_names: self.module_names.merge_with(&other.module_names), } } } @@ -208,6 +228,21 @@ impl<'a> TypeRenderer<'a> { self.imports.borrow_mut().insert(name.to_owned()); "" } + + /// Get the Swift module name for an external type + fn external_types_in_different_modules(&self) -> bool { + self.config.external_types.in_different_modules + } + + /// Get the Swift module name for an external type + fn external_type_module_name(&self, name: &str) -> String { + self.config + .external_types + .module_names + .get(name) + .cloned() + .unwrap_or_else(|| name.to_snake_case()) + } } /// Template for generating the `.h` file that defines the low-level C FFI. diff --git a/uniffi_bindgen/src/bindings/swift/templates/ExternalType.swift b/uniffi_bindgen/src/bindings/swift/templates/ExternalType.swift new file mode 100644 index 0000000000..ea90cc8c40 --- /dev/null +++ b/uniffi_bindgen/src/bindings/swift/templates/ExternalType.swift @@ -0,0 +1,4 @@ +{%- if self.external_types_in_different_modules() %} +{# This import will bring in both the type itself and the FfiConverter for it #} +{{ self.add_import(self.external_type_module_name(crate_name).borrow()) }} +{%- endif %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/Types.swift b/uniffi_bindgen/src/bindings/swift/templates/Types.swift index 36d98c2e91..c6dcad2de6 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Types.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Types.swift @@ -64,6 +64,9 @@ {%- when Type::Custom { name, builtin } %} {%- include "CustomType.swift" %} +{%- when Type::External { crate_name, name } %} +{%- include "ExternalType.swift" %} + {%- when Type::Enum(name) %} {%- include "EnumTemplate.swift" %} diff --git a/uniffi_bindgen/src/bindings/swift/test.rs b/uniffi_bindgen/src/bindings/swift/test.rs index e51c4a9c05..ca07643f9c 100644 --- a/uniffi_bindgen/src/bindings/swift/test.rs +++ b/uniffi_bindgen/src/bindings/swift/test.rs @@ -26,13 +26,32 @@ pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result< GeneratedSources::new(&test_helper.cdylib_path()?, &out_dir, &test_helper) .context("generate_sources()")?; - // Compile the generated sources together to create a single swift module - compile_swift_module( - &out_dir, - &calc_module_name(&generated_sources.main_source_filename), - &generated_sources.generated_swift_files, - &generated_sources.module_map, - )?; + if test_helper.swift_use_separate_modules() { + // Compile each external sources into a separate swift module + for swift_file in generated_sources.external_swift_files() { + let filename = swift_file.file_name().unwrap(); + compile_swift_module( + &out_dir, + &calc_module_name(filename), + [swift_file], + &generated_sources.module_map, + )? + } + compile_swift_module( + &out_dir, + &calc_module_name(&generated_sources.main_source_filename), + &[generated_sources.main_source_filename], + &generated_sources.module_map, + )?; + } else { + // Compile the generated sources together to create a single swift module + compile_swift_module( + &out_dir, + &calc_module_name(&generated_sources.main_source_filename), + &generated_sources.generated_swift_files, + &generated_sources.module_map, + )?; + } // Run the test script against compiled bindings @@ -193,6 +212,13 @@ impl GeneratedSources { false, ) } + + fn external_swift_files(&self) -> impl Iterator { + self.generated_swift_files + .iter() + .filter(|path| path.file_name().unwrap() != self.main_source_filename) + .map(std::ops::Deref::deref) + } } // Wraps glob to use Utf8Paths and flattens errors diff --git a/uniffi_testing/src/lib.rs b/uniffi_testing/src/lib.rs index cc08895214..412f2ce1aa 100644 --- a/uniffi_testing/src/lib.rs +++ b/uniffi_testing/src/lib.rs @@ -23,6 +23,10 @@ struct UniFFITestingMetadata { /// directory #[serde(rename = "external-crates")] external_crates: Option>, + /// Should generated files for Swift external crates be compiled as separate modules? The + /// default is to compile them all together into 1 module. + #[serde(default, rename = "swift-use-separate-modules")] + swift_use_separate_modules: bool, } // A source to compile for a test @@ -133,6 +137,13 @@ impl UniFFITestHelper { } } + pub fn swift_use_separate_modules(&self) -> bool { + match &self.metadata { + Some(metadata) => metadata.swift_use_separate_modules, + None => false, + } + } + /// Create at `out_dir` for testing /// /// This directory can be used for: