summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/uniffi
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi-example-arithmetic/Cargo.toml39
-rw-r--r--third_party/rust/uniffi-example-arithmetic/build.rs7
-rw-r--r--third_party/rust/uniffi-example-arithmetic/src/arithmetic.udl16
-rw-r--r--third_party/rust/uniffi-example-arithmetic/src/lib.rs34
-rw-r--r--third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.kts29
-rw-r--r--third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.py37
-rw-r--r--third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.rb31
-rw-r--r--third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.swift32
-rw-r--r--third_party/rust/uniffi-example-arithmetic/tests/test_generated_bindings.rs6
-rw-r--r--third_party/rust/uniffi-example-arithmetic/uniffi.toml12
-rw-r--r--third_party/rust/uniffi-example-geometry/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi-example-geometry/Cargo.toml36
-rw-r--r--third_party/rust/uniffi-example-geometry/build.rs7
-rw-r--r--third_party/rust/uniffi-example-geometry/src/geometry.udl15
-rw-r--r--third_party/rust/uniffi-example-geometry/src/lib.rs47
-rw-r--r--third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.kts10
-rw-r--r--third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py10
-rw-r--r--third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb16
-rw-r--r--third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.swift10
-rw-r--r--third_party/rust/uniffi-example-geometry/tests/test_generated_bindings.rs6
-rw-r--r--third_party/rust/uniffi-example-geometry/uniffi.toml1
-rw-r--r--third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi-example-rondpoint/Cargo.toml36
-rw-r--r--third_party/rust/uniffi-example-rondpoint/build.rs7
-rw-r--r--third_party/rust/uniffi-example-rondpoint/src/lib.rs293
-rw-r--r--third_party/rust/uniffi-example-rondpoint/src/rondpoint.udl146
-rw-r--r--third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.kts250
-rw-r--r--third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py146
-rw-r--r--third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb142
-rw-r--r--third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.swift232
-rw-r--r--third_party/rust/uniffi-example-rondpoint/tests/test_generated_bindings.rs6
-rw-r--r--third_party/rust/uniffi-example-sprites/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi-example-sprites/Cargo.toml36
-rw-r--r--third_party/rust/uniffi-example-sprites/build.rs7
-rw-r--r--third_party/rust/uniffi-example-sprites/src/lib.rs65
-rw-r--r--third_party/rust/uniffi-example-sprites/src/sprites.udl22
-rw-r--r--third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.kts25
-rw-r--r--third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py17
-rw-r--r--third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb22
-rw-r--r--third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.swift16
-rw-r--r--third_party/rust/uniffi-example-sprites/tests/test_generated_bindings.rs6
-rw-r--r--third_party/rust/uniffi-example-todolist/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi-example-todolist/Cargo.toml40
-rw-r--r--third_party/rust/uniffi-example-todolist/build.rs7
-rw-r--r--third_party/rust/uniffi-example-todolist/src/lib.rs150
-rw-r--r--third_party/rust/uniffi-example-todolist/src/todolist.udl38
-rw-r--r--third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.kts83
-rw-r--r--third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py44
-rw-r--r--third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb47
-rw-r--r--third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.swift69
-rw-r--r--third_party/rust/uniffi-example-todolist/tests/test_generated_bindings.rs6
-rw-r--r--third_party/rust/uniffi/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi/Cargo.toml68
-rw-r--r--third_party/rust/uniffi/release.toml14
-rw-r--r--third_party/rust/uniffi/src/cli.rs106
-rw-r--r--third_party/rust/uniffi/src/lib.rs37
-rw-r--r--third_party/rust/uniffi/tests/ui/proc_macro_arc.rs25
-rw-r--r--third_party/rust/uniffi/tests/ui/proc_macro_arc.stderr31
-rw-r--r--third_party/rust/uniffi/tests/ui/version_mismatch.rs4
-rw-r--r--third_party/rust/uniffi/tests/ui/version_mismatch.stderr7
-rw-r--r--third_party/rust/uniffi_bindgen/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi_bindgen/Cargo.toml75
-rw-r--r--third_party/rust/uniffi_bindgen/askama.toml21
-rw-r--r--third_party/rust/uniffi_bindgen/src/backend/config.rs17
-rw-r--r--third_party/rust/uniffi_bindgen/src/backend/declarations.rs31
-rw-r--r--third_party/rust/uniffi_bindgen/src/backend/mod.rs16
-rw-r--r--third_party/rust/uniffi_bindgen/src/backend/oracle.rs35
-rw-r--r--third_party/rust/uniffi_bindgen/src/backend/types.rs218
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs94
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs37
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs32
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs417
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs79
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs43
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt74
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt130
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt62
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt36
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt107
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt109
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt9
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt71
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt66
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt35
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt161
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt100
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt41
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt66
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt23
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt45
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt38
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt17
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt94
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt82
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt48
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs107
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/mod.rs121
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs122
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs41
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs36
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs448
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs69
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py16
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py73
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py104
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py52
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py93
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py75
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py7
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py67
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py39
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py74
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py21
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py45
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py23
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py190
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py30
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py13
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py93
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py101
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py71
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/test.rs62
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs273
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs47
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs50
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb59
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb117
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb17
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb73
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb20
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb246
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb301
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb218
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb16
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb73
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb47
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs62
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs98
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.rs25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs35
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs476
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs87
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs99
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift20
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h55
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift60
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift154
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift86
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift24
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift60
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift83
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift84
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift22
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift88
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift20
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift62
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift183
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift21
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift37
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift34
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift15
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift90
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift97
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs225
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/attributes.rs759
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/callbacks.rs169
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/enum_.rs441
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/error.rs230
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/ffi.rs102
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/function.rs288
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/literal.rs186
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/mod.rs1218
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/namespace.rs132
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/object.rs599
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/record.rs243
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/types/finder.rs244
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/types/mod.rs342
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/types/resolver.rs367
-rw-r--r--third_party/rust/uniffi_bindgen/src/lib.rs483
-rw-r--r--third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs100
-rw-r--r--third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs185
-rw-r--r--third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs19
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs152
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs197
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs45
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs95
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs48
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs73
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/ReexportUniFFIScaffolding.rs27
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/RustBuffer.rs27
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs17
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/macros.rs118
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs58
-rw-r--r--third_party/rust/uniffi_build/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi_build/Cargo.toml39
-rw-r--r--third_party/rust/uniffi_build/src/lib.rs31
-rw-r--r--third_party/rust/uniffi_checksum_derive/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi_checksum_derive/Cargo.toml42
-rw-r--r--third_party/rust/uniffi_checksum_derive/src/lib.rs134
-rw-r--r--third_party/rust/uniffi_core/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi_core/Cargo.toml52
-rw-r--r--third_party/rust/uniffi_core/release.toml16
-rw-r--r--third_party/rust/uniffi_core/src/ffi/ffidefault.rs52
-rw-r--r--third_party/rust/uniffi_core/src/ffi/foreignbytes.rs118
-rw-r--r--third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs229
-rw-r--r--third_party/rust/uniffi_core/src/ffi/mod.rs15
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustbuffer.rs353
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustcalls.rs279
-rw-r--r--third_party/rust/uniffi_core/src/lib.rs692
-rw-r--r--third_party/rust/uniffi_core/src/panichook.rs34
-rw-r--r--third_party/rust/uniffi_macros/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi_macros/Cargo.toml69
-rw-r--r--third_party/rust/uniffi_macros/src/enum_.rs160
-rw-r--r--third_party/rust/uniffi_macros/src/error.rs170
-rw-r--r--third_party/rust/uniffi_macros/src/export.rs210
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata.rs26
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata/convert.rs222
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata/function.rs32
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata/impl_.rs93
-rw-r--r--third_party/rust/uniffi_macros/src/export/scaffolding.rs199
-rw-r--r--third_party/rust/uniffi_macros/src/lib.rs179
-rw-r--r--third_party/rust/uniffi_macros/src/object.rs35
-rw-r--r--third_party/rust/uniffi_macros/src/record.rs108
-rw-r--r--third_party/rust/uniffi_macros/src/test.rs90
-rw-r--r--third_party/rust/uniffi_macros/src/util.rs223
-rw-r--r--third_party/rust/uniffi_meta/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi_meta/Cargo.toml33
-rw-r--r--third_party/rust/uniffi_meta/src/lib.rs280
-rw-r--r--third_party/rust/uniffi_testing/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi_testing/Cargo.toml43
-rw-r--r--third_party/rust/uniffi_testing/README.md18
-rw-r--r--third_party/rust/uniffi_testing/src/lib.rs275
292 files changed, 24075 insertions, 0 deletions
diff --git a/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json b/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json
new file mode 100644
index 0000000000..65c9cc95fa
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"33bc6eceebec9d3aa6e97e1481ba74f2e1949d0b9a5edd8b978be548713ee338","build.rs":"937e2a5fdb7c0e850af07a4d7676e57f62f06b842cbc0aa7291ffe58b5725caa","src/arithmetic.udl":"8554c6907ece627645f6b896f71430e5412bf19b0ac6becf63eb9a69868d0f7a","src/lib.rs":"c454193443e92d49f997c760f4131192fb66bf213bbac1710c1ebde19e765e5d","tests/bindings/test_arithmetic.kts":"e0e9347755db4e18f70b1b74c2d5a4aa328373015090ed959b46d65c2a205d92","tests/bindings/test_arithmetic.py":"3e41d69e21e96a6830197c760f3b7bddd754edc0c5515b7bd33b79cccb10f941","tests/bindings/test_arithmetic.rb":"ea0fdce0a4c7b557b427db77521da05240cd6e87d60a128ac2307fab3bbbc76d","tests/bindings/test_arithmetic.swift":"455b87d95fc690af9c35f9e43676e9c855dedddd2fc1c9e1cbaa6a02835c2d4c","tests/test_generated_bindings.rs":"26b92d6b3e648f6fadd4182cbdba4f412b73da48a789785fd98cd486b29abf05","uniffi.toml":"ad149df611a6e3a853a029d90a88a694660f6a4ebe18dcb5f9f503819761dacd"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-arithmetic/Cargo.toml b/third_party/rust/uniffi-example-arithmetic/Cargo.toml
new file mode 100644
index 0000000000..bac35f5d80
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/Cargo.toml
@@ -0,0 +1,39 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi-example-arithmetic"
+version = "0.22.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+publish = false
+license = "MPL-2.0"
+
+[lib]
+name = "arithmetical"
+crate-type = [
+ "lib",
+ "cdylib",
+]
+
+[dependencies]
+thiserror = "1.0"
+
+[dependencies.uniffi]
+path = "../../uniffi"
+
+[dev-dependencies.uniffi]
+path = "../../uniffi"
+features = ["bindgen-tests"]
+
+[build-dependencies.uniffi]
+path = "../../uniffi"
+features = ["build"]
diff --git a/third_party/rust/uniffi-example-arithmetic/build.rs b/third_party/rust/uniffi-example-arithmetic/build.rs
new file mode 100644
index 0000000000..fcf5fe5b5c
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/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::generate_scaffolding("./src/arithmetic.udl").unwrap();
+}
diff --git a/third_party/rust/uniffi-example-arithmetic/src/arithmetic.udl b/third_party/rust/uniffi-example-arithmetic/src/arithmetic.udl
new file mode 100644
index 0000000000..117df4834a
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/src/arithmetic.udl
@@ -0,0 +1,16 @@
+[Error]
+enum ArithmeticError {
+ "IntegerOverflow",
+};
+
+namespace arithmetic {
+ [Throws=ArithmeticError]
+ u64 add(u64 a, u64 b);
+
+ [Throws=ArithmeticError]
+ u64 sub(u64 a, u64 b);
+
+ u64 div(u64 dividend, u64 divisor);
+
+ boolean equal(u64 a, u64 b);
+};
diff --git a/third_party/rust/uniffi-example-arithmetic/src/lib.rs b/third_party/rust/uniffi-example-arithmetic/src/lib.rs
new file mode 100644
index 0000000000..92ab8c072b
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/src/lib.rs
@@ -0,0 +1,34 @@
+/* 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/. */
+
+#[derive(Debug, thiserror::Error)]
+pub enum ArithmeticError {
+ #[error("Integer overflow on an operation with {a} and {b}")]
+ IntegerOverflow { a: u64, b: u64 },
+}
+
+fn add(a: u64, b: u64) -> Result<u64> {
+ a.checked_add(b)
+ .ok_or(ArithmeticError::IntegerOverflow { a, b })
+}
+
+fn sub(a: u64, b: u64) -> Result<u64> {
+ a.checked_sub(b)
+ .ok_or(ArithmeticError::IntegerOverflow { a, b })
+}
+
+fn div(dividend: u64, divisor: u64) -> u64 {
+ if divisor == 0 {
+ panic!("Can't divide by zero");
+ }
+ dividend / divisor
+}
+
+fn equal(a: u64, b: u64) -> bool {
+ a == b
+}
+
+type Result<T, E = ArithmeticError> = std::result::Result<T, E>;
+
+uniffi::include_scaffolding!("arithmetic");
diff --git a/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.kts b/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.kts
new file mode 100644
index 0000000000..ef11850ae2
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.kts
@@ -0,0 +1,29 @@
+import org.mozilla.uniffi.example.arithmetic.*;
+
+assert(add(2u, 4u) == 6uL)
+assert(add(4u, 8u) == 12uL)
+
+try {
+ sub(0u, 2u)
+ throw RuntimeException("Should have thrown a IntegerOverflow exception!")
+} catch (e: ArithmeticException) {
+ // It's okay!
+}
+
+assert(sub(4u, 2u) == 2uL)
+assert(sub(8u, 4u) == 4uL)
+
+assert(div(8u, 4u) == 2uL)
+
+try {
+ div(8u, 0u)
+ throw RuntimeException("Should have panicked when dividing by zero")
+} catch (e: InternalException) {
+ // It's okay!
+}
+
+assert(equal(2u, 2uL))
+assert(equal(4u, 4uL))
+
+assert(!equal(2u, 4uL))
+assert(!equal(4u, 8uL))
diff --git a/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.py b/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.py
new file mode 100644
index 0000000000..0d4e666fbf
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.py
@@ -0,0 +1,37 @@
+from arithmetic import *
+
+try:
+ add(18446744073709551615, 1)
+ assert(not("Should have thrown a IntegerOverflow exception!"))
+except ArithmeticError.IntegerOverflow:
+ # It's okay!
+ pass
+
+assert add(2, 4) == 6
+assert add(4, 8) == 12
+
+try:
+ sub(0, 1)
+ assert(not("Should have thrown a IntegerOverflow exception!"))
+except ArithmeticError.IntegerOverflow:
+ # It's okay!
+ pass
+
+assert sub(4, 2) == 2
+assert sub(8, 4) == 4
+
+assert div(8, 4) == 2
+
+try:
+ div(8, 0)
+except InternalError:
+ # It's okay!
+ pass
+else:
+ assert(not("Should have panicked when dividing by zero"))
+
+assert equal(2, 2)
+assert equal(4, 4)
+
+assert not equal(2, 4)
+assert not equal(4, 8)
diff --git a/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.rb b/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.rb
new file mode 100644
index 0000000000..6669eb279f
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'arithmetic'
+
+include Test::Unit::Assertions
+
+assert_raise Arithmetic::ArithmeticError::IntegerOverflow do
+ Arithmetic.add 18_446_744_073_709_551_615, 1
+end
+
+assert_equal Arithmetic.add(2, 4), 6
+assert_equal Arithmetic.add(4, 8), 12
+
+assert_raise Arithmetic::ArithmeticError::IntegerOverflow do
+ Arithmetic.sub 0, 1
+end
+
+assert_equal Arithmetic.sub(4, 2), 2
+assert_equal Arithmetic.sub(8, 4), 4
+assert_equal Arithmetic.div(8, 4), 2
+
+assert_raise Arithmetic::InternalError do
+ Arithmetic.div 8, 0
+end
+
+assert Arithmetic.equal(2, 2)
+assert Arithmetic.equal(4, 4)
+
+assert !Arithmetic.equal(2, 4)
+assert !Arithmetic.equal(4, 8)
diff --git a/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.swift b/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.swift
new file mode 100644
index 0000000000..a8e34680e4
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/tests/bindings/test_arithmetic.swift
@@ -0,0 +1,32 @@
+import arithmetic
+
+do {
+ let _ = try add(a: 18446744073709551615, b: 1)
+ fatalError("Should have thrown a IntegerOverflow exception!")
+} catch ArithmeticError.IntegerOverflow {
+ // It's okay!
+}
+
+assert(try! add(a: 2, b: 4) == 6, "add work")
+assert(try! add(a: 4, b: 8) == 12, "add work")
+
+do {
+ let _ = try sub(a: 0, b: 1)
+ fatalError("Should have thrown a IntegerOverflow exception!")
+} catch ArithmeticError.IntegerOverflow {
+ // It's okay!
+}
+
+assert(try! sub(a: 4, b: 2) == 2, "sub work")
+assert(try! sub(a: 8, b: 4) == 4, "sub work")
+
+assert(div(dividend: 8, divisor: 4) == 2, "div works")
+
+// We can't test panicking in Swift because we force unwrap the error in
+// `div`, which we can't catch.
+
+assert(equal(a: 2, b: 2), "equal works")
+assert(equal(a: 4, b: 4), "equal works")
+
+assert(!equal(a: 2, b: 4), "non-equal works")
+assert(!equal(a: 4, b: 8), "non-equal works")
diff --git a/third_party/rust/uniffi-example-arithmetic/tests/test_generated_bindings.rs b/third_party/rust/uniffi-example-arithmetic/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..168e6e1d4c
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/tests/test_generated_bindings.rs
@@ -0,0 +1,6 @@
+uniffi::build_foreign_language_testcases!(
+ "tests/bindings/test_arithmetic.rb",
+ "tests/bindings/test_arithmetic.py",
+ "tests/bindings/test_arithmetic.kts",
+ "tests/bindings/test_arithmetic.swift",
+);
diff --git a/third_party/rust/uniffi-example-arithmetic/uniffi.toml b/third_party/rust/uniffi-example-arithmetic/uniffi.toml
new file mode 100644
index 0000000000..a9fab4c0b4
--- /dev/null
+++ b/third_party/rust/uniffi-example-arithmetic/uniffi.toml
@@ -0,0 +1,12 @@
+[bindings.kotlin]
+package_name = "org.mozilla.uniffi.example.arithmetic"
+cdylib_name = "arithmetical"
+
+[bindings.python]
+cdylib_name = "arithmetical"
+
+[bindings.ruby]
+cdylib_name = "arithmetical"
+
+[bindings.swift]
+cdylib_name = "arithmetical"
diff --git a/third_party/rust/uniffi-example-geometry/.cargo-checksum.json b/third_party/rust/uniffi-example-geometry/.cargo-checksum.json
new file mode 100644
index 0000000000..0354019788
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"ead86fd17caf11fb99bfac6eb5fb897d976226d018e32f33a2f55894815e6b34","build.rs":"8b13adbcc027872d444923e5b5d5b941d085a5de0f4a32ffa4f77f9a86ad78a0","src/geometry.udl":"7da7a6ec080c7117ec3c25206e23f9ed436e60b1a26fba34f991547680443550","src/lib.rs":"be9c624f691a8d1ebe3be6dbbcde44033759b8321a3a298453018dd69b9c14c7","tests/bindings/test_geometry.kts":"e537185e3c699df1c0468525700e8a38f9a504b2a663c38442146b951e38e9a7","tests/bindings/test_geometry.py":"3ea483b8a4fbe13aefa6641177ae149f75f734bc32bf0da533b97c1abf3dc317","tests/bindings/test_geometry.rb":"17c2fe8a7b477419a6646983dd88f1b07a0304b58a568c03e9bfa640d5b2df5c","tests/bindings/test_geometry.swift":"a61fec6bfe16020809e20e4da372748c24366767138c5672a0bfff85c4b62d78","tests/test_generated_bindings.rs":"ff8fc093ccb6ee3ee2235c09276c7bb87234ad143667429cb721e46379577f3d","uniffi.toml":"5b28f45d3c2581a52cf886a502f034778a002815b66994e5da2081a5c9f5284b"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-geometry/Cargo.toml b/third_party/rust/uniffi-example-geometry/Cargo.toml
new file mode 100644
index 0000000000..5314bbcda3
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/Cargo.toml
@@ -0,0 +1,36 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi-example-geometry"
+version = "0.22.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+publish = false
+license = "MPL-2.0"
+
+[lib]
+name = "uniffi_geometry"
+crate-type = [
+ "lib",
+ "cdylib",
+]
+
+[dependencies.uniffi]
+path = "../../uniffi"
+
+[dev-dependencies.uniffi]
+path = "../../uniffi"
+features = ["bindgen-tests"]
+
+[build-dependencies.uniffi]
+path = "../../uniffi"
+features = ["build"]
diff --git a/third_party/rust/uniffi-example-geometry/build.rs b/third_party/rust/uniffi-example-geometry/build.rs
new file mode 100644
index 0000000000..86c91badd9
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/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::generate_scaffolding("./src/geometry.udl").unwrap();
+}
diff --git a/third_party/rust/uniffi-example-geometry/src/geometry.udl b/third_party/rust/uniffi-example-geometry/src/geometry.udl
new file mode 100644
index 0000000000..af60d429bf
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/src/geometry.udl
@@ -0,0 +1,15 @@
+
+namespace geometry {
+ double gradient(Line ln);
+ Point? intersection(Line ln1, Line ln2);
+};
+
+dictionary Point {
+ double coord_x;
+ double coord_y;
+};
+
+dictionary Line {
+ Point start;
+ Point end;
+};
diff --git a/third_party/rust/uniffi-example-geometry/src/lib.rs b/third_party/rust/uniffi-example-geometry/src/lib.rs
new file mode 100644
index 0000000000..0e6add54f2
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/src/lib.rs
@@ -0,0 +1,47 @@
+/* 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/. */
+
+// https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
+// Silence, clippy!
+const EPSILON: f64 = 0.0001f64;
+
+#[derive(Debug, Clone)]
+pub struct Point {
+ coord_x: f64,
+ coord_y: f64,
+}
+
+#[derive(Debug, Clone)]
+pub struct Line {
+ start: Point,
+ end: Point,
+}
+
+pub fn gradient(ln: Line) -> f64 {
+ let rise = ln.end.coord_y - ln.start.coord_y;
+ let run = ln.end.coord_x - ln.start.coord_x;
+ rise / run
+}
+
+pub fn intersection(ln1: Line, ln2: Line) -> Option<Point> {
+ // TODO: yuck, should be able to take &Line as argument here
+ // and have rust figure it out with a bunch of annotations...
+ let g1 = gradient(ln1.clone());
+ let z1 = ln1.start.coord_y - g1 * ln1.start.coord_x;
+ let g2 = gradient(ln2.clone());
+ let z2 = ln2.start.coord_y - g2 * ln2.start.coord_x;
+ // Parallel lines do not intersect.
+ if (g1 - g2).abs() < EPSILON {
+ return None;
+ }
+ // Otherwise, they intersect at this fancy calculation that
+ // I found on wikipedia.
+ let x = (z2 - z1) / (g1 - g2);
+ Some(Point {
+ coord_x: x,
+ coord_y: g1 * x + z1,
+ })
+}
+
+include!(concat!(env!("OUT_DIR"), "/geometry.uniffi.rs"));
diff --git a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.kts b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.kts
new file mode 100644
index 0000000000..77bb9932ec
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.kts
@@ -0,0 +1,10 @@
+import uniffi.geometry.*;
+
+val ln1 = Line(Point(0.0,0.0), Point(1.0,2.0))
+val ln2 = Line(Point(1.0,1.0), Point(2.0,2.0))
+
+assert( gradient(ln1) == 2.0 )
+assert( gradient(ln2) == 1.0 )
+
+assert( intersection(ln1, ln2) == Point(0.0, 0.0) )
+assert( intersection(ln1, ln1) == null )
diff --git a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py
new file mode 100644
index 0000000000..a40e2af6ec
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py
@@ -0,0 +1,10 @@
+from geometry import *
+
+ln1 = Line(Point(0,0), Point(1,2))
+ln2 = Line(Point(1,1), Point(2,2))
+
+assert gradient(ln1) == 2
+assert gradient(ln2) == 1
+
+assert intersection(ln1, ln2) == Point(0, 0)
+assert intersection(ln1, ln1) is None
diff --git a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb
new file mode 100644
index 0000000000..8b1280d823
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'geometry'
+
+include Test::Unit::Assertions
+include Geometry
+
+ln1 = Line.new(Point.new(0.0, 0.0), Point.new(1.0, 2.0))
+ln2 = Line.new(Point.new(1.0, 1.0), Point.new(2.0, 2.0))
+
+assert_equal Geometry.gradient(ln1), 2
+assert_equal Geometry.gradient(ln2), 1
+
+assert_equal Geometry.intersection(ln1, ln2), Point.new(0, 0)
+assert Geometry.intersection(ln1, ln1).nil?
diff --git a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.swift b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.swift
new file mode 100644
index 0000000000..58bd65607f
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.swift
@@ -0,0 +1,10 @@
+import geometry
+
+let ln1 = Line(start: Point(coordX: 0, coordY: 0), end: Point(coordX: 1, coordY: 2))
+let ln2 = Line(start: Point(coordX: 1, coordY: 1), end: Point(coordX: 2, coordY: 2))
+
+assert(gradient(ln: ln1) == 2.0)
+assert(gradient(ln: ln2) == 1.0)
+
+assert(intersection(ln1: ln1, ln2: ln2) == Point(coordX: 0, coordY: 0))
+assert(intersection(ln1: ln1, ln2: ln1) == nil)
diff --git a/third_party/rust/uniffi-example-geometry/tests/test_generated_bindings.rs b/third_party/rust/uniffi-example-geometry/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..4638d847c8
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/tests/test_generated_bindings.rs
@@ -0,0 +1,6 @@
+uniffi::build_foreign_language_testcases!(
+ "tests/bindings/test_geometry.py",
+ "tests/bindings/test_geometry.rb",
+ "tests/bindings/test_geometry.kts",
+ "tests/bindings/test_geometry.swift",
+);
diff --git a/third_party/rust/uniffi-example-geometry/uniffi.toml b/third_party/rust/uniffi-example-geometry/uniffi.toml
new file mode 100644
index 0000000000..969379365a
--- /dev/null
+++ b/third_party/rust/uniffi-example-geometry/uniffi.toml
@@ -0,0 +1 @@
+[bindings.swift]
diff --git a/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json b/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json
new file mode 100644
index 0000000000..89fd7e5d7b
--- /dev/null
+++ b/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"61b717bb1057108afdc32cb57de6efbd44fe839a8b9137ef1eefca2c3ac31ea8","build.rs":"8fcc699e02a7a7a163c03784a0d78f527ca34ab79a1fe583a01c1b320d8669ce","src/lib.rs":"b5eb9861fc436ab9fe9b0c00f95e374d812bbc0ea10b98cf746d38c96e44343a","src/rondpoint.udl":"ca4d8720758608b06ffd5f81bfc73881fbd0693a7a5b21bfe61a4557ea052048","tests/bindings/test_rondpoint.kts":"4aac8353278807f4add95c81f4c6c61187204b9767f882fd64872ed8ac1f6451","tests/bindings/test_rondpoint.py":"d618274170af767f8a5614a2565ea698b26ea3e1a222d5c110e7b2d00763e73b","tests/bindings/test_rondpoint.rb":"9cc49df311823d6caedbe7b05ff8c4da6329063c2ce16810192aaaa7edcdf5f5","tests/bindings/test_rondpoint.swift":"fa806e7e09c22ed44496658f6e0781765447bbdd250d7adf4b1152248ed70e69","tests/test_generated_bindings.rs":"5464f89e91c458f164b83a454c6df67a2953873e8a785a4720a2253d843f88e5"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-rondpoint/Cargo.toml b/third_party/rust/uniffi-example-rondpoint/Cargo.toml
new file mode 100644
index 0000000000..c4efbe5b8f
--- /dev/null
+++ b/third_party/rust/uniffi-example-rondpoint/Cargo.toml
@@ -0,0 +1,36 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi-example-rondpoint"
+version = "0.22.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+publish = false
+license = "MPL-2.0"
+
+[lib]
+name = "uniffi_rondpoint"
+crate-type = [
+ "lib",
+ "cdylib",
+]
+
+[dependencies.uniffi]
+path = "../../uniffi"
+
+[dev-dependencies.uniffi]
+path = "../../uniffi"
+features = ["bindgen-tests"]
+
+[build-dependencies.uniffi]
+path = "../../uniffi"
+features = ["build"]
diff --git a/third_party/rust/uniffi-example-rondpoint/build.rs b/third_party/rust/uniffi-example-rondpoint/build.rs
new file mode 100644
index 0000000000..529881fefc
--- /dev/null
+++ b/third_party/rust/uniffi-example-rondpoint/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::generate_scaffolding("./src/rondpoint.udl").unwrap();
+}
diff --git a/third_party/rust/uniffi-example-rondpoint/src/lib.rs b/third_party/rust/uniffi-example-rondpoint/src/lib.rs
new file mode 100644
index 0000000000..b5b78fb767
--- /dev/null
+++ b/third_party/rust/uniffi-example-rondpoint/src/lib.rs
@@ -0,0 +1,293 @@
+/* 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 std::collections::HashMap;
+
+#[derive(Debug, Clone)]
+pub struct Dictionnaire {
+ un: Enumeration,
+ deux: bool,
+ petit_nombre: u8,
+ gros_nombre: u64,
+}
+
+#[derive(Debug, Clone)]
+pub struct DictionnaireNombres {
+ petit_nombre: u8,
+ court_nombre: u16,
+ nombre_simple: u32,
+ gros_nombre: u64,
+}
+
+#[derive(Debug, Clone)]
+pub struct DictionnaireNombresSignes {
+ petit_nombre: i8,
+ court_nombre: i16,
+ nombre_simple: i32,
+ gros_nombre: i64,
+}
+
+#[derive(Debug, Clone)]
+pub enum Enumeration {
+ Un,
+ Deux,
+ Trois,
+}
+
+#[derive(Debug, Clone)]
+pub enum EnumerationAvecDonnees {
+ Zero,
+ Un { premier: u32 },
+ Deux { premier: u32, second: String },
+}
+
+#[allow(non_camel_case_types)]
+#[allow(non_snake_case)]
+pub struct minusculeMAJUSCULEDict {
+ minusculeMAJUSCULEField: bool,
+}
+
+#[allow(non_camel_case_types)]
+pub enum minusculeMAJUSCULEEnum {
+ minusculeMAJUSCULEVariant,
+}
+
+fn copie_enumeration(e: Enumeration) -> Enumeration {
+ e
+}
+
+fn copie_enumerations(e: Vec<Enumeration>) -> Vec<Enumeration> {
+ e
+}
+
+fn copie_carte(
+ e: HashMap<String, EnumerationAvecDonnees>,
+) -> HashMap<String, EnumerationAvecDonnees> {
+ e
+}
+
+fn copie_dictionnaire(d: Dictionnaire) -> Dictionnaire {
+ d
+}
+
+fn switcheroo(b: bool) -> bool {
+ !b
+}
+
+// Test that values can traverse both ways across the FFI.
+// Even if roundtripping works, it's possible we have
+// symmetrical errors that cancel each other out.
+#[derive(Debug, Clone)]
+struct Retourneur;
+impl Retourneur {
+ fn new() -> Self {
+ Retourneur
+ }
+ fn identique_i8(&self, value: i8) -> i8 {
+ value
+ }
+ fn identique_u8(&self, value: u8) -> u8 {
+ value
+ }
+ fn identique_i16(&self, value: i16) -> i16 {
+ value
+ }
+ fn identique_u16(&self, value: u16) -> u16 {
+ value
+ }
+ fn identique_i32(&self, value: i32) -> i32 {
+ value
+ }
+ fn identique_u32(&self, value: u32) -> u32 {
+ value
+ }
+ fn identique_i64(&self, value: i64) -> i64 {
+ value
+ }
+ fn identique_u64(&self, value: u64) -> u64 {
+ value
+ }
+ fn identique_float(&self, value: f32) -> f32 {
+ value
+ }
+ fn identique_double(&self, value: f64) -> f64 {
+ value
+ }
+ fn identique_boolean(&self, value: bool) -> bool {
+ value
+ }
+ fn identique_string(&self, value: String) -> String {
+ value
+ }
+ fn identique_nombres_signes(
+ &self,
+ value: DictionnaireNombresSignes,
+ ) -> DictionnaireNombresSignes {
+ value
+ }
+ fn identique_nombres(&self, value: DictionnaireNombres) -> DictionnaireNombres {
+ value
+ }
+ fn identique_optionneur_dictionnaire(
+ &self,
+ value: OptionneurDictionnaire,
+ ) -> OptionneurDictionnaire {
+ value
+ }
+}
+
+#[derive(Debug, Clone)]
+struct Stringifier;
+
+#[allow(dead_code)]
+impl Stringifier {
+ fn new() -> Self {
+ Stringifier
+ }
+ fn to_string_i8(&self, value: i8) -> String {
+ value.to_string()
+ }
+ fn to_string_u8(&self, value: u8) -> String {
+ value.to_string()
+ }
+ fn to_string_i16(&self, value: i16) -> String {
+ value.to_string()
+ }
+ fn to_string_u16(&self, value: u16) -> String {
+ value.to_string()
+ }
+ fn to_string_i32(&self, value: i32) -> String {
+ value.to_string()
+ }
+ fn to_string_u32(&self, value: u32) -> String {
+ value.to_string()
+ }
+ fn to_string_i64(&self, value: i64) -> String {
+ value.to_string()
+ }
+ fn to_string_u64(&self, value: u64) -> String {
+ value.to_string()
+ }
+ fn to_string_float(&self, value: f32) -> String {
+ value.to_string()
+ }
+ fn to_string_double(&self, value: f64) -> String {
+ value.to_string()
+ }
+ fn to_string_boolean(&self, value: bool) -> String {
+ value.to_string()
+ }
+ fn well_known_string(&self, value: String) -> String {
+ format!("uniffi 💚 {value}!")
+ }
+}
+
+#[derive(Debug, Clone)]
+struct Optionneur;
+impl Optionneur {
+ fn new() -> Self {
+ Optionneur
+ }
+ fn sinon_string(&self, value: String) -> String {
+ value
+ }
+ fn sinon_null(&self, value: Option<String>) -> Option<String> {
+ value
+ }
+ fn sinon_boolean(&self, value: bool) -> bool {
+ value
+ }
+ fn sinon_sequence(&self, value: Vec<String>) -> Vec<String> {
+ value
+ }
+
+ fn sinon_zero(&self, value: Option<i32>) -> Option<i32> {
+ value
+ }
+
+ fn sinon_u8_dec(&self, value: u8) -> u8 {
+ value
+ }
+ fn sinon_i8_dec(&self, value: i8) -> i8 {
+ value
+ }
+ fn sinon_u16_dec(&self, value: u16) -> u16 {
+ value
+ }
+ fn sinon_i16_dec(&self, value: i16) -> i16 {
+ value
+ }
+ fn sinon_u32_dec(&self, value: u32) -> u32 {
+ value
+ }
+ fn sinon_i32_dec(&self, value: i32) -> i32 {
+ value
+ }
+ fn sinon_u64_dec(&self, value: u64) -> u64 {
+ value
+ }
+ fn sinon_i64_dec(&self, value: i64) -> i64 {
+ value
+ }
+
+ fn sinon_u8_hex(&self, value: u8) -> u8 {
+ value
+ }
+ fn sinon_i8_hex(&self, value: i8) -> i8 {
+ value
+ }
+ fn sinon_u16_hex(&self, value: u16) -> u16 {
+ value
+ }
+ fn sinon_i16_hex(&self, value: i16) -> i16 {
+ value
+ }
+ fn sinon_u32_hex(&self, value: u32) -> u32 {
+ value
+ }
+ fn sinon_i32_hex(&self, value: i32) -> i32 {
+ value
+ }
+ fn sinon_u64_hex(&self, value: u64) -> u64 {
+ value
+ }
+ fn sinon_i64_hex(&self, value: i64) -> i64 {
+ value
+ }
+
+ fn sinon_u32_oct(&self, value: u32) -> u32 {
+ value
+ }
+
+ fn sinon_f32(&self, value: f32) -> f32 {
+ value
+ }
+ fn sinon_f64(&self, value: f64) -> f64 {
+ value
+ }
+
+ fn sinon_enum(&self, value: Enumeration) -> Enumeration {
+ value
+ }
+}
+
+pub struct OptionneurDictionnaire {
+ i8_var: i8,
+ u8_var: u8,
+ i16_var: i16,
+ u16_var: u16,
+ i32_var: i32,
+ u32_var: u32,
+ i64_var: i64,
+ u64_var: u64,
+ float_var: f32,
+ double_var: f64,
+ boolean_var: bool,
+ string_var: String,
+ list_var: Vec<String>,
+ enumeration_var: Enumeration,
+ dictionnaire_var: Option<minusculeMAJUSCULEEnum>,
+}
+
+include!(concat!(env!("OUT_DIR"), "/rondpoint.uniffi.rs"));
diff --git a/third_party/rust/uniffi-example-rondpoint/src/rondpoint.udl b/third_party/rust/uniffi-example-rondpoint/src/rondpoint.udl
new file mode 100644
index 0000000000..05b1bfe825
--- /dev/null
+++ b/third_party/rust/uniffi-example-rondpoint/src/rondpoint.udl
@@ -0,0 +1,146 @@
+namespace rondpoint {
+ Dictionnaire copie_dictionnaire(Dictionnaire d);
+ Enumeration copie_enumeration(Enumeration e);
+ sequence<Enumeration> copie_enumerations(sequence<Enumeration> e);
+ record<DOMString, EnumerationAvecDonnees> copie_carte(record<DOMString, EnumerationAvecDonnees> c);
+ boolean switcheroo(boolean b);
+};
+
+dictionary minusculeMAJUSCULEDict {
+ boolean minusculeMAJUSCULEField;
+};
+
+enum minusculeMAJUSCULEEnum {
+ "minusculeMAJUSCULEVariant",
+};
+
+enum Enumeration {
+ "Un",
+ "Deux",
+ "Trois",
+};
+
+[Enum]
+interface EnumerationAvecDonnees {
+ Zero();
+ Un(u32 premier);
+ Deux(u32 premier, string second);
+};
+
+dictionary Dictionnaire {
+ Enumeration un;
+ boolean deux;
+ u8 petit_nombre;
+ u64 gros_nombre;
+};
+
+dictionary DictionnaireNombres {
+ u8 petit_nombre;
+ u16 court_nombre;
+ u32 nombre_simple;
+ u64 gros_nombre;
+};
+
+dictionary DictionnaireNombresSignes {
+ i8 petit_nombre;
+ i16 court_nombre;
+ i32 nombre_simple;
+ i64 gros_nombre;
+};
+
+interface Retourneur {
+ constructor();
+ i8 identique_i8(i8 value);
+ u8 identique_u8(u8 value);
+ i16 identique_i16(i16 value);
+ u16 identique_u16(u16 value);
+ i32 identique_i32(i32 value);
+ u32 identique_u32(u32 value);
+ i64 identique_i64(i64 value);
+ u64 identique_u64(u64 value);
+ float identique_float(float value);
+ double identique_double(double value);
+ boolean identique_boolean(boolean value);
+ string identique_string(string value);
+
+ DictionnaireNombresSignes identique_nombres_signes(DictionnaireNombresSignes value);
+ DictionnaireNombres identique_nombres(DictionnaireNombres value);
+ OptionneurDictionnaire identique_optionneur_dictionnaire(OptionneurDictionnaire value);
+};
+
+interface Stringifier {
+ constructor();
+ string well_known_string(string value);
+
+ string to_string_i8(i8 value);
+ string to_string_u8(u8 value);
+ string to_string_i16(i16 value);
+ string to_string_u16(u16 value);
+ string to_string_i32(i32 value);
+ string to_string_u32(u32 value);
+ string to_string_i64(i64 value);
+ string to_string_u64(u64 value);
+ string to_string_float(float value);
+ string to_string_double(double value);
+ string to_string_boolean(boolean value);
+};
+
+interface Optionneur {
+ constructor();
+ boolean sinon_boolean(optional boolean value = false);
+ string sinon_string(optional string value = "default");
+
+ sequence<string> sinon_sequence(optional sequence<string> value = []);
+
+ // Either sides of nullable.
+ string? sinon_null(optional string? value = null);
+ i32? sinon_zero(optional i32? value = 0);
+
+ // Decimal integers, all 42.
+ u8 sinon_u8_dec(optional u8 value = 42);
+ i8 sinon_i8_dec(optional i8 value = -42);
+ u16 sinon_u16_dec(optional u16 value = 42);
+ i16 sinon_i16_dec(optional i16 value = 42);
+ u32 sinon_u32_dec(optional u32 value = 42);
+ i32 sinon_i32_dec(optional i32 value = 42);
+ u64 sinon_u64_dec(optional u64 value = 42);
+ i64 sinon_i64_dec(optional i64 value = 42);
+
+ // Hexadecimal, including negatgives.
+ u8 sinon_u8_hex(optional u8 value = 0xff);
+ i8 sinon_i8_hex(optional i8 value = -0x7f);
+ u16 sinon_u16_hex(optional u16 value = 0xffff);
+ i16 sinon_i16_hex(optional i16 value = 0x7f);
+ u32 sinon_u32_hex(optional u32 value = 0xffffffff);
+ i32 sinon_i32_hex(optional i32 value = 0x7fffffff);
+ u64 sinon_u64_hex(optional u64 value = 0xffffffffffffffff);
+ i64 sinon_i64_hex(optional i64 value = 0x7fffffffffffffff);
+
+ // Octal, FWIW.
+ u32 sinon_u32_oct(optional u32 value = 0755);
+
+ // Floats
+ f32 sinon_f32(optional f32 value = 42.0);
+ f64 sinon_f64(optional f64 value = 42.1);
+
+ // Enums, which we have to treat as strings in the UDL frontend.
+ Enumeration sinon_enum(optional Enumeration value = "Trois");
+};
+
+dictionary OptionneurDictionnaire {
+ i8 i8_var = -8;
+ u8 u8_var = 8;
+ i16 i16_var = -0x10;
+ u16 u16_var = 0x10;
+ i32 i32_var = -32;
+ u32 u32_var = 32;
+ i64 i64_var = -64;
+ u64 u64_var = 64;
+ float float_var = 4.0;
+ double double_var = 8.0;
+ boolean boolean_var = true;
+ string string_var = "default";
+ sequence<string> list_var = [];
+ Enumeration enumeration_var = "DEUX";
+ minusculeMAJUSCULEEnum? dictionnaire_var = null;
+};
diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.kts b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.kts
new file mode 100644
index 0000000000..cc5ddf2a86
--- /dev/null
+++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.kts
@@ -0,0 +1,250 @@
+import uniffi.rondpoint.*
+
+val dico = Dictionnaire(Enumeration.DEUX, true, 0u, 123456789u)
+val copyDico = copieDictionnaire(dico)
+assert(dico == copyDico)
+
+assert(copieEnumeration(Enumeration.DEUX) == Enumeration.DEUX)
+assert(copieEnumerations(listOf(Enumeration.UN, Enumeration.DEUX)) == listOf(Enumeration.UN, Enumeration.DEUX))
+assert(copieCarte(mapOf(
+ "0" to EnumerationAvecDonnees.Zero,
+ "1" to EnumerationAvecDonnees.Un(1u),
+ "2" to EnumerationAvecDonnees.Deux(2u, "deux")
+)) == mapOf(
+ "0" to EnumerationAvecDonnees.Zero,
+ "1" to EnumerationAvecDonnees.Un(1u),
+ "2" to EnumerationAvecDonnees.Deux(2u, "deux")
+))
+
+val var1: EnumerationAvecDonnees = EnumerationAvecDonnees.Zero
+val var2: EnumerationAvecDonnees = EnumerationAvecDonnees.Un(1u)
+val var3: EnumerationAvecDonnees = EnumerationAvecDonnees.Un(2u)
+assert(var1 != var2)
+assert(var2 != var3)
+assert(var1 == EnumerationAvecDonnees.Zero)
+assert(var1 != EnumerationAvecDonnees.Un(1u))
+assert(var2 == EnumerationAvecDonnees.Un(1u))
+
+assert(switcheroo(false))
+
+// Test the roundtrip across the FFI.
+// This shows that the values we send come back in exactly the same state as we sent them.
+// i.e. it shows that lowering from kotlin and lifting into rust is symmetrical with
+// lowering from rust and lifting into kotlin.
+val rt = Retourneur()
+
+fun <T> List<T>.affirmAllerRetour(fn: (T) -> T) {
+ this.forEach { v ->
+ assert(fn.invoke(v) == v) { "$fn($v)" }
+ }
+}
+
+// Booleans
+listOf(true, false).affirmAllerRetour(rt::identiqueBoolean)
+
+// Bytes.
+listOf(Byte.MIN_VALUE, Byte.MAX_VALUE).affirmAllerRetour(rt::identiqueI8)
+listOf(0x00, 0xFF).map { it.toUByte() }.affirmAllerRetour(rt::identiqueU8)
+
+// Shorts
+listOf(Short.MIN_VALUE, Short.MAX_VALUE).affirmAllerRetour(rt::identiqueI16)
+listOf(0x0000, 0xFFFF).map { it.toUShort() }.affirmAllerRetour(rt::identiqueU16)
+
+// Ints
+listOf(0, 1, -1, Int.MIN_VALUE, Int.MAX_VALUE).affirmAllerRetour(rt::identiqueI32)
+listOf(0x00000000, 0xFFFFFFFF).map { it.toUInt() }.affirmAllerRetour(rt::identiqueU32)
+
+// Longs
+listOf(0L, 1L, -1L, Long.MIN_VALUE, Long.MAX_VALUE).affirmAllerRetour(rt::identiqueI64)
+listOf(0u, 1u, ULong.MIN_VALUE, ULong.MAX_VALUE).affirmAllerRetour(rt::identiqueU64)
+
+// Floats
+listOf(0.0F, 0.5F, 0.25F, Float.MIN_VALUE, Float.MAX_VALUE).affirmAllerRetour(rt::identiqueFloat)
+
+// Doubles
+listOf(0.0, 1.0, Double.MIN_VALUE, Double.MAX_VALUE).affirmAllerRetour(rt::identiqueDouble)
+
+// Strings
+listOf("", "abc", "null\u0000byte", "été", "ښي لاس ته لوستلو لوستل", "😻emoji 👨‍👧‍👦multi-emoji, 🇨🇭a flag, a canal, panama")
+ .affirmAllerRetour(rt::identiqueString)
+
+listOf(-1, 0, 1).map { DictionnaireNombresSignes(it.toByte(), it.toShort(), it.toInt(), it.toLong()) }
+ .affirmAllerRetour(rt::identiqueNombresSignes)
+
+listOf(0, 1).map { DictionnaireNombres(it.toUByte(), it.toUShort(), it.toUInt(), it.toULong()) }
+ .affirmAllerRetour(rt::identiqueNombres)
+
+
+rt.destroy()
+
+// Test one way across the FFI.
+//
+// We send one representation of a value to lib.rs, and it transforms it into another, a string.
+// lib.rs sends the string back, and then we compare here in kotlin.
+//
+// This shows that the values are transformed into strings the same way in both kotlin and rust.
+// i.e. if we assume that the string return works (we test this assumption elsewhere)
+// we show that lowering from kotlin and lifting into rust has values that both kotlin and rust
+// both stringify in the same way. i.e. the same values.
+//
+// If we roundtripping proves the symmetry of our lowering/lifting from here to rust, and lowering/lifting from rust t here,
+// and this convinces us that lowering/lifting from here to rust is correct, then
+// together, we've shown the correctness of the return leg.
+val st = Stringifier()
+
+typealias StringyEquals<T> = (observed: String, expected: T) -> Boolean
+fun <T> List<T>.affirmEnchaine(
+ fn: (T) -> String,
+ equals: StringyEquals<T> = { obs, exp -> obs == exp.toString() }
+) {
+ this.forEach { exp ->
+ val obs = fn.invoke(exp)
+ assert(equals(obs, exp)) { "$fn($exp): observed=$obs, expected=$exp" }
+ }
+}
+
+// Test the efficacy of the string transport from rust. If this fails, but everything else
+// works, then things are very weird.
+val wellKnown = st.wellKnownString("kotlin")
+assert("uniffi 💚 kotlin!" == wellKnown) { "wellKnownString 'uniffi 💚 kotlin!' == '$wellKnown'" }
+
+// Booleans
+listOf(true, false).affirmEnchaine(st::toStringBoolean)
+
+// Bytes.
+listOf(Byte.MIN_VALUE, Byte.MAX_VALUE).affirmEnchaine(st::toStringI8)
+listOf(UByte.MIN_VALUE, UByte.MAX_VALUE).affirmEnchaine(st::toStringU8)
+
+// Shorts
+listOf(Short.MIN_VALUE, Short.MAX_VALUE).affirmEnchaine(st::toStringI16)
+listOf(UShort.MIN_VALUE, UShort.MAX_VALUE).affirmEnchaine(st::toStringU16)
+
+// Ints
+listOf(0, 1, -1, Int.MIN_VALUE, Int.MAX_VALUE).affirmEnchaine(st::toStringI32)
+listOf(0u, 1u, UInt.MIN_VALUE, UInt.MAX_VALUE).affirmEnchaine(st::toStringU32)
+
+// Longs
+listOf(0L, 1L, -1L, Long.MIN_VALUE, Long.MAX_VALUE).affirmEnchaine(st::toStringI64)
+listOf(0u, 1u, ULong.MIN_VALUE, ULong.MAX_VALUE).affirmEnchaine(st::toStringU64)
+
+// Floats
+// MIN_VALUE is 1.4E-45. Accuracy and formatting get weird at small sizes.
+listOf(0.0F, 1.0F, -1.0F, Float.MIN_VALUE, Float.MAX_VALUE).affirmEnchaine(st::toStringFloat) { s, n -> s.toFloat() == n }
+
+// Doubles
+// MIN_VALUE is 4.9E-324. Accuracy and formatting get weird at small sizes.
+listOf(0.0, 1.0, -1.0, Double.MIN_VALUE, Double.MAX_VALUE).affirmEnchaine(st::toStringDouble) { s, n -> s.toDouble() == n }
+
+st.destroy()
+
+// Prove to ourselves that default arguments are being used.
+// Step 1: call the methods without arguments, and check against the UDL.
+val op = Optionneur()
+
+assert(op.sinonString() == "default")
+
+assert(op.sinonBoolean() == false)
+
+assert(op.sinonSequence() == listOf<String>())
+
+// optionals
+assert(op.sinonNull() == null)
+assert(op.sinonZero() == 0)
+
+// decimal integers
+assert(op.sinonI8Dec() == (-42).toByte())
+assert(op.sinonU8Dec() == 42.toUByte())
+assert(op.sinonI16Dec() == 42.toShort())
+assert(op.sinonU16Dec() == 42.toUShort())
+assert(op.sinonI32Dec() == 42)
+assert(op.sinonU32Dec() == 42.toUInt())
+assert(op.sinonI64Dec() == 42L)
+assert(op.sinonU64Dec() == 42uL)
+
+// hexadecimal integers
+assert(op.sinonI8Hex() == (-0x7f).toByte())
+assert(op.sinonU8Hex() == 0xff.toUByte())
+assert(op.sinonI16Hex() == 0x7f.toShort())
+assert(op.sinonU16Hex() == 0xffff.toUShort())
+assert(op.sinonI32Hex() == 0x7fffffff)
+assert(op.sinonU32Hex() == 0xffffffff.toUInt())
+assert(op.sinonI64Hex() == 0x7fffffffffffffffL)
+assert(op.sinonU64Hex() == 0xffffffffffffffffuL)
+
+// octal integers
+assert(op.sinonU32Oct() == 493u) // 0o755
+
+// floats
+assert(op.sinonF32() == 42.0f)
+assert(op.sinonF64() == 42.1)
+
+// enums
+assert(op.sinonEnum() == Enumeration.TROIS)
+
+// Step 2. Convince ourselves that if we pass something else, then that changes the output.
+// We have shown something coming out of the sinon methods, but without eyeballing the Rust
+// we can't be sure that the arguments will change the return value.
+listOf("foo", "bar").affirmAllerRetour(op::sinonString)
+listOf(true, false).affirmAllerRetour(op::sinonBoolean)
+listOf(listOf("a", "b"), listOf()).affirmAllerRetour(op::sinonSequence)
+
+// optionals
+listOf("0", "1").affirmAllerRetour(op::sinonNull)
+listOf(0, 1).affirmAllerRetour(op::sinonZero)
+
+// integers
+listOf(0, 1).map { it.toUByte() }.affirmAllerRetour(op::sinonU8Dec)
+listOf(0, 1).map { it.toByte() }.affirmAllerRetour(op::sinonI8Dec)
+listOf(0, 1).map { it.toUShort() }.affirmAllerRetour(op::sinonU16Dec)
+listOf(0, 1).map { it.toShort() }.affirmAllerRetour(op::sinonI16Dec)
+listOf(0, 1).map { it.toUInt() }.affirmAllerRetour(op::sinonU32Dec)
+listOf(0, 1).map { it.toInt() }.affirmAllerRetour(op::sinonI32Dec)
+listOf(0, 1).map { it.toULong() }.affirmAllerRetour(op::sinonU64Dec)
+listOf(0, 1).map { it.toLong() }.affirmAllerRetour(op::sinonI64Dec)
+
+listOf(0, 1).map { it.toUByte() }.affirmAllerRetour(op::sinonU8Hex)
+listOf(0, 1).map { it.toByte() }.affirmAllerRetour(op::sinonI8Hex)
+listOf(0, 1).map { it.toUShort() }.affirmAllerRetour(op::sinonU16Hex)
+listOf(0, 1).map { it.toShort() }.affirmAllerRetour(op::sinonI16Hex)
+listOf(0, 1).map { it.toUInt() }.affirmAllerRetour(op::sinonU32Hex)
+listOf(0, 1).map { it.toInt() }.affirmAllerRetour(op::sinonI32Hex)
+listOf(0, 1).map { it.toULong() }.affirmAllerRetour(op::sinonU64Hex)
+listOf(0, 1).map { it.toLong() }.affirmAllerRetour(op::sinonI64Hex)
+
+listOf(0, 1).map { it.toUInt() }.affirmAllerRetour(op::sinonU32Oct)
+
+// floats
+listOf(0.0f, 1.0f).affirmAllerRetour(op::sinonF32)
+listOf(0.0, 1.0).affirmAllerRetour(op::sinonF64)
+
+// enums
+Enumeration.values().toList().affirmAllerRetour(op::sinonEnum)
+
+op.destroy()
+
+// Testing defaulting properties in record types.
+val defaultes = OptionneurDictionnaire()
+val explicites = OptionneurDictionnaire(
+ i8Var = -8,
+ u8Var = 8u,
+ i16Var = -16,
+ u16Var = 0x10u,
+ i32Var = -32,
+ u32Var = 32u,
+ i64Var = -64L,
+ u64Var = 64uL,
+ floatVar = 4.0f,
+ doubleVar = 8.0,
+ booleanVar = true,
+ stringVar = "default",
+ listVar = listOf(),
+ enumerationVar = Enumeration.DEUX,
+ dictionnaireVar = null
+)
+assert(defaultes == explicites)
+
+// …and makes sure they travel across and back the FFI.
+val rt2 = Retourneur()
+listOf(defaultes).affirmAllerRetour(rt2::identiqueOptionneurDictionnaire)
+
+rt2.destroy()
diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py
new file mode 100644
index 0000000000..ecfcc1e527
--- /dev/null
+++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py
@@ -0,0 +1,146 @@
+import sys
+import ctypes
+from rondpoint import *
+
+dico = Dictionnaire(Enumeration.DEUX, True, 0, 123456789)
+copyDico = copie_dictionnaire(dico)
+assert dico == copyDico
+
+assert copie_enumeration(Enumeration.DEUX) == Enumeration.DEUX
+assert copie_enumerations([Enumeration.UN, Enumeration.DEUX]) == [Enumeration.UN, Enumeration.DEUX]
+assert copie_carte({
+ "0": EnumerationAvecDonnees.ZERO(),
+ "1": EnumerationAvecDonnees.UN(1),
+ "2": EnumerationAvecDonnees.DEUX(2, "deux"),
+}) == {
+ "0": EnumerationAvecDonnees.ZERO(),
+ "1": EnumerationAvecDonnees.UN(1),
+ "2": EnumerationAvecDonnees.DEUX(2, "deux"),
+}
+
+assert switcheroo(False) is True
+
+assert EnumerationAvecDonnees.ZERO() != EnumerationAvecDonnees.UN(1)
+assert EnumerationAvecDonnees.UN(1) == EnumerationAvecDonnees.UN(1)
+assert EnumerationAvecDonnees.UN(1) != EnumerationAvecDonnees.UN(2)
+
+# Test the roundtrip across the FFI.
+# This shows that the values we send come back in exactly the same state as we sent them.
+# i.e. it shows that lowering from python and lifting into rust is symmetrical with
+# lowering from rust and lifting into python.
+rt = Retourneur()
+
+def affirmAllerRetour(vals, identique):
+ for v in vals:
+ id_v = identique(v)
+ assert id_v == v, f"Round-trip failure: {v} => {id_v}"
+
+MIN_I8 = -1 * 2**7
+MAX_I8 = 2**7 - 1
+MIN_I16 = -1 * 2**15
+MAX_I16 = 2**15 - 1
+MIN_I32 = -1 * 2**31
+MAX_I32 = 2**31 - 1
+MIN_I64 = -1 * 2**31
+MAX_I64 = 2**31 - 1
+
+# Python floats are always doubles, so won't round-trip through f32 correctly.
+# This truncates them appropriately.
+F32_ONE_THIRD = ctypes.c_float(1.0 / 3).value
+
+# Booleans
+affirmAllerRetour([True, False], rt.identique_boolean)
+
+# Bytes.
+affirmAllerRetour([MIN_I8, -1, 0, 1, MAX_I8], rt.identique_i8)
+affirmAllerRetour([0x00, 0x12, 0xFF], rt.identique_u8)
+
+# Shorts
+affirmAllerRetour([MIN_I16, -1, 0, 1, MAX_I16], rt.identique_i16)
+affirmAllerRetour([0x0000, 0x1234, 0xFFFF], rt.identique_u16)
+
+# Ints
+affirmAllerRetour([MIN_I32, -1, 0, 1, MAX_I32], rt.identique_i32)
+affirmAllerRetour([0x00000000, 0x12345678, 0xFFFFFFFF], rt.identique_u32)
+
+# Longs
+affirmAllerRetour([MIN_I64, -1, 0, 1, MAX_I64], rt.identique_i64)
+affirmAllerRetour([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], rt.identique_u64)
+
+# Floats
+affirmAllerRetour([0.0, 0.5, 0.25, 1.0, F32_ONE_THIRD], rt.identique_float)
+
+# Doubles
+affirmAllerRetour(
+ [0.0, 0.5, 0.25, 1.0, 1.0 / 3, sys.float_info.max, sys.float_info.min],
+ rt.identique_double
+)
+
+# Strings
+affirmAllerRetour(
+ ["", "abc", "été", "ښي لاس ته لوستلو لوستل", "😻emoji 👨‍👧‍👦multi-emoji, 🇨🇭a flag, a canal, panama"],
+ rt.identique_string
+)
+
+# Test one way across the FFI.
+#
+# We send one representation of a value to lib.rs, and it transforms it into another, a string.
+# lib.rs sends the string back, and then we compare here in python.
+#
+# This shows that the values are transformed into strings the same way in both python and rust.
+# i.e. if we assume that the string return works (we test this assumption elsewhere)
+# we show that lowering from python and lifting into rust has values that both python and rust
+# both stringify in the same way. i.e. the same values.
+#
+# If we roundtripping proves the symmetry of our lowering/lifting from here to rust, and lowering/lifting from rust to here,
+# and this convinces us that lowering/lifting from here to rust is correct, then
+# together, we've shown the correctness of the return leg.
+st = Stringifier()
+
+def affirmEnchaine(vals, toString, rustyStringify=lambda v: str(v).lower()):
+ for v in vals:
+ str_v = toString(v)
+ assert rustyStringify(v) == str_v, f"String compare error {v} => {str_v}"
+
+# Test the efficacy of the string transport from rust. If this fails, but everything else
+# works, then things are very weird.
+wellKnown = st.well_known_string("python")
+assert "uniffi 💚 python!" == wellKnown
+
+# Booleans
+affirmEnchaine([True, False], st.to_string_boolean)
+
+# Bytes.
+affirmEnchaine([MIN_I8, -1, 0, 1, MAX_I8], st.to_string_i8)
+affirmEnchaine([0x00, 0x12, 0xFF], st.to_string_u8)
+
+# Shorts
+affirmEnchaine([MIN_I16, -1, 0, 1, MAX_I16], st.to_string_i16)
+affirmEnchaine([0x0000, 0x1234, 0xFFFF], st.to_string_u16)
+
+# Ints
+affirmEnchaine([MIN_I32, -1, 0, 1, MAX_I32], st.to_string_i32)
+affirmEnchaine([0x00000000, 0x12345678, 0xFFFFFFFF], st.to_string_u32)
+
+# Longs
+affirmEnchaine([MIN_I64, -1, 0, 1, MAX_I64], st.to_string_i64)
+affirmEnchaine([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], st.to_string_u64)
+
+# Floats
+def rustyFloatToStr(v):
+ """Stringify a float in the same way that rust seems to."""
+ # Rust doesn't include the decimal part of whole enumber floats when stringifying.
+ if int(v) == v:
+ return str(int(v))
+ return str(v)
+
+affirmEnchaine([0.0, 0.5, 0.25, 1.0], st.to_string_float, rustyFloatToStr)
+assert st.to_string_float(F32_ONE_THIRD) == "0.33333334" # annoyingly different string repr
+
+# Doubles
+# TODO: float_info.max/float_info.min don't stringify-roundtrip properly yet, TBD.
+affirmEnchaine(
+ [0.0, 0.5, 0.25, 1.0, 1.0 / 3],
+ st.to_string_double,
+ rustyFloatToStr,
+)
diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb
new file mode 100644
index 0000000000..0121f6e0f9
--- /dev/null
+++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb
@@ -0,0 +1,142 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'rondpoint'
+
+include Test::Unit::Assertions
+include Rondpoint
+
+dico = Dictionnaire.new Enumeration::DEUX, true, 0, 123_456_789
+
+assert_equal dico, Rondpoint.copie_dictionnaire(dico)
+
+assert_equal Rondpoint.copie_enumeration(Enumeration::DEUX), Enumeration::DEUX
+
+assert_equal Rondpoint.copie_enumerations([
+ Enumeration::UN,
+ Enumeration::DEUX
+ ]), [Enumeration::UN, Enumeration::DEUX]
+
+assert_equal Rondpoint.copie_carte({
+ '0' => EnumerationAvecDonnees::ZERO.new,
+ '1' => EnumerationAvecDonnees::UN.new(1),
+ '2' => EnumerationAvecDonnees::DEUX.new(2, 'deux')
+ }), {
+ '0' => EnumerationAvecDonnees::ZERO.new,
+ '1' => EnumerationAvecDonnees::UN.new(1),
+ '2' => EnumerationAvecDonnees::DEUX.new(2, 'deux')
+ }
+
+assert Rondpoint.switcheroo(false)
+
+assert_not_equal EnumerationAvecDonnees::ZERO.new, EnumerationAvecDonnees::UN.new(1)
+assert_equal EnumerationAvecDonnees::UN.new(1), EnumerationAvecDonnees::UN.new(1)
+assert_not_equal EnumerationAvecDonnees::UN.new(1), EnumerationAvecDonnees::UN.new(2)
+
+# Test the roundtrip across the FFI.
+# This shows that the values we send come back in exactly the same state as we sent them.
+# i.e. it shows that lowering from ruby and lifting into rust is symmetrical with
+# lowering from rust and lifting into ruby.
+RT = Retourneur.new
+
+def affirm_aller_retour(vals, fn_name)
+ vals.each do |v|
+ id_v = RT.public_send fn_name, v
+
+ assert_equal id_v, v, "Round-trip failure: #{v} => #{id_v}"
+ end
+end
+
+MIN_I8 = -1 * 2**7
+MAX_I8 = 2**7 - 1
+MIN_I16 = -1 * 2**15
+MAX_I16 = 2**15 - 1
+MIN_I32 = -1 * 2**31
+MAX_I32 = 2**31 - 1
+MIN_I64 = -1 * 2**31
+MAX_I64 = 2**31 - 1
+
+# Ruby floats are always doubles, so won't round-trip through f32 correctly.
+# This truncates them appropriately.
+F32_ONE_THIRD = [1.0 / 3].pack('f').unpack('f')[0]
+
+# Booleans
+affirm_aller_retour([true, false], :identique_boolean)
+
+# Bytes.
+affirm_aller_retour([MIN_I8, -1, 0, 1, MAX_I8], :identique_i8)
+affirm_aller_retour([0x00, 0x12, 0xFF], :identique_u8)
+
+# Shorts
+affirm_aller_retour([MIN_I16, -1, 0, 1, MAX_I16], :identique_i16)
+affirm_aller_retour([0x0000, 0x1234, 0xFFFF], :identique_u16)
+
+# Ints
+affirm_aller_retour([MIN_I32, -1, 0, 1, MAX_I32], :identique_i32)
+affirm_aller_retour([0x00000000, 0x12345678, 0xFFFFFFFF], :identique_u32)
+
+# Longs
+affirm_aller_retour([MIN_I64, -1, 0, 1, MAX_I64], :identique_i64)
+affirm_aller_retour([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], :identique_u64)
+
+# Floats
+affirm_aller_retour([0.0, 0.5, 0.25, 1.0, F32_ONE_THIRD], :identique_float)
+
+# Doubles
+affirm_aller_retour(
+ [0.0, 0.5, 0.25, 1.0, 1.0 / 3, Float::MAX, Float::MIN],
+ :identique_double
+)
+
+# Strings
+affirm_aller_retour(
+ ['', 'abc', 'été', 'ښي لاس ته لوستلو لوستل',
+ '😻emoji 👨‍👧‍👦multi-emoji, 🇨🇭a flag, a canal, panama'],
+ :identique_string
+)
+
+# Test one way across the FFI.
+#
+# We send one representation of a value to lib.rs, and it transforms it into another, a string.
+# lib.rs sends the string back, and then we compare here in ruby.
+#
+# This shows that the values are transformed into strings the same way in both ruby and rust.
+# i.e. if we assume that the string return works (we test this assumption elsewhere)
+# we show that lowering from ruby and lifting into rust has values that both ruby and rust
+# both stringify in the same way. i.e. the same values.
+#
+# If we roundtripping proves the symmetry of our lowering/lifting from here to rust, and lowering/lifting from rust to here,
+# and this convinces us that lowering/lifting from here to rust is correct, then
+# together, we've shown the correctness of the return leg.
+ST = Stringifier.new
+
+def affirm_enchaine(vals, fn_name)
+ vals.each do |v|
+ str_v = ST.public_send fn_name, v
+
+ assert_equal v.to_s, str_v, "String compare error #{v} => #{str_v}"
+ end
+end
+
+# Test the efficacy of the string transport from rust. If this fails, but everything else
+# works, then things are very weird.
+assert_equal ST.well_known_string('ruby'), 'uniffi 💚 ruby!'
+
+# Booleans
+affirm_enchaine([true, false], :to_string_boolean)
+
+# Bytes.
+affirm_enchaine([MIN_I8, -1, 0, 1, MAX_I8], :to_string_i8)
+affirm_enchaine([0x00, 0x12, 0xFF], :to_string_u8)
+
+# Shorts
+affirm_enchaine([MIN_I16, -1, 0, 1, MAX_I16], :to_string_i16)
+affirm_enchaine([0x0000, 0x1234, 0xFFFF], :to_string_u16)
+
+# Ints
+affirm_enchaine([MIN_I32, -1, 0, 1, MAX_I32], :to_string_i32)
+affirm_enchaine([0x00000000, 0x12345678, 0xFFFFFFFF], :to_string_u32)
+
+# Longs
+affirm_enchaine([MIN_I64, -1, 0, 1, MAX_I64], :to_string_i64)
+affirm_enchaine([0x0000000000000000, 0x1234567890ABCDEF, 0xFFFFFFFFFFFFFFFF], :to_string_u64)
diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.swift b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.swift
new file mode 100644
index 0000000000..d9f47058ed
--- /dev/null
+++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.swift
@@ -0,0 +1,232 @@
+import rondpoint
+
+let dico = Dictionnaire(un: .deux, deux: false, petitNombre: 0, grosNombre: 123456789)
+let copyDico = copieDictionnaire(d: dico)
+assert(dico == copyDico)
+
+assert(copieEnumeration(e: .deux) == .deux)
+assert(copieEnumerations(e: [.un, .deux]) == [.un, .deux])
+assert(copieCarte(c:
+ ["0": .zero,
+ "1": .un(premier: 1),
+ "2": .deux(premier: 2, second: "deux")
+]) == [
+ "0": .zero,
+ "1": .un(premier: 1),
+ "2": .deux(premier: 2, second: "deux")
+])
+
+assert(EnumerationAvecDonnees.zero != EnumerationAvecDonnees.un(premier: 1))
+assert(EnumerationAvecDonnees.un(premier: 1) == EnumerationAvecDonnees.un(premier: 1))
+assert(EnumerationAvecDonnees.un(premier: 1) != EnumerationAvecDonnees.un(premier: 2))
+
+
+assert(switcheroo(b: false))
+
+// Test the roundtrip across the FFI.
+// This shows that the values we send come back in exactly the same state as we sent them.
+// i.e. it shows that lowering from swift and lifting into rust is symmetrical with
+// lowering from rust and lifting into swift.
+let rt = Retourneur()
+
+// Booleans
+[true, false].affirmAllerRetour(rt.identiqueBoolean)
+
+// Bytes.
+[.min, .max].affirmAllerRetour(rt.identiqueI8)
+[0x00, 0xFF].map { $0 as UInt8 }.affirmAllerRetour(rt.identiqueU8)
+
+// Shorts
+[.min, .max].affirmAllerRetour(rt.identiqueI16)
+[0x0000, 0xFFFF].map { $0 as UInt16 }.affirmAllerRetour(rt.identiqueU16)
+
+// Ints
+[0, 1, -1, .min, .max].affirmAllerRetour(rt.identiqueI32)
+[0x00000000, 0xFFFFFFFF].map { $0 as UInt32 }.affirmAllerRetour(rt.identiqueU32)
+
+// Longs
+[.zero, 1, -1, .min, .max].affirmAllerRetour(rt.identiqueI64)
+[.zero, 1, .min, .max].affirmAllerRetour(rt.identiqueU64)
+
+// Floats
+[.zero, 1, 0.25, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmAllerRetour(rt.identiqueFloat)
+
+// Doubles
+[0.0, 1.0, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmAllerRetour(rt.identiqueDouble)
+
+// Strings
+["", "abc", "null\0byte", "été", "ښي لاس ته لوستلو لوستل", "😻emoji 👨‍👧‍👦multi-emoji, 🇨🇭a flag, a canal, panama"]
+ .affirmAllerRetour(rt.identiqueString)
+
+// Test one way across the FFI.
+//
+// We send one representation of a value to lib.rs, and it transforms it into another, a string.
+// lib.rs sends the string back, and then we compare here in swift.
+//
+// This shows that the values are transformed into strings the same way in both swift and rust.
+// i.e. if we assume that the string return works (we test this assumption elsewhere)
+// we show that lowering from swift and lifting into rust has values that both swift and rust
+// both stringify in the same way. i.e. the same values.
+//
+// If we roundtripping proves the symmetry of our lowering/lifting from here to rust, and lowering/lifting from rust t here,
+// and this convinces us that lowering/lifting from here to rust is correct, then
+// together, we've shown the correctness of the return leg.
+let st = Stringifier()
+
+// Test the effigacy of the string transport from rust. If this fails, but everything else
+// works, then things are very weird.
+let wellKnown = st.wellKnownString(value: "swift")
+assert("uniffi 💚 swift!" == wellKnown, "wellKnownString 'uniffi 💚 swift!' == '\(wellKnown)'")
+
+// Booleans
+[true, false].affirmEnchaine(st.toStringBoolean)
+
+// Bytes.
+[.min, .max].affirmEnchaine(st.toStringI8)
+[.min, .max].affirmEnchaine(st.toStringU8)
+
+// Shorts
+[.min, .max].affirmEnchaine(st.toStringI16)
+[.min, .max].affirmEnchaine(st.toStringU16)
+
+// Ints
+[0, 1, -1, .min, .max].affirmEnchaine(st.toStringI32)
+[0, 1, .min, .max].affirmEnchaine(st.toStringU32)
+
+// Longs
+[.zero, 1, -1, .min, .max].affirmEnchaine(st.toStringI64)
+[.zero, 1, .min, .max].affirmEnchaine(st.toStringU64)
+
+// Floats
+[.zero, 1, -1, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmEnchaine(st.toStringFloat) { Float.init($0) == $1 }
+
+// Doubles
+[.zero, 1, -1, .leastNonzeroMagnitude, .greatestFiniteMagnitude].affirmEnchaine(st.toStringDouble) { Double.init($0) == $1 }
+
+// Some extension functions for testing the results of roundtripping and stringifying
+extension Array where Element: Equatable {
+ static func defaultEquals(_ observed: String, expected: Element) -> Bool {
+ let exp = "\(expected)"
+ return observed == exp
+ }
+
+ func affirmEnchaine(_ fn: (Element) -> String, equals: (String, Element) -> Bool = defaultEquals) {
+ self.forEach { v in
+ let obs = fn(v)
+ assert(equals(obs, v), "toString_\(type(of:v))(\(v)): observed=\(obs), expected=\(v)")
+ }
+ }
+
+ func affirmAllerRetour(_ fn: (Element) -> Element) {
+ self.forEach { v in
+ assert(fn(v) == v, "identique_\(type(of:v))(\(v))")
+ }
+ }
+}
+
+// Prove to ourselves that default arguments are being used.
+// Step 1: call the methods without arguments, and check against the UDL.
+let op = Optionneur()
+
+assert(op.sinonString() == "default")
+
+assert(op.sinonBoolean() == false)
+
+assert(op.sinonSequence() == [])
+
+// optionals
+assert(op.sinonNull() == nil)
+assert(op.sinonZero() == 0)
+
+// decimal integers
+assert(op.sinonU8Dec() == UInt8(42))
+assert(op.sinonI8Dec() == Int8(-42))
+assert(op.sinonU16Dec() == UInt16(42))
+assert(op.sinonI16Dec() == Int16(42))
+assert(op.sinonU32Dec() == UInt32(42))
+assert(op.sinonI32Dec() == Int32(42))
+assert(op.sinonU64Dec() == UInt64(42))
+assert(op.sinonI64Dec() == Int64(42))
+
+// hexadecimal integers
+assert(op.sinonU8Hex() == UInt8(0xff))
+assert(op.sinonI8Hex() == Int8(-0x7f))
+assert(op.sinonU16Hex() == UInt16(0xffff))
+assert(op.sinonI16Hex() == Int16(0x7f))
+assert(op.sinonU32Hex() == UInt32(0xffffffff))
+assert(op.sinonI32Hex() == Int32(0x7fffffff))
+assert(op.sinonU64Hex() == UInt64(0xffffffffffffffff))
+assert(op.sinonI64Hex() == Int64(0x7fffffffffffffff))
+
+// octal integers
+assert(op.sinonU32Oct() == UInt32(0o755))
+
+// floats
+assert(op.sinonF32() == 42.0)
+assert(op.sinonF64() == Double(42.1))
+
+// enums
+assert(op.sinonEnum() == .trois)
+
+// Step 2. Convince ourselves that if we pass something else, then that changes the output.
+// We have shown something coming out of the sinon methods, but without eyeballing the Rust
+// we can't be sure that the arguments will change the return value.
+["foo", "bar"].affirmAllerRetour(op.sinonString)
+[true, false].affirmAllerRetour(op.sinonBoolean)
+[["a", "b"], []].affirmAllerRetour(op.sinonSequence)
+
+// optionals
+["0", "1"].affirmAllerRetour(op.sinonNull)
+[0, 1].affirmAllerRetour(op.sinonZero)
+
+// integers
+[0, 1].affirmAllerRetour(op.sinonU8Dec)
+[0, 1].affirmAllerRetour(op.sinonI8Dec)
+[0, 1].affirmAllerRetour(op.sinonU16Dec)
+[0, 1].affirmAllerRetour(op.sinonI16Dec)
+[0, 1].affirmAllerRetour(op.sinonU32Dec)
+[0, 1].affirmAllerRetour(op.sinonI32Dec)
+[0, 1].affirmAllerRetour(op.sinonU64Dec)
+[0, 1].affirmAllerRetour(op.sinonI64Dec)
+
+[0, 1].affirmAllerRetour(op.sinonU8Hex)
+[0, 1].affirmAllerRetour(op.sinonI8Hex)
+[0, 1].affirmAllerRetour(op.sinonU16Hex)
+[0, 1].affirmAllerRetour(op.sinonI16Hex)
+[0, 1].affirmAllerRetour(op.sinonU32Hex)
+[0, 1].affirmAllerRetour(op.sinonI32Hex)
+[0, 1].affirmAllerRetour(op.sinonU64Hex)
+[0, 1].affirmAllerRetour(op.sinonI64Hex)
+
+[0, 1].affirmAllerRetour(op.sinonU32Oct)
+
+// floats
+[0.0, 1.0].affirmAllerRetour(op.sinonF32)
+[0.0, 1.0].affirmAllerRetour(op.sinonF64)
+
+// enums
+[.un, .deux, .trois].affirmAllerRetour(op.sinonEnum)
+
+// Testing defaulting properties in record types.
+let defaultes = OptionneurDictionnaire()
+let explicites = OptionneurDictionnaire(
+ i8Var: Int8(-8),
+ u8Var: UInt8(8),
+ i16Var: Int16(-16),
+ u16Var: UInt16(0x10),
+ i32Var: -32,
+ u32Var: UInt32(32),
+ i64Var: Int64(-64),
+ u64Var: UInt64(64),
+ floatVar: Float(4.0),
+ doubleVar: Double(8.0),
+ booleanVar: true,
+ stringVar: "default",
+ listVar: [],
+ enumerationVar: .deux,
+ dictionnaireVar: nil
+)
+
+// …and makes sure they travel across and back the FFI.
+assert(defaultes == explicites)
+[defaultes].affirmAllerRetour(rt.identiqueOptionneurDictionnaire)
diff --git a/third_party/rust/uniffi-example-rondpoint/tests/test_generated_bindings.rs b/third_party/rust/uniffi-example-rondpoint/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..d337374334
--- /dev/null
+++ b/third_party/rust/uniffi-example-rondpoint/tests/test_generated_bindings.rs
@@ -0,0 +1,6 @@
+uniffi::build_foreign_language_testcases!(
+ "tests/bindings/test_rondpoint.kts",
+ "tests/bindings/test_rondpoint.swift",
+ "tests/bindings/test_rondpoint.py",
+ "tests/bindings/test_rondpoint.rb",
+);
diff --git a/third_party/rust/uniffi-example-sprites/.cargo-checksum.json b/third_party/rust/uniffi-example-sprites/.cargo-checksum.json
new file mode 100644
index 0000000000..fac6b3ddfc
--- /dev/null
+++ b/third_party/rust/uniffi-example-sprites/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"78f11927c8b2562a9975726e0ac20b51d124b6a21fdd633a9c0b1f0b69c5c788","build.rs":"84ba28d15fc0987d8efb5d523a643717c6b4b9b29570c0cc9cff81126ecfce7f","src/lib.rs":"857ae59f3194cb7fe117a308854bc793b2b56441e297bd0e327795fd435494da","src/sprites.udl":"bfd35f04ba0549301189dfb8fc45b0f39bad00956c324f33be0e845fb7ff78aa","tests/bindings/test_sprites.kts":"06ed115325f37ce59ed6f33e2d651cd2aa352fddcc644580f62a6da6ca075844","tests/bindings/test_sprites.py":"2e6ce838cfb387586257703c3500062438e840dd7ae57d185cdc244dc0745b8f","tests/bindings/test_sprites.rb":"6289a1833c7c8f4583ee4f0488d680de2ee46cfb203095a9b66d7234e2f07d53","tests/bindings/test_sprites.swift":"b2c0a6f4d5edfd7de7c2ba77b838865ffda153a6f364f273456175192d3e6e00","tests/test_generated_bindings.rs":"9a22d693c97fc6d90031cc60f61ece1d9279165ad6a92c9fe937448e126e8de6"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-sprites/Cargo.toml b/third_party/rust/uniffi-example-sprites/Cargo.toml
new file mode 100644
index 0000000000..6c55e9d88c
--- /dev/null
+++ b/third_party/rust/uniffi-example-sprites/Cargo.toml
@@ -0,0 +1,36 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi-example-sprites"
+version = "0.22.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+publish = false
+license = "MPL-2.0"
+
+[lib]
+name = "uniffi_sprites"
+crate-type = [
+ "lib",
+ "cdylib",
+]
+
+[dependencies.uniffi]
+path = "../../uniffi"
+
+[dev-dependencies.uniffi]
+path = "../../uniffi"
+features = ["bindgen-tests"]
+
+[build-dependencies.uniffi]
+path = "../../uniffi"
+features = ["build"]
diff --git a/third_party/rust/uniffi-example-sprites/build.rs b/third_party/rust/uniffi-example-sprites/build.rs
new file mode 100644
index 0000000000..858b8d1615
--- /dev/null
+++ b/third_party/rust/uniffi-example-sprites/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::generate_scaffolding("./src/sprites.udl").unwrap();
+}
diff --git a/third_party/rust/uniffi-example-sprites/src/lib.rs b/third_party/rust/uniffi-example-sprites/src/lib.rs
new file mode 100644
index 0000000000..7aa3741d19
--- /dev/null
+++ b/third_party/rust/uniffi-example-sprites/src/lib.rs
@@ -0,0 +1,65 @@
+/* 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 std::sync::RwLock;
+
+// A point in two-dimensional space.
+#[derive(Debug, Clone)]
+pub struct Point {
+ x: f64,
+ y: f64,
+}
+
+// A magnitude and direction in two-dimensional space.
+// For simplicity we represent this as a point relative to the origin.
+#[derive(Debug, Clone)]
+pub struct Vector {
+ dx: f64,
+ dy: f64,
+}
+
+// Move from the given Point, according to the given Vector.
+pub fn translate(p: &Point, v: Vector) -> Point {
+ Point {
+ x: p.x + v.dx,
+ y: p.y + v.dy,
+ }
+}
+
+// An entity in our imaginary world, which occupies a position in space
+// and which can move about over time.
+#[derive(Debug)]
+pub struct Sprite {
+ // We must use interior mutability for managing mutable state, hence the `RwLock`.
+ current_position: RwLock<Point>,
+}
+
+impl Sprite {
+ fn new(initial_position: Option<Point>) -> Sprite {
+ Sprite {
+ current_position: RwLock::new(initial_position.unwrap_or(Point { x: 0.0, y: 0.0 })),
+ }
+ }
+
+ fn new_relative_to(reference: Point, direction: Vector) -> Sprite {
+ Sprite {
+ current_position: RwLock::new(translate(&reference, direction)),
+ }
+ }
+
+ fn get_position(&self) -> Point {
+ self.current_position.read().unwrap().clone()
+ }
+
+ fn move_to(&self, position: Point) {
+ *self.current_position.write().unwrap() = position;
+ }
+
+ fn move_by(&self, direction: Vector) {
+ let mut current_position = self.current_position.write().unwrap();
+ *current_position = translate(&current_position, direction)
+ }
+}
+
+include!(concat!(env!("OUT_DIR"), "/sprites.uniffi.rs"));
diff --git a/third_party/rust/uniffi-example-sprites/src/sprites.udl b/third_party/rust/uniffi-example-sprites/src/sprites.udl
new file mode 100644
index 0000000000..6781c6cee5
--- /dev/null
+++ b/third_party/rust/uniffi-example-sprites/src/sprites.udl
@@ -0,0 +1,22 @@
+
+namespace sprites {
+ Point translate([ByRef] Point position, Vector direction);
+};
+
+dictionary Point {
+ double x;
+ double y;
+};
+
+dictionary Vector {
+ double dx;
+ double dy;
+};
+
+interface Sprite {
+ constructor(Point? initial_position);
+ [Name=new_relative_to] constructor(Point reference, Vector direction);
+ Point get_position();
+ void move_to(Point position);
+ void move_by(Vector direction);
+};
diff --git a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.kts b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.kts
new file mode 100644
index 0000000000..42451f28dd
--- /dev/null
+++ b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.kts
@@ -0,0 +1,25 @@
+import uniffi.sprites.*;
+
+val sempty = Sprite(null)
+assert( sempty.getPosition() == Point(0.0, 0.0) )
+
+val s = Sprite(Point(0.0, 1.0))
+assert( s.getPosition() == Point(0.0, 1.0) )
+
+s.moveTo(Point(1.0, 2.0))
+assert( s.getPosition() == Point(1.0, 2.0) )
+
+s.moveBy(Vector(-4.0, 2.0))
+assert( s.getPosition() == Point(-3.0, 4.0) )
+
+s.destroy()
+try {
+ s.moveBy(Vector(0.0, 0.0))
+ assert(false) { "Should not be able to call anything after `destroy`" }
+} catch(e: IllegalStateException) {
+ assert(true)
+}
+
+val srel = Sprite.newRelativeTo(Point(0.0, 1.0), Vector(1.0, 1.5))
+assert( srel.getPosition() == Point(1.0, 2.5) )
+
diff --git a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py
new file mode 100644
index 0000000000..5142c2fc42
--- /dev/null
+++ b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py
@@ -0,0 +1,17 @@
+from sprites import *
+
+sempty = Sprite(None)
+assert sempty.get_position() == Point(0, 0)
+
+s = Sprite(Point(0, 1))
+assert s.get_position() == Point(0, 1)
+
+s.move_to(Point(1, 2))
+assert s.get_position() == Point(1, 2)
+
+s.move_by(Vector(-4, 2))
+assert s.get_position() == Point(-3, 4)
+
+srel = Sprite.new_relative_to(Point(0, 1), Vector(1, 1.5))
+assert srel.get_position() == Point(1, 2.5)
+
diff --git a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb
new file mode 100644
index 0000000000..9d79b57026
--- /dev/null
+++ b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'sprites'
+
+include Test::Unit::Assertions
+include Sprites
+
+sempty = Sprite.new(nil)
+assert_equal sempty.get_position, Point.new(0, 0)
+
+s = Sprite.new(Point.new(0, 1))
+assert_equal s.get_position, Point.new(0, 1)
+
+s.move_to(Point.new(1, 2))
+assert_equal s.get_position, Point.new(1, 2)
+
+s.move_by(Vector.new(-4, 2))
+assert_equal s.get_position, Point.new(-3, 4)
+
+srel = Sprite.new_relative_to(Point.new(0, 1), Vector.new(1, 1.5))
+assert_equal srel.get_position, Point.new(1, 2.5)
diff --git a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.swift b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.swift
new file mode 100644
index 0000000000..d5428ac679
--- /dev/null
+++ b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.swift
@@ -0,0 +1,16 @@
+import sprites
+
+let sempty = Sprite(initialPosition: nil)
+assert( sempty.getPosition() == Point(x: 0, y: 0))
+
+let s = Sprite(initialPosition: Point(x: 0, y: 1))
+assert( s.getPosition() == Point(x: 0, y: 1))
+
+s.moveTo(position: Point(x: 1.0, y: 2.0))
+assert( s.getPosition() == Point(x: 1, y: 2))
+
+s.moveBy(direction: Vector(dx: -4, dy: 2))
+assert( s.getPosition() == Point(x: -3, y: 4))
+
+let srel = Sprite.newRelativeTo(reference: Point(x: 0.0, y: 1.0), direction: Vector(dx: 1, dy: 1.5))
+assert( srel.getPosition() == Point(x: 1.0, y: 2.5) )
diff --git a/third_party/rust/uniffi-example-sprites/tests/test_generated_bindings.rs b/third_party/rust/uniffi-example-sprites/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..00dd779d68
--- /dev/null
+++ b/third_party/rust/uniffi-example-sprites/tests/test_generated_bindings.rs
@@ -0,0 +1,6 @@
+uniffi::build_foreign_language_testcases!(
+ "tests/bindings/test_sprites.py",
+ "tests/bindings/test_sprites.rb",
+ "tests/bindings/test_sprites.kts",
+ "tests/bindings/test_sprites.swift",
+);
diff --git a/third_party/rust/uniffi-example-todolist/.cargo-checksum.json b/third_party/rust/uniffi-example-todolist/.cargo-checksum.json
new file mode 100644
index 0000000000..0f7516a258
--- /dev/null
+++ b/third_party/rust/uniffi-example-todolist/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"2cadeccd37f54f3fe48ee85eeed4acdf6e7299acb6b43ed026d16695150cbab7","build.rs":"696168e6dd8a1f181748cb36161bee14a7523b9e5d9b2e3938f02cf24d374e45","src/lib.rs":"338494783dbbcd8abbc7a1b5206f52296064091faa0a04e2d622a0d370ad12fe","src/todolist.udl":"1f8a24049c2340b9184e95facfc191ecdcb91541729ae7f20b4625d67685f13c","tests/bindings/test_todolist.kts":"f3d29b48e0193563fc4f131d91ea697f758174dcdb80ea554f233949e575bf55","tests/bindings/test_todolist.py":"f7430af9347df0daa954d38bc2203ce400affbb9a53fced4bb67a6796afa0664","tests/bindings/test_todolist.rb":"6524b5271a9cc0e2d78ca9f86ccb6973889926688a0843b4505a4f62d48f6dcb","tests/bindings/test_todolist.swift":"d1911b85fe0c8c0b42e5421b5af5d7359c9a65bba477d23560eb4b0f52e80662","tests/test_generated_bindings.rs":"46ef1fbedaac0c4867812ef2632a641eab36ab0ee12f5757567dd037aeddcbd3"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-todolist/Cargo.toml b/third_party/rust/uniffi-example-todolist/Cargo.toml
new file mode 100644
index 0000000000..a6f4895972
--- /dev/null
+++ b/third_party/rust/uniffi-example-todolist/Cargo.toml
@@ -0,0 +1,40 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi-example-todolist"
+version = "0.22.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+publish = false
+license = "MPL-2.0"
+
+[lib]
+name = "uniffi_todolist"
+crate-type = [
+ "lib",
+ "cdylib",
+]
+
+[dependencies]
+once_cell = "1.12"
+thiserror = "1.0"
+
+[dependencies.uniffi]
+path = "../../uniffi"
+
+[dev-dependencies.uniffi]
+path = "../../uniffi"
+features = ["bindgen-tests"]
+
+[build-dependencies.uniffi]
+path = "../../uniffi"
+features = ["build"]
diff --git a/third_party/rust/uniffi-example-todolist/build.rs b/third_party/rust/uniffi-example-todolist/build.rs
new file mode 100644
index 0000000000..7376ae1a20
--- /dev/null
+++ b/third_party/rust/uniffi-example-todolist/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::generate_scaffolding("./src/todolist.udl").unwrap();
+}
diff --git a/third_party/rust/uniffi-example-todolist/src/lib.rs b/third_party/rust/uniffi-example-todolist/src/lib.rs
new file mode 100644
index 0000000000..fb2900e615
--- /dev/null
+++ b/third_party/rust/uniffi-example-todolist/src/lib.rs
@@ -0,0 +1,150 @@
+/* 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 std::sync::{Arc, RwLock};
+
+use once_cell::sync::Lazy;
+
+#[derive(Debug, Clone)]
+pub struct TodoEntry {
+ text: String,
+}
+
+// There is a single "default" TodoList that can be shared
+// by all consumers of this component. Depending on requirements,
+// a real app might like to use a `Weak<>` rather than an `Arc<>`
+// here to reduce the risk of circular references.
+static DEFAULT_LIST: Lazy<RwLock<Option<Arc<TodoList>>>> = Lazy::new(|| RwLock::new(None));
+
+#[derive(Debug, thiserror::Error)]
+pub enum TodoError {
+ #[error("The todo does not exist!")]
+ TodoDoesNotExist,
+ #[error("The todolist is empty!")]
+ EmptyTodoList,
+ #[error("That todo already exists!")]
+ DuplicateTodo,
+ #[error("Empty String error!: {0}")]
+ EmptyString(String),
+ #[error("I am a delegated Error: {0}")]
+ DeligatedError(#[from] std::io::Error),
+}
+
+/// Get a reference to the global default TodoList, if set.
+///
+fn get_default_list() -> Option<Arc<TodoList>> {
+ DEFAULT_LIST.read().unwrap().clone()
+}
+
+/// Set the global default TodoList.
+///
+/// This will silently drop any previously set value.
+///
+fn set_default_list(list: Arc<TodoList>) {
+ *DEFAULT_LIST.write().unwrap() = Some(list);
+}
+
+/// Create a new TodoEntry from the given string.
+///
+fn create_entry_with<S: Into<String>>(item: S) -> Result<TodoEntry> {
+ let text = item.into();
+ if text.is_empty() {
+ return Err(TodoError::EmptyString(
+ "Cannot add empty string as entry".to_string(),
+ ));
+ }
+ Ok(TodoEntry { text })
+}
+
+type Result<T, E = TodoError> = std::result::Result<T, E>;
+
+// A simple Todolist.
+// UniFFI requires that we use interior mutability for managing mutable state, so we wrap our `Vec` in a RwLock.
+// (A Mutex would also work, but a RwLock is more appropriate for this use-case, so we use it).
+#[derive(Debug)]
+pub struct TodoList {
+ items: RwLock<Vec<String>>,
+}
+
+impl TodoList {
+ fn new() -> Self {
+ Self {
+ items: RwLock::new(Vec::new()),
+ }
+ }
+
+ fn add_item<S: Into<String>>(&self, item: S) -> Result<()> {
+ let item = item.into();
+ if item.is_empty() {
+ return Err(TodoError::EmptyString(
+ "Cannot add empty string as item".to_string(),
+ ));
+ }
+ let mut items = self.items.write().unwrap();
+ if items.contains(&item) {
+ return Err(TodoError::DuplicateTodo);
+ }
+ items.push(item);
+ Ok(())
+ }
+
+ fn get_last(&self) -> Result<String> {
+ let items = self.items.read().unwrap();
+ items.last().cloned().ok_or(TodoError::EmptyTodoList)
+ }
+
+ fn get_first(&self) -> Result<String> {
+ let items = self.items.read().unwrap();
+ items.first().cloned().ok_or(TodoError::EmptyTodoList)
+ }
+
+ fn add_entries(&self, entries: Vec<TodoEntry>) {
+ let mut items = self.items.write().unwrap();
+ items.extend(entries.into_iter().map(|e| e.text))
+ }
+
+ fn add_entry(&self, entry: TodoEntry) -> Result<()> {
+ self.add_item(entry.text)
+ }
+
+ fn add_items<S: Into<String>>(&self, items: Vec<S>) {
+ let mut my_items = self.items.write().unwrap();
+ my_items.extend(items.into_iter().map(Into::into))
+ }
+
+ fn get_items(&self) -> Vec<String> {
+ let items = self.items.read().unwrap();
+ items.clone()
+ }
+
+ fn get_entries(&self) -> Vec<TodoEntry> {
+ let items = self.items.read().unwrap();
+ items
+ .iter()
+ .map(|text| TodoEntry { text: text.clone() })
+ .collect()
+ }
+
+ fn get_last_entry(&self) -> Result<TodoEntry> {
+ let text = self.get_last()?;
+ Ok(TodoEntry { text })
+ }
+
+ fn clear_item<S: Into<String>>(&self, item: S) -> Result<()> {
+ let item = item.into();
+ let mut items = self.items.write().unwrap();
+ let idx = items
+ .iter()
+ .position(|s| s == &item)
+ .ok_or(TodoError::TodoDoesNotExist)?;
+ items.remove(idx);
+ Ok(())
+ }
+
+ fn make_default(self: Arc<Self>) {
+ set_default_list(self);
+ }
+}
+
+include!(concat!(env!("OUT_DIR"), "/todolist.uniffi.rs"));
diff --git a/third_party/rust/uniffi-example-todolist/src/todolist.udl b/third_party/rust/uniffi-example-todolist/src/todolist.udl
new file mode 100644
index 0000000000..5c923314cd
--- /dev/null
+++ b/third_party/rust/uniffi-example-todolist/src/todolist.udl
@@ -0,0 +1,38 @@
+namespace todolist {
+ TodoList? get_default_list();
+ undefined set_default_list(TodoList list);
+
+ [Throws=TodoError]
+ TodoEntry create_entry_with(string todo);
+};
+
+dictionary TodoEntry {
+ string text;
+};
+
+[Error]
+enum TodoError {
+ "TodoDoesNotExist", "EmptyTodoList", "DuplicateTodo", "EmptyString", "DeligatedError"
+};
+
+interface TodoList {
+ constructor();
+ [Throws=TodoError]
+ void add_item(string todo);
+ [Throws=TodoError]
+ void add_entry(TodoEntry entry);
+ sequence<TodoEntry> get_entries();
+ sequence<string> get_items();
+ void add_entries(sequence<TodoEntry> entries);
+ void add_items(sequence<string> items);
+ [Throws=TodoError]
+ TodoEntry get_last_entry();
+ [Throws=TodoError]
+ string get_last();
+ [Throws=TodoError]
+ string get_first();
+ [Throws=TodoError]
+ void clear_item(string todo);
+ [Self=ByArc]
+ undefined make_default();
+};
diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.kts b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.kts
new file mode 100644
index 0000000000..bb2b292224
--- /dev/null
+++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.kts
@@ -0,0 +1,83 @@
+import uniffi.todolist.*
+
+val todo = TodoList()
+
+// This throws an exception:
+try {
+ todo.getLast()
+ throw RuntimeException("Should have thrown a TodoError!")
+} catch (e: TodoException.EmptyTodoList) {
+ // It's okay, we don't have any items yet!
+}
+
+try {
+ createEntryWith("")
+ throw RuntimeException("Should have thrown a TodoError!")
+} catch (e: TodoException) {
+ // It's okay, the string was empty!
+ assert(e is TodoException.EmptyString)
+ assert(e !is TodoException.EmptyTodoList)
+}
+
+todo.addItem("Write strings support")
+
+assert(todo.getLast() == "Write strings support")
+
+todo.addItem("Write tests for strings support")
+
+assert(todo.getLast() == "Write tests for strings support")
+
+val entry = createEntryWith("Write bindings for strings as record members")
+
+todo.addEntry(entry)
+assert(todo.getLast() == "Write bindings for strings as record members")
+assert(todo.getLastEntry().text == "Write bindings for strings as record members")
+
+todo.addItem("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+assert(todo.getLast() == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+
+val entry2 = TodoEntry("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+todo.addEntry(entry2)
+assert(todo.getLastEntry().text == "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+
+assert(todo.getEntries().size == 5)
+
+todo.addEntries(listOf(TodoEntry("foo"), TodoEntry("bar")))
+assert(todo.getEntries().size == 7)
+assert(todo.getLastEntry().text == "bar")
+
+todo.addItems(listOf("bobo", "fofo"))
+assert(todo.getItems().size == 9)
+assert(todo.getItems()[7] == "bobo")
+
+assert(getDefaultList() == null)
+
+// Note that each individual object instance needs to be explicitly destroyed,
+// either by using the `.use` helper or explicitly calling its `.destroy` method.
+// Failure to do so will leak the underlying Rust object.
+TodoList().use { todo2 ->
+ setDefaultList(todo)
+ getDefaultList()!!.use { default ->
+ assert(todo.getEntries() == default.getEntries())
+ assert(todo2.getEntries() != default.getEntries())
+ }
+
+ todo2.makeDefault()
+ getDefaultList()!!.use { default ->
+ assert(todo.getEntries() != default.getEntries())
+ assert(todo2.getEntries() == default.getEntries())
+ }
+
+ todo.addItem("Test liveness after being demoted from default")
+ assert(todo.getLast() == "Test liveness after being demoted from default")
+
+ todo2.addItem("Test shared state through local vs default reference")
+ getDefaultList()!!.use { default ->
+ assert(default.getLast() == "Test shared state through local vs default reference")
+ }
+}
+
+// Ensure the kotlin version of deinit doesn't crash, and is idempotent.
+todo.destroy()
+todo.destroy()
+
diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py
new file mode 100644
index 0000000000..017e999fb2
--- /dev/null
+++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py
@@ -0,0 +1,44 @@
+from todolist import *
+
+todo = TodoList()
+
+entry = TodoEntry("Write bindings for strings in records")
+
+todo.add_item("Write python bindings")
+
+assert(todo.get_last() == "Write python bindings")
+
+todo.add_item("Write tests for bindings")
+
+assert(todo.get_last() == "Write tests for bindings")
+
+todo.add_entry(entry)
+
+assert(todo.get_last() == "Write bindings for strings in records")
+assert(todo.get_last_entry().text == "Write bindings for strings in records")
+
+todo.add_item("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+assert(todo.get_last() == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+
+entry2 = TodoEntry("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+todo.add_entry(entry2)
+assert(todo.get_last_entry().text == "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+
+todo2 = TodoList()
+assert(todo != todo2)
+assert(todo is not todo2)
+
+assert(get_default_list() is None)
+
+set_default_list(todo)
+assert(todo.get_items() == get_default_list().get_items())
+
+todo2.make_default()
+assert(todo2.get_items() == get_default_list().get_items())
+
+todo.add_item("Test liveness after being demoted from default")
+assert(todo.get_last() == "Test liveness after being demoted from default")
+
+todo2.add_item("Test shared state through local vs default reference")
+assert(get_default_list().get_last() == "Test shared state through local vs default reference")
+
diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb
new file mode 100644
index 0000000000..d9e04f92e7
--- /dev/null
+++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'todolist'
+
+include Test::Unit::Assertions
+include Todolist
+
+todo = TodoList.new
+entry = TodoEntry.new 'Write bindings for strings in records'
+
+todo.add_item('Write ruby bindings')
+
+assert_equal todo.get_last, 'Write ruby bindings'
+
+todo.add_item('Write tests for bindings')
+
+assert_equal todo.get_last, 'Write tests for bindings'
+
+todo.add_entry(entry)
+
+assert_equal todo.get_last, 'Write bindings for strings in records'
+assert_equal todo.get_last_entry.text, 'Write bindings for strings in records'
+
+todo.add_item("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+assert_equal todo.get_last, "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣"
+
+entry2 = TodoEntry.new("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+todo.add_entry(entry2)
+assert_equal todo.get_last_entry.text, "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣"
+
+todo2 = TodoList.new
+assert todo2.get_items != todo.get_items
+
+assert Todolist.get_default_list == nil
+
+Todolist.set_default_list todo
+assert todo.get_items == Todolist.get_default_list.get_items
+
+todo2.make_default
+assert todo2.get_items == Todolist.get_default_list.get_items
+
+todo.add_item "Test liveness after being demoted from default"
+assert todo.get_last == "Test liveness after being demoted from default"
+
+todo2.add_item "Test shared state through local vs default reference"
+assert Todolist.get_default_list.get_last == "Test shared state through local vs default reference" \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.swift b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.swift
new file mode 100644
index 0000000000..6ce72cadb2
--- /dev/null
+++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.swift
@@ -0,0 +1,69 @@
+import todolist
+
+
+let todo = TodoList()
+do {
+ let _ = try todo.getLast()
+ fatalError("Should have thrown an EmptyTodoList error!")
+} catch TodoError.EmptyTodoList{
+ //It's okay! There are not todos!
+}
+try! todo.addItem(todo: "Write swift bindings")
+assert( try! todo.getLast() == "Write swift bindings")
+
+try! todo.addItem(todo: "Write tests for bindings")
+assert(try! todo.getLast() == "Write tests for bindings")
+
+let entry = TodoEntry(text: "Write bindings for strings as record members")
+try! todo.addEntry(entry: entry)
+assert(try! todo.getLast() == "Write bindings for strings as record members")
+
+try! todo.addItem(todo: "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+assert(try! todo.getLast() == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
+
+do {
+ let _ = try createEntryWith(todo: "")
+ fatalError("Should have thrown an EmptyString error!")
+} catch TodoError.EmptyString {
+ // It's okay! It was an empty string
+}
+
+let entry2 = TodoEntry(text: "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+try! todo.addEntry(entry: entry2)
+assert(try! todo.getLastEntry() == entry2)
+
+assert(todo.getEntries().count == 5)
+
+todo.addEntries(entries: [TodoEntry(text: "foo"), TodoEntry(text: "bar")])
+assert(todo.getEntries().count == 7)
+assert(todo.getItems().count == 7)
+assert(try! todo.getLast() == "bar")
+
+todo.addItems(items: ["bobo", "fofo"])
+assert(todo.getItems().count == 9)
+assert(todo.getItems()[7] == "bobo")
+
+// Ensure deinit doesn't crash.
+for _ in 0..<10 {
+ let list = TodoList()
+ try! list.addItem(todo: "todo")
+}
+
+let todo2 = TodoList()
+
+assert(getDefaultList() == nil)
+
+setDefaultList(list: todo)
+assert(todo.getItems() == getDefaultList()!.getItems())
+assert(todo2.getItems() != getDefaultList()!.getItems())
+
+todo2.makeDefault()
+assert(todo.getItems() != getDefaultList()!.getItems())
+assert(todo2.getItems() == getDefaultList()!.getItems())
+
+try! todo.addItem(todo: "Test liveness after being demoted from default")
+assert(try! todo.getLast() == "Test liveness after being demoted from default")
+
+try! todo2.addItem(todo: "Test shared state through local vs default reference")
+assert(try! getDefaultList()!.getLast() == "Test shared state through local vs default reference")
+
diff --git a/third_party/rust/uniffi-example-todolist/tests/test_generated_bindings.rs b/third_party/rust/uniffi-example-todolist/tests/test_generated_bindings.rs
new file mode 100644
index 0000000000..cefdbfe1dc
--- /dev/null
+++ b/third_party/rust/uniffi-example-todolist/tests/test_generated_bindings.rs
@@ -0,0 +1,6 @@
+uniffi::build_foreign_language_testcases!(
+ "tests/bindings/test_todolist.kts",
+ "tests/bindings/test_todolist.swift",
+ "tests/bindings/test_todolist.rb",
+ "tests/bindings/test_todolist.py"
+);
diff --git a/third_party/rust/uniffi/.cargo-checksum.json b/third_party/rust/uniffi/.cargo-checksum.json
new file mode 100644
index 0000000000..5b1e3bd47e
--- /dev/null
+++ b/third_party/rust/uniffi/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"aa6288a6de59c4f25216578c7b27a51fbe98fb41c22c88f561dd171dfd905bd6","release.toml":"183ddd766a32826c448f33db7420abf2332b327f563e5de771c9e1733ef93e7a","src/cli.rs":"d8c8ebf37db05b31c33190de80923fde91c96c520a09c62ff6754b68a26dcf08","src/lib.rs":"08c447841ed496727112ccc4f6469bf00c50109575281c3b64f5dced65bbc119","tests/ui/proc_macro_arc.rs":"d766dffee3fe6a93522d40f44a7f15592db141fd674034fa5f016e06f510e87b","tests/ui/proc_macro_arc.stderr":"32096490256b658d42b8187547d07473439329e62676aa9b1ec6dbeef401ad7a","tests/ui/version_mismatch.rs":"16ea359e5853517ee0d0704c015ae8c825533109fbefd715130d0f4a51f15898","tests/ui/version_mismatch.stderr":"21dcb836253312ba8e3a0502cce6ff279818aaaadcea9628a41b196e0c8c94b6"},"package":"f71cc01459bc34cfe43fabf32b39f1228709bc6db1b3a664a92940af3d062376"} \ No newline at end of file
diff --git a/third_party/rust/uniffi/Cargo.toml b/third_party/rust/uniffi/Cargo.toml
new file mode 100644
index 0000000000..ddaa6c536c
--- /dev/null
+++ b/third_party/rust/uniffi/Cargo.toml
@@ -0,0 +1,68 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi"
+version = "0.23.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+description = "a multi-language bindings generator for rust"
+homepage = "https://mozilla.github.io/uniffi-rs"
+documentation = "https://mozilla.github.io/uniffi-rs"
+keywords = [
+ "ffi",
+ "bindgen",
+]
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/uniffi-rs"
+
+[dependencies.anyhow]
+version = "1"
+
+[dependencies.camino]
+version = "1.0.8"
+optional = true
+
+[dependencies.clap]
+version = "3.1"
+features = [
+ "cargo",
+ "std",
+ "derive",
+]
+optional = true
+
+[dependencies.uniffi_bindgen]
+version = "=0.23.0"
+optional = true
+
+[dependencies.uniffi_build]
+version = "=0.23.0"
+optional = true
+
+[dependencies.uniffi_core]
+version = "=0.23.0"
+
+[dependencies.uniffi_macros]
+version = "=0.23.0"
+
+[dev-dependencies.trybuild]
+version = "1"
+
+[features]
+bindgen-tests = ["dep:uniffi_bindgen"]
+build = ["dep:uniffi_build"]
+cli = [
+ "dep:uniffi_bindgen",
+ "dep:clap",
+ "dep:camino",
+]
+default = []
diff --git a/third_party/rust/uniffi/release.toml b/third_party/rust/uniffi/release.toml
new file mode 100644
index 0000000000..3decae3abf
--- /dev/null
+++ b/third_party/rust/uniffi/release.toml
@@ -0,0 +1,14 @@
+# Note that this `release.toml` exists to capture things that must only be
+# done once for `cargo release-uniffi`.
+#
+# [../uniffi_core/release.toml](../uniffi_core/release.toml) captures things that must only be done for `cargo release-backend-crates`
+#
+# All other config exists in [../release.toml](../release.toml).
+
+tag = true
+
+# This is how we manage the sections in CHANGELOG.md
+pre-release-replacements = [
+ {file="../CHANGELOG.md", search="\\[\\[UnreleasedUniFFIVersion\\]\\]", replace="v{{version}}", exactly=2},
+ {file="../CHANGELOG.md", search="\\[\\[NextUnreleasedUniFFIVersion\\]\\]", replace="[[UnreleasedUniFFIVersion]]", exactly=1},
+]
diff --git a/third_party/rust/uniffi/src/cli.rs b/third_party/rust/uniffi/src/cli.rs
new file mode 100644
index 0000000000..705ea9f453
--- /dev/null
+++ b/third_party/rust/uniffi/src/cli.rs
@@ -0,0 +1,106 @@
+/* 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 camino::Utf8PathBuf;
+use clap::{Parser, Subcommand};
+
+// Structs to help our cmdline parsing. Note that docstrings below form part
+// of the "help" output.
+
+/// Scaffolding and bindings generator for Rust
+#[derive(Parser)]
+#[clap(name = "uniffi-bindgen")]
+#[clap(version = clap::crate_version!())]
+#[clap(propagate_version = true)]
+struct Cli {
+ #[clap(subcommand)]
+ command: Commands,
+}
+
+#[derive(Subcommand)]
+enum Commands {
+ /// Generate foreign language bindings
+ Generate {
+ /// Foreign language(s) for which to build bindings.
+ #[clap(long, short, possible_values = &["kotlin", "python", "swift", "ruby"])]
+ language: Vec<String>,
+
+ /// Directory in which to write generated files. Default is same folder as .udl file.
+ #[clap(long, short)]
+ out_dir: Option<Utf8PathBuf>,
+
+ /// Do not try to format the generated bindings.
+ #[clap(long, short)]
+ no_format: bool,
+
+ /// Path to the optional uniffi config file. If not provided, uniffi-bindgen will try to guess it from the UDL's file location.
+ #[clap(long, short)]
+ config: Option<Utf8PathBuf>,
+
+ /// Extract proc-macro metadata from a native lib (cdylib or staticlib) for this crate.
+ #[clap(long)]
+ lib_file: Option<Utf8PathBuf>,
+
+ /// Path to the UDL file.
+ udl_file: Utf8PathBuf,
+ },
+
+ /// Generate Rust scaffolding code
+ Scaffolding {
+ /// Directory in which to write generated files. Default is same folder as .udl file.
+ #[clap(long, short)]
+ out_dir: Option<Utf8PathBuf>,
+
+ /// Path to the optional uniffi config file. If not provided, uniffi-bindgen will try to guess it from the UDL's file location.
+ #[clap(long, short)]
+ config: Option<Utf8PathBuf>,
+
+ /// Do not try to format the generated bindings.
+ #[clap(long, short)]
+ no_format: bool,
+
+ /// Path to the UDL file.
+ udl_file: Utf8PathBuf,
+ },
+
+ /// Print the JSON representation of the interface from a dynamic library
+ PrintJson {
+ /// Path to the library file (.so, .dll, .dylib, or .a)
+ path: Utf8PathBuf,
+ },
+}
+
+pub fn run_main() -> anyhow::Result<()> {
+ let cli = Cli::parse();
+ match &cli.command {
+ Commands::Generate {
+ language,
+ out_dir,
+ no_format,
+ config,
+ lib_file,
+ udl_file,
+ } => uniffi_bindgen::generate_bindings(
+ udl_file,
+ config.as_deref(),
+ language.iter().map(String::as_str).collect(),
+ out_dir.as_deref(),
+ lib_file.as_deref(),
+ !no_format,
+ ),
+ Commands::Scaffolding {
+ out_dir,
+ config,
+ no_format,
+ udl_file,
+ } => uniffi_bindgen::generate_component_scaffolding(
+ udl_file,
+ config.as_deref(),
+ out_dir.as_deref(),
+ !no_format,
+ ),
+ Commands::PrintJson { path } => uniffi_bindgen::print_json(path),
+ }?;
+ Ok(())
+}
diff --git a/third_party/rust/uniffi/src/lib.rs b/third_party/rust/uniffi/src/lib.rs
new file mode 100644
index 0000000000..bb308c55f2
--- /dev/null
+++ b/third_party/rust/uniffi/src/lib.rs
@@ -0,0 +1,37 @@
+/* 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/. */
+
+/// Reexport items from other uniffi creates
+pub use uniffi_core::*;
+pub use uniffi_macros::{export, include_scaffolding, Enum, Error, Object, Record};
+#[cfg(feature = "cli")]
+mod cli;
+#[cfg(feature = "bindgen-tests")]
+pub use uniffi_bindgen::bindings::kotlin::run_test as kotlin_run_test;
+#[cfg(feature = "bindgen-tests")]
+pub use uniffi_bindgen::bindings::python::run_test as python_run_test;
+#[cfg(feature = "bindgen-tests")]
+pub use uniffi_bindgen::bindings::ruby::run_test as ruby_run_test;
+#[cfg(feature = "bindgen-tests")]
+pub use uniffi_bindgen::bindings::swift::run_test as swift_run_test;
+#[cfg(feature = "cli")]
+pub use uniffi_bindgen::{generate_bindings, generate_component_scaffolding, print_json};
+#[cfg(feature = "build")]
+pub use uniffi_build::generate_scaffolding;
+#[cfg(feature = "bindgen-tests")]
+pub use uniffi_macros::build_foreign_language_testcases;
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn trybuild_ui_tests() {
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/ui/*.rs");
+ }
+}
+
+#[cfg(feature = "cli")]
+pub fn uniffi_bindgen_main() {
+ cli::run_main().unwrap();
+}
diff --git a/third_party/rust/uniffi/tests/ui/proc_macro_arc.rs b/third_party/rust/uniffi/tests/ui/proc_macro_arc.rs
new file mode 100644
index 0000000000..bdffc020e0
--- /dev/null
+++ b/third_party/rust/uniffi/tests/ui/proc_macro_arc.rs
@@ -0,0 +1,25 @@
+use std::sync::Arc;
+
+fn main() {}
+
+pub struct Foo;
+
+#[uniffi::export]
+fn make_foo() -> Arc<Foo> {
+ Arc::new(Foo)
+}
+
+mod child {
+ use std::sync::Arc;
+
+ enum Foo {}
+
+ #[uniffi::export]
+ fn take_foo(foo: Arc<Foo>) {
+ match &*foo {}
+ }
+}
+
+mod uniffi_types {
+ pub use super::Foo;
+}
diff --git a/third_party/rust/uniffi/tests/ui/proc_macro_arc.stderr b/third_party/rust/uniffi/tests/ui/proc_macro_arc.stderr
new file mode 100644
index 0000000000..9e89f7bccf
--- /dev/null
+++ b/third_party/rust/uniffi/tests/ui/proc_macro_arc.stderr
@@ -0,0 +1,31 @@
+error[E0271]: type mismatch resolving `<Arc<child::Foo> as child::_::_::{closure#0}::TypeEq>::This == Arc<Foo>`
+ --> tests/ui/proc_macro_arc.rs:18:22
+ |
+18 | fn take_foo(foo: Arc<Foo>) {
+ | ^^^^^^^^ type mismatch resolving `<Arc<child::Foo> as child::_::_::{closure#0}::TypeEq>::This == Arc<Foo>`
+ |
+note: expected this to be `Arc<Foo>`
+ --> tests/ui/proc_macro_arc.rs:18:22
+ |
+18 | fn take_foo(foo: Arc<Foo>) {
+ | ^^^
+ = note: enum `child::Foo` and struct `Foo` have similar names, but are actually distinct types
+note: enum `child::Foo` is defined in module `crate::child` of the current crate
+ --> tests/ui/proc_macro_arc.rs:15:5
+ |
+15 | enum Foo {}
+ | ^^^^^^^^
+note: struct `Foo` is defined in module `crate` of the current crate
+ --> tests/ui/proc_macro_arc.rs:5:1
+ |
+5 | pub struct Foo;
+ | ^^^^^^^^^^^^^^
+note: required by a bound in `child::_::_::{closure#0}::assert_type_eq_all`
+ --> tests/ui/proc_macro_arc.rs:18:22
+ |
+18 | fn take_foo(foo: Arc<Foo>) {
+ | ^^^
+ | |
+ | required by a bound in this
+ | required by this bound in `child::_::_::{closure#0}::assert_type_eq_all`
+ = note: this error originates in the macro `::uniffi::deps::static_assertions::assert_type_eq_all` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/third_party/rust/uniffi/tests/ui/version_mismatch.rs b/third_party/rust/uniffi/tests/ui/version_mismatch.rs
new file mode 100644
index 0000000000..6a7edb891d
--- /dev/null
+++ b/third_party/rust/uniffi/tests/ui/version_mismatch.rs
@@ -0,0 +1,4 @@
+// This should fail with a version mismatch.
+uniffi::assert_compatible_version!("0.0.1"); // An error message would go here.
+
+fn main() {}
diff --git a/third_party/rust/uniffi/tests/ui/version_mismatch.stderr b/third_party/rust/uniffi/tests/ui/version_mismatch.stderr
new file mode 100644
index 0000000000..bc30714099
--- /dev/null
+++ b/third_party/rust/uniffi/tests/ui/version_mismatch.stderr
@@ -0,0 +1,7 @@
+error[E0080]: evaluation of constant value failed
+ --> tests/ui/version_mismatch.rs:2:1
+ |
+2 | uniffi::assert_compatible_version!("0.0.1"); // An error message would go here.
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute `0_usize - 1_usize`, which would overflow
+ |
+ = note: this error originates in the macro `uniffi::deps::static_assertions::const_assert` which comes from the expansion of the macro `uniffi::assert_compatible_version` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/third_party/rust/uniffi_bindgen/.cargo-checksum.json b/third_party/rust/uniffi_bindgen/.cargo-checksum.json
new file mode 100644
index 0000000000..2032b8dcaa
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"5bba6c7346e8263e0dac4e8a346cc96c89ed14d0e329ebb993e555ad3008e11c","askama.toml":"1a245b7803adca782837e125c49100147d2de0d5a1c949ff95e91af1701f6058","src/backend/config.rs":"4861dbf251dbb10beb1ed7e3eea7d79499a0de1cd9ce9ee8381a0e729c097dea","src/backend/declarations.rs":"12b8d6e651f84634de5cd02a47601965df7438f64f1a73f136bd89b6b5d515cf","src/backend/mod.rs":"899cd3b816d0467e35789b92ac3b8d5910f6dab98156d405db4803da8721fd36","src/backend/oracle.rs":"fefe7c77fe807cd2b6cc7e27e9d33c02a6f055a0168487fe98a9e53c979fab7c","src/backend/types.rs":"16a99765b562becb47a8057897b5de810d432690c8db7f26ba646aff1f07e532","src/bindings/kotlin/gen_kotlin/callback_interface.rs":"b7fe795670830f3aa8a955c787b1127fe68313ee751013948527948fe5526b01","src/bindings/kotlin/gen_kotlin/compounds.rs":"d1e9a4237ff2ff711a3eae7a564c39e26f598c156ebfd34c0f04879e3533dd4f","src/bindings/kotlin/gen_kotlin/custom.rs":"4176f6ed5f66504f8fd981198bbfbae795dab5ef0d0281881d19b697f5560c44","src/bindings/kotlin/gen_kotlin/enum_.rs":"f85ae8dcb55c8f274139bf321af0ba237ae69894165ad6bd693d793f58af8e5e","src/bindings/kotlin/gen_kotlin/error.rs":"867f583aea5da7aabeb9b6d2544d7e52984cdea4aa008ce5f2ec941074735e1a","src/bindings/kotlin/gen_kotlin/external.rs":"1f7e91d7439891fe3c403274e35880ee4fc3a0da555510bdfa23c1ed2bbd8020","src/bindings/kotlin/gen_kotlin/miscellany.rs":"644ee5bb1f3619be5a36b2b3900af554ea38073cd054004f421e69c3cb8d50bc","src/bindings/kotlin/gen_kotlin/mod.rs":"01b50b6c6282023b3160be2475700b0e8a4065ed3a93e01159eda5846fd2cd1d","src/bindings/kotlin/gen_kotlin/object.rs":"6478a3e9d5e66186521730d0d481abd1ee4f123050ea050ac5a483842f08b003","src/bindings/kotlin/gen_kotlin/primitives.rs":"9b5114201f97b1e400825aef47b33ea9718ee89b161702093142f73ddf42b801","src/bindings/kotlin/gen_kotlin/record.rs":"7961fcfbec5ebf8fc010b564ea4bd59402c919f6922898d48226d8c995569dd7","src/bindings/kotlin/mod.rs":"12e18af4dad5ecc2b5a9fbe6b185452ad874c707f8efcc989afad1df4e4e0502","src/bindings/kotlin/templates/BooleanHelper.kt":"28e8a5088c8d58c9bfdbc575af8d8725060521fdd7d092684a8044b24ae567c7","src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt":"6ede374b0fcbb3bcc939894e6f4729b3bec7ec7356831a60fba96ca38dc91aa8","src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt":"f2f767a256dcb2cb62b5809792f7335e014c10b6a25351028a2845dac2387bb5","src/bindings/kotlin/templates/CustomTypeTemplate.kt":"5d4dacf29e89bcdcc46d155d993e6059d2df704e775dc853469310198253b231","src/bindings/kotlin/templates/DurationHelper.kt":"414a98161538a26f3a9b357353270c1f245ad6ceed99496aca7162cf473a92fd","src/bindings/kotlin/templates/EnumTemplate.kt":"4cacb93506f46f09e80e695f16da3d706d52a6e3c0b282158e2c466cc7b10962","src/bindings/kotlin/templates/ErrorTemplate.kt":"a5ec2bdfc026838e1096dbf3301f21aa4ea22e8c93458d45bb1c8c7b9ee1fc5b","src/bindings/kotlin/templates/ExternalTypeTemplate.kt":"155df3cc8fce46ef43948da9a9062eb6516f4966d4969e278df8ed497cc6d657","src/bindings/kotlin/templates/FfiConverterTemplate.kt":"aa22962aaa9f641d48ccf44cb56d9f8a7736cbfaa01e1a1656662cfe5dd5c1d7","src/bindings/kotlin/templates/Float32Helper.kt":"662d95af3b629d143fb4d47cb7e9aa26ed28a5f3846de0341e28b0f9fb08bc25","src/bindings/kotlin/templates/Float64Helper.kt":"a77d099fa7d91e8702c1700e7949ffb6aaba9c6e8041ff48bab34b8e1fc9a0aa","src/bindings/kotlin/templates/Helpers.kt":"46c07798a26b53b06405c8bbbf86e3fcf38fadc1484ea04ce9d482defea89288","src/bindings/kotlin/templates/Int16Helper.kt":"7f83c4a48e1f3b2a59a3ca6a2662be8bc9baf3a5a748b31223cb3f51721ef249","src/bindings/kotlin/templates/Int32Helper.kt":"e02e4702175554b09fd2dd6ac3089dcd2c395f08ec60e762159566a9c9889450","src/bindings/kotlin/templates/Int64Helper.kt":"7a6fd6ca486852c89399c699935a9dfa1c32b9356d9a965cfde532581f05d9fa","src/bindings/kotlin/templates/Int8Helper.kt":"0554545494b6b9a76ce94f9c1723f8cf4230a13076feb75d620b1c9ca1ac4668","src/bindings/kotlin/templates/MapTemplate.kt":"399569d6443e8ad01e2deb95d78d8d2d15bf8eccee8be4fbe9ce4b8ebc0a6101","src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt":"1eba4e77381155c2c96a6af2ef30fca881ade4957852f730fd10aa4f3d9cd3c4","src/bindings/kotlin/templates/ObjectRuntime.kt":"7f38f54a0c889d7534d23afdace6b87b6ced5c024a36b5450078a06e071caad8","src/bindings/kotlin/templates/ObjectTemplate.kt":"7e32d92ec6116da9b7d7f278b15333a2fd2a82dde8c21534e3f6fe371d19f333","src/bindings/kotlin/templates/OptionalTemplate.kt":"5f9f2c1baa829ed3c9b61c3edb0f1fccf5ea3cccc052a69cf8966715d8fcf149","src/bindings/kotlin/templates/RecordTemplate.kt":"193ecdad9322fb5483b95bf2a259270a9b22ba054790794e9abb3fd219196bc5","src/bindings/kotlin/templates/RustBufferTemplate.kt":"415637f80a78c12b3d00db063c14a7ab5c61b098bdb1fc81a0be8bae9511776b","src/bindings/kotlin/templates/SequenceTemplate.kt":"786693b20c608a4f059b91df115278f5f12122b4c14a2e7ce18b6fc9b22b1296","src/bindings/kotlin/templates/StringHelper.kt":"060839663580d8199671b7c3bb3dce5e9106aa766ce2c6e0afc2d2bd788a1d83","src/bindings/kotlin/templates/TimestampHelper.kt":"353c2890f06ad6dda238e9aebb4bdff7bb838e17e46abf351ed3ff1fbc4e6580","src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt":"09b8bb5ffea7075518ee657f83017d44ff0cbf584fd85676b082199139c9be09","src/bindings/kotlin/templates/Types.kt":"1041a3ecfeb3be10423fe01f351fc8e1e5cf6e860ee5610596537905e5130f5a","src/bindings/kotlin/templates/UInt16Helper.kt":"e84a1f30a5a899ba2c5db614d3f3c74f25bccf6dd99bf68b8830829332d051e9","src/bindings/kotlin/templates/UInt32Helper.kt":"7cdf08cc580046935f27ba07b53685968608a102e0a6be305111037c63d7ddf8","src/bindings/kotlin/templates/UInt64Helper.kt":"fd7baacbf3ab6202ff83edcc66e5f7beb11a10053ba66d0b49547616cc7cbe1f","src/bindings/kotlin/templates/UInt8Helper.kt":"bbf5a6d66c995aea9fe2fa9840c6bfa78b03520a09469b984f0e1d43191e453a","src/bindings/kotlin/templates/macros.kt":"53c6a41afc03d1501bf5301e317994c314ee492827e93f3d39c7a1e49acc9660","src/bindings/kotlin/templates/wrapper.kt":"03e0e56b49036b144acc7044e9ab94679a6a76c3590f64df47f6eb9b589692c3","src/bindings/kotlin/test.rs":"2676303bc721561b5efb02966184905a4d75105ea6abf6825fd1297e66190687","src/bindings/mod.rs":"b156e444c4a55cdd17f9e700e393ef2464f59219121821163a4f9bba02afe474","src/bindings/python/gen_python/callback_interface.rs":"e3ffb8ba1aa8ac8ddcfff6554e7ec0d2d695d12955a98a04d114d2d6ca5dacc8","src/bindings/python/gen_python/compounds.rs":"e406c773c3b66368ea74973df742cdcba3014521812048f15de7c0e783eedfaf","src/bindings/python/gen_python/custom.rs":"33756f6bdafbd9b1a3a439c1cced3c83dc1fdb82b5ec235c064e69670ea6086c","src/bindings/python/gen_python/enum_.rs":"634c8406b07cbd24ea4f71cae4c971169e4989ce8019188f4bcd515bc3c77c5d","src/bindings/python/gen_python/error.rs":"d1a3b3edb91d9502064463cc3324770a1a6e0f234d6b49b5d7c43636bfe81d59","src/bindings/python/gen_python/external.rs":"99c1822466045f721fb1def8228b1dcefaabdc3615d005b3360e4a578ec45bfb","src/bindings/python/gen_python/miscellany.rs":"f3898b75cf494b39662d886eb78d9cc06685650265fc8a1e87e5de67baa342ae","src/bindings/python/gen_python/mod.rs":"2e6ce9aa3fc83e909f54cf3e33999d72c128abe06554b5973569d77fa76cbd3f","src/bindings/python/gen_python/object.rs":"b4d3d8a935d4acd689cf1f3857d461dbab3a51c7c8bd821890198fb58ef3a63f","src/bindings/python/gen_python/primitives.rs":"dff24f2c224723258fcad1a867a5cf984eea01dd2f32b8c1067ad562e1270826","src/bindings/python/gen_python/record.rs":"f961dfe8dd1e5caa633754de7ecc684c2211c9b6633a5e6beb6053e60500b9f2","src/bindings/python/mod.rs":"9e6b347e8d5ab165083a6e241d28763cffb99d34d76b6b245268f265b8585355","src/bindings/python/templates/BooleanHelper.py":"d384ffeefcb5982c4875e819d06e919a295eaa9ff57735e6fef0801bb810d5c9","src/bindings/python/templates/CallbackInterfaceRuntime.py":"7ffef485fc008e2d9efcd07326102f300bd4673b4351353e9e2908355936c3d7","src/bindings/python/templates/CallbackInterfaceTemplate.py":"6443f9494dd84752bd8f64e607e1fa68fd53ce57c3078563ce1d05e5e912b89e","src/bindings/python/templates/CustomType.py":"db3309b3f944fb813d4cef47a04d67a86f824183f7e31289184683155fd985d9","src/bindings/python/templates/DurationHelper.py":"179c14dccd8cc7dc9791f896414f0b5d124ec116eb78173371bd8a0743208da1","src/bindings/python/templates/EnumTemplate.py":"825ca373286f1b3b252b8a1263e29c9e2f0d3e170ceb364bd6c28d6c4595597b","src/bindings/python/templates/ErrorTemplate.py":"b6b1b0d3fc074a7c3ba7db394c5d6cb3279b0fc0e6a331154303c2c6c907458b","src/bindings/python/templates/ExternalTemplate.py":"3b8c035dca67f1e2699481498ec02a0c92fe41d666409140e282bfeaf14889a1","src/bindings/python/templates/Float32Helper.py":"7dbc51889cff47ebf1600fad490849e4a87cac4fc0d1756eebd21609eb80b4a9","src/bindings/python/templates/Float64Helper.py":"ba9f334d1339b6eaedcacc1e35068939727170a684f41dec9ee04762ed98cec1","src/bindings/python/templates/Helpers.py":"3a4999b02cbd2439814b28318e9a1ae07dbd607931d907f6c17b81e92f4354a1","src/bindings/python/templates/Int16Helper.py":"60c22fb8b445841ebb3c68be11b81c9eba84a680eaa0e30770953361231da9b5","src/bindings/python/templates/Int32Helper.py":"aff0a017cf767394174e46d8c4fe5a5a704a8e6384fcc38d227634dfe7826916","src/bindings/python/templates/Int64Helper.py":"6c314b91699a6c6ad7db3ef636713bc2a0af9609c82acfd6062b0588177c0026","src/bindings/python/templates/Int8Helper.py":"a6e2d121b1a6d59886fceab3949e571aba1abc06dfede52666954bf15366fb6f","src/bindings/python/templates/MapTemplate.py":"bbe609b865010b98b38c58f2d4fcc97f2adec3e931903bf67263a5e440a84400","src/bindings/python/templates/NamespaceLibraryTemplate.py":"4726dbeb61508a71153436126bc04d03af966dca4f5b37511beb8bcfb6f1f307","src/bindings/python/templates/ObjectTemplate.py":"68302f6da6e4e80fc0cc8eb4ef2d4353d19ad3e9955112a2fabec8bf1b1293c7","src/bindings/python/templates/OptionalTemplate.py":"ab6da433370ba7c1316e266247ee1b7165bc02f6f288e40a7c68806c018e3282","src/bindings/python/templates/RecordTemplate.py":"98cef2adbc2b890e5c67257fb32a57380070988ef4112408eeca85e0b87b566b","src/bindings/python/templates/RustBufferHelper.py":"11f733051e63733c637fb19c4758cb58a40d045792028465f36891f89c7c5f36","src/bindings/python/templates/RustBufferTemplate.py":"90950cfeeb7a028aac9b65aeca897b217eddcfa165a0d59e8af037e834f34146","src/bindings/python/templates/SequenceTemplate.py":"faf2b1d5272a66258972d88c29d5b527cf9e589c8399e30f7ad5a0503133ce9b","src/bindings/python/templates/StringHelper.py":"941a7ad71d9598701efa15323df93766934583a55f4266d26db31e6b744603fc","src/bindings/python/templates/TimestampHelper.py":"b412cea69117858c05bae3210d378c6296658ed02a50e87c52c392dcb62c7892","src/bindings/python/templates/TopLevelFunctionTemplate.py":"2b2883b14f324e543cfeea5293d2a4907fa9ebe93b779da7174a58929d8b0442","src/bindings/python/templates/Types.py":"65012f62eb048124efa2a200ee6d52dd9e1c4a0c1cb0d7370d1750a77fe8a958","src/bindings/python/templates/UInt16Helper.py":"06be5c9dacdf20e586f8236ed75cf2ca2470078fd8570843ea97c581b25bf860","src/bindings/python/templates/UInt32Helper.py":"41bb9bbf9b7be1060945e1267b1cc052585ec43696b1591f0ee779a0be0feaff","src/bindings/python/templates/UInt64Helper.py":"ba2825fc295a07292d9fb4aeebe74dabb9e6dbe505643e2347875ab12e511f31","src/bindings/python/templates/UInt8Helper.py":"dca5b3fc4a429fb233326224f85c4eccd3a7802ca9958ec309c7f197d59b4e3d","src/bindings/python/templates/macros.py":"0f953985263bbd7c5c0353cfc7ffb34582152e34520836a4756c5de652427fc9","src/bindings/python/templates/wrapper.py":"95570ea057b9fdd4fbc8a2fea68a24e6f06b0daf2660d21d06869b858b5a0542","src/bindings/python/test.rs":"8d3a5389b9764f7dbeb4dd9d025607868e1bcb5a2999f9abe44b144d3f55a980","src/bindings/ruby/gen_ruby/mod.rs":"c8e258096bb869abe92dc42645960aebafecac738306708eeda45bed4fb64fa7","src/bindings/ruby/gen_ruby/tests.rs":"7dcb86b08e643c43503f4cac6396833497f6988b004321c0067700ee29ffbf32","src/bindings/ruby/mod.rs":"5b7aad30caaf36502bc5965863e7e54d64253308457bd77b4142caa9423229ae","src/bindings/ruby/templates/EnumTemplate.rb":"5480edb347f5829e478d19474691babd72f37616ed846d519b5a61cb1d6cf047","src/bindings/ruby/templates/ErrorTemplate.rb":"147b2c3ff44c19624e7bf7b3a2a04b7badbba5743eaefa0d5e6c05943119c87e","src/bindings/ruby/templates/NamespaceLibraryTemplate.rb":"9b1454208bc83ef8f26aef33713d681e2284dbfea986ec0dd6c9b9c8b7d65e4a","src/bindings/ruby/templates/ObjectTemplate.rb":"0cfd9438e4821cf2164b23d748b3227a8cffbe2fab5b7eb70832228ccb628ee0","src/bindings/ruby/templates/RecordTemplate.rb":"4aeff886928ca972e5dc9b799581b30c66a6f6dce446af3285dd3ed6b422dea9","src/bindings/ruby/templates/RustBufferBuilder.rb":"42ae86a51b931f410e957232dbbc30a2bf49f6795b2c34dd618d1ea78e98f261","src/bindings/ruby/templates/RustBufferStream.rb":"147fa14a8bcb23f22df1bea6c2dabe42f5109187a117db8e982d79f83f731fcd","src/bindings/ruby/templates/RustBufferTemplate.rb":"8f37664f5436ba74ccdd801e16220f1b879d2fb56f51f5845b6047c92dc079f8","src/bindings/ruby/templates/TopLevelFunctionTemplate.rb":"88213e7e25bef664da939c04dd5621f438af735ffcb4d2d0c24a529538630069","src/bindings/ruby/templates/macros.rb":"a4c0cdd8b063ae08fd5b971fd747c6483edff92751f45a6a7458bcd85829cc00","src/bindings/ruby/templates/wrapper.rb":"542cdf46fb871e66089c9f008cf472cca411fe217d8c66a0a66776c35469aab5","src/bindings/ruby/test.rs":"efae020dd2cb26127da8a084f974e7720724d161a2c014bd1b7794a158768b33","src/bindings/swift/gen_swift/callback_interface.rs":"e331871ac6c9ac9b9533848fb6ddfcabc1e605970343cad307b6d86b72ebe96a","src/bindings/swift/gen_swift/compounds.rs":"f9e87b342f1f9a14295d87bad59d786d4c253a24d22900c2aba44816713718ae","src/bindings/swift/gen_swift/custom.rs":"45cdfa35ef7345dc353d0d2f3cebb17d726e90abdf5ef49422d6b2db65f8fd25","src/bindings/swift/gen_swift/enum_.rs":"018eea78ef85c9f8d715a5bc15c8273030d4f6ba297019949eb578c5cc6276fd","src/bindings/swift/gen_swift/error.rs":"bd95c3303e40f03a321f2cdc8e15a0251f4c7ddbc3c32c4c57eb9569db218488","src/bindings/swift/gen_swift/external.rs":"ef70de30186e07e16888b17876fcbcf440a488ac599bbe7d23606f09845d4ca1","src/bindings/swift/gen_swift/miscellany.rs":"66f16968f6cccc0b61c544e336a49b96218551731dcce566a176903c9afb3b57","src/bindings/swift/gen_swift/mod.rs":"ee7b97574a99fa712cbcbcbc077104c92cbc3b6342df0724e43311587320d880","src/bindings/swift/gen_swift/object.rs":"072a44f484cc66694c57a9fa41ba50a531c9ce19738e11ce9df17cdfc007648f","src/bindings/swift/gen_swift/primitives.rs":"9d086634994e5993c1a1ff327f637a826174b169d949cd390c71af77ceac5d3b","src/bindings/swift/gen_swift/record.rs":"48296332960be3731b9139dc664d4b8a5d56d04cffa34dc995b62cf202b4dbfd","src/bindings/swift/mod.rs":"155a5ffe5eb924471924864c83446130e6af67d51fddddda7fdfc12e369c103a","src/bindings/swift/templates/BooleanHelper.swift":"f02e391bed44ca0e03c66c4e6a1545caaae490fc72c4cf5935e66220082a8c30","src/bindings/swift/templates/BridgingHeaderTemplate.h":"93a289e393ecdbe1bf986215c3b19d2aed7677d47f79b1833ce73cf6f8762e80","src/bindings/swift/templates/CallbackInterfaceRuntime.swift":"c67193f2fc11dbf26f286439dce8f5733b6a7b8733704ac4f4a6c5605b9d5e7a","src/bindings/swift/templates/CallbackInterfaceTemplate.swift":"e1197096a99e9cfe477ba586a88c558561d94474106e011ad30671b8af78c874","src/bindings/swift/templates/CustomType.swift":"45dba980e4f1f1359f4609cb6493c613185e4fe12231baf324f34810e9500b60","src/bindings/swift/templates/DurationHelper.swift":"cbc41aaa58bda6c2313ede36a9f656a01a28f9c72aa1624e0e1c1da7b841ffb6","src/bindings/swift/templates/EnumTemplate.swift":"f1c03a389474cc3f2aa8d379f6800ff0d9df49f899f5a7e68c21009c8c3bd76f","src/bindings/swift/templates/ErrorTemplate.swift":"5014392d7a29ac271e140f27dd231b5a031abe9b6cf909d2b80200443ee03c18","src/bindings/swift/templates/Float32Helper.swift":"ea32538058c4b3c72b1cd2530ac00d0349fadab5e1bc617d33aae4c87692fc98","src/bindings/swift/templates/Float64Helper.swift":"e27e9424dc6e97b8cacc6ca4c796dd2d16dcfcb877e2f19c45eca03381a41e78","src/bindings/swift/templates/Helpers.swift":"3cf3a5342a1ef0c7e078b0e58c32ae4437c4fb5dc41acbbd56b1128393c76602","src/bindings/swift/templates/Int16Helper.swift":"204906911813a3931436057c23032f4c4e39e023df90d641d6c6086aefe2f820","src/bindings/swift/templates/Int32Helper.swift":"0997f059c9c4edd3c41aee0bbad4aa2bda6d791a0d623ad8014d5aa6bdae718d","src/bindings/swift/templates/Int64Helper.swift":"bcf8c2deebb3ee9bce87735adc4bd100981989943b69f6a7fb499a9aec4c25d9","src/bindings/swift/templates/Int8Helper.swift":"ad1ec0fa213724933fa4dc4e2e304e13ac4722b774bfffac44793986b997dd33","src/bindings/swift/templates/MapTemplate.swift":"53971ec388417b02519f8deb8d66361ab4693eae77d116f6051cbea4738054ec","src/bindings/swift/templates/ModuleMapTemplate.modulemap":"99ad1e9bf550a21497296f9248ecd4385dd6d0b5892951d24cf990cdbf3eec2c","src/bindings/swift/templates/ObjectTemplate.swift":"9e77b66477b50b77159e6fe885313833976219202bbb0c03613c8c79c5b31a36","src/bindings/swift/templates/OptionalTemplate.swift":"2376487ceadca3975f0e82ddf6ce61af8bbbf5b0592fa9cd977460f148d8c99d","src/bindings/swift/templates/RecordTemplate.swift":"5209d2438686a19bbf3903091796d3840a75de2df1beea2f99c5fbce6a83cd44","src/bindings/swift/templates/RustBufferTemplate.swift":"f4422fdf0cb5b4db267d461f063dedc319ea1a5a13bae1b82c3f108ba8c658bb","src/bindings/swift/templates/SequenceTemplate.swift":"8425b279197582a94b4cf363ab0463907a68a624e24045720ef7df2bcacf3383","src/bindings/swift/templates/StringHelper.swift":"968b9b9b7fbe06a2ac2143016edaff3552e201652accb8d613b03645f0d24a90","src/bindings/swift/templates/TimestampHelper.swift":"82eece13aa186c8e3745c8ad2f1290639ca9689573018a2bdc5c75afbae58c26","src/bindings/swift/templates/TopLevelFunctionTemplate.swift":"ca7fcbaa06ee31c7433fd6aa238b6c1135a843f469d9b4f37d4874d514fd80d0","src/bindings/swift/templates/Types.swift":"8faebfad82be0788acb4b61716761366cd85e280e1c89ca741d423eebf5d220a","src/bindings/swift/templates/UInt16Helper.swift":"d6fba577324fc0e9e50329c968df99341de418011be126bd29702f8a94d87c02","src/bindings/swift/templates/UInt32Helper.swift":"5e0cf151a0c40098b3d96815ba3de23e15fe52f3c517577e1b0c7e7a4c12428f","src/bindings/swift/templates/UInt64Helper.swift":"17237b38d09ced8d2a8ff1ad9ca86873a19e417280e0e60f33d7063397ea4b7b","src/bindings/swift/templates/UInt8Helper.swift":"c4cb2ee4a78b54ca8d0013383c6b43e9ecd42776e3dc7d6e40086325d41714e5","src/bindings/swift/templates/macros.swift":"17819060db72ff2d67d0fee8b4932e0fe5cfe1cb25b70bcfd83bcfbf0cbbd9a7","src/bindings/swift/templates/wrapper.swift":"ca1af5fe47bca972c76fb8c9f52b18f727d4343204408de3dd0aae384f0a4171","src/bindings/swift/test.rs":"72ba6a6fbc65ffd035d4a5694e462d5fa0734cb5b9b585246a5508dba9ce200b","src/interface/attributes.rs":"31bb5b30f6be41871bade5dfc3958156079d91c48110b279efdb2ae96102bf4c","src/interface/callbacks.rs":"e14abd2a9575ae55891ddc7f20d6643211b6eaad575ac3143f0591f185ad498d","src/interface/enum_.rs":"e93ce9d66fa77d565bb02256a1e356cead49af31774999f6b481d5ec81582fe9","src/interface/error.rs":"e51b64505b38a17807ce82070b253c61b5cb7ca71e42dae3913fa462f3a84356","src/interface/ffi.rs":"f056724ad06074e7ebb4adc32372d8d7b5f8a29d0c817b709e4d4cfc7f5d83b0","src/interface/function.rs":"0576142a6dbcb3f9f435f0d9ac78ef588b152462969123c1ed38fd01bd42078c","src/interface/literal.rs":"771956995994e2a215c84c4ba8d91dfe52e5b36b5137e49902af90273a1b663c","src/interface/mod.rs":"d956a482926a521e641409734eb311be97dfb29af3281634a988e5b29e34dd55","src/interface/namespace.rs":"ab0f63241bb6a0a32f9fd2a1544468cb6daa1f91f6116b7246b52a4a94e4f461","src/interface/object.rs":"7043293251d8c67c5fa37c42026c65a56defe68688a98a4d05ddb94545024f08","src/interface/record.rs":"5274385d79198cf100e9aa307a8021ba6edbd75e24ae5db2f8244986c951f55a","src/interface/types/finder.rs":"e1a0697a51c847d58f5adc529abb006e7fc55954ec92a9c822926a661acc0386","src/interface/types/mod.rs":"dc23b5626031df99ca414430d339deaa6966fe5c9969cebf46be057582e9faf8","src/interface/types/resolver.rs":"83f80d582279e16d6c6bfb2b5dd7950639d99e1d5c6627f6b3d284a7abf255ef","src/lib.rs":"af929600e6cf23801d494ce2fd44913a4f5c1a770f903aef982f9bf33f9a6fbc","src/macro_metadata/ci.rs":"c3768df2bf1c12089077ef04b542ddcc1bf2d6c1539aa0ee7afa6f3bfb026dc6","src/macro_metadata/extract.rs":"d07beb4e1e03befaad6ce57c8103a03cb7ce6092871ae4bff9eb1282293653e6","src/macro_metadata/mod.rs":"1668e6d947fd4a957f557410a8095649de6f8d7fd3eb6a403b8587280dca2d54","src/scaffolding/mod.rs":"d2b1e373627cbc5e54b1bb82ffca8555982bfa3e71af33fb2ab7e20979bb946a","src/scaffolding/templates/CallbackInterfaceTemplate.rs":"9cc291b565a07a48418bd219e375a174e484562599fa543188e6e88409b528ad","src/scaffolding/templates/EnumTemplate.rs":"eb8802f04f1fd1c9451ad72c1195c136288fdb4e488922b24db75b2fdae65cd7","src/scaffolding/templates/ErrorTemplate.rs":"e7ca4538908a273dd7ba3834219b30fa0e537b2171144f06b6a96fbefb90cd1d","src/scaffolding/templates/ExternalTypesTemplate.rs":"3805714b9c31b83650326b783150e281162ff03d204044bdba7912de2730c7d9","src/scaffolding/templates/ObjectTemplate.rs":"34de640428486e17a61ded4f42ce13d8eac3e66cc9f60196ce2575cdc0bd8df6","src/scaffolding/templates/RecordTemplate.rs":"e8d5af954f46f023a243721d6fc70aa176c3a4c0a9dd340048bfe46f3eeed783","src/scaffolding/templates/ReexportUniFFIScaffolding.rs":"559a17c8e39c473ff1effe9651f05b83d443ecd8abed13e03f2b63872d7e1593","src/scaffolding/templates/RustBuffer.rs":"ccf7521012d93c41265375c6d2e80ce861ec93b41383da83c37718386dd726f6","src/scaffolding/templates/TopLevelFunctionTemplate.rs":"35eaefb0862f25ff683d58fb0be6ad5f74bbe2fc85047273200c3a5f4c728434","src/scaffolding/templates/macros.rs":"d42ed1801927b38cd6a526b2b00cfc2b9bfdf65b5501216c4a3d903e0765af7c","src/scaffolding/templates/scaffolding_template.rs":"51a34e249f788c02f117f3bd7c3de1633fcc765c6625c762c5bd870541cae6e3"},"package":"dbbba5103051c18f10b22f80a74439ddf7100273f217a547005d2735b2498994"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_bindgen/Cargo.toml b/third_party/rust/uniffi_bindgen/Cargo.toml
new file mode 100644
index 0000000000..2ef7afb6c4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/Cargo.toml
@@ -0,0 +1,75 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi_bindgen"
+version = "0.23.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+description = "a multi-language bindings generator for rust (codegen and cli tooling)"
+homepage = "https://mozilla.github.io/uniffi-rs"
+documentation = "https://mozilla.github.io/uniffi-rs"
+keywords = [
+ "ffi",
+ "bindgen",
+]
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/uniffi-rs"
+
+[dependencies.anyhow]
+version = "1"
+
+[dependencies.askama]
+version = "0.11"
+features = ["config"]
+default-features = false
+
+[dependencies.bincode]
+version = "1.3"
+
+[dependencies.camino]
+version = "1.0.8"
+
+[dependencies.fs-err]
+version = "2.7.0"
+
+[dependencies.glob]
+version = "0.3"
+
+[dependencies.goblin]
+version = "0.6"
+
+[dependencies.heck]
+version = "0.4"
+
+[dependencies.once_cell]
+version = "1.12"
+
+[dependencies.paste]
+version = "1.0"
+
+[dependencies.serde]
+version = "1"
+
+[dependencies.serde_json]
+version = "1.0.80"
+
+[dependencies.toml]
+version = "0.5"
+
+[dependencies.uniffi_meta]
+version = "=0.23.0"
+
+[dependencies.uniffi_testing]
+version = "=0.23.0"
+
+[dependencies.weedle2]
+version = "4.0.0"
diff --git a/third_party/rust/uniffi_bindgen/askama.toml b/third_party/rust/uniffi_bindgen/askama.toml
new file mode 100644
index 0000000000..a0aae5f41b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/askama.toml
@@ -0,0 +1,21 @@
+[general]
+# Directories to search for templates, relative to the crate root.
+dirs = [ "src/scaffolding/templates", "src/bindings/kotlin/templates", "src/bindings/python/templates", "src/bindings/swift/templates", "src/bindings/ruby/templates" ]
+
+[[syntax]]
+name = "kt"
+
+[[syntax]]
+name = "py"
+
+[[syntax]]
+name = "swift"
+
+[[syntax]]
+name = "c"
+
+[[syntax]]
+name = "rs"
+
+[[syntax]]
+name = "rb"
diff --git a/third_party/rust/uniffi_bindgen/src/backend/config.rs b/third_party/rust/uniffi_bindgen/src/backend/config.rs
new file mode 100644
index 0000000000..616cde0656
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/backend/config.rs
@@ -0,0 +1,17 @@
+/* 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 serde::{Deserialize, Serialize};
+
+/// Config value for template expressions
+///
+/// These are strings that support simple template substitution. `{}` gets replaced by a value.
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+pub struct TemplateExpression(String);
+
+impl TemplateExpression {
+ pub fn render(&self, var: &str) -> String {
+ self.0.replace("{}", var)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/backend/declarations.rs b/third_party/rust/uniffi_bindgen/src/backend/declarations.rs
new file mode 100644
index 0000000000..320cfe995c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/backend/declarations.rs
@@ -0,0 +1,31 @@
+/* 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 super::CodeOracle;
+
+/// A trait that is able to render a declaration about a particular member declared in
+/// the `ComponentInterface`.
+/// Like `CodeType`, it can render declaration code and imports. It also is able to render
+/// code at start-up of the FFI.
+/// All methods are optional, and there is no requirement that the trait be used for a particular
+/// `interface::` member. Thus, it can also be useful for conditionally rendering code.
+pub trait CodeDeclaration {
+ /// A list of imports that are needed if this type is in use.
+ /// Classes are imported exactly once.
+ fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+ None
+ }
+
+ /// Code (one or more statements) that is run on start-up of the library,
+ /// but before the client code has access to it.
+ fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+ None
+ }
+
+ /// Code which represents this member. e.g. the foreign language class definition for
+ /// a given Object type.
+ fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+ None
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/backend/mod.rs b/third_party/rust/uniffi_bindgen/src/backend/mod.rs
new file mode 100644
index 0000000000..ed4c032508
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/backend/mod.rs
@@ -0,0 +1,16 @@
+/* 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/. */
+
+mod config;
+mod declarations;
+mod oracle;
+mod types;
+
+pub use config::TemplateExpression;
+pub use declarations::CodeDeclaration;
+pub use oracle::CodeOracle;
+pub use types::CodeType;
+
+pub type TypeIdentifier = crate::interface::Type;
+pub type Literal = crate::interface::Literal;
diff --git a/third_party/rust/uniffi_bindgen/src/backend/oracle.rs b/third_party/rust/uniffi_bindgen/src/backend/oracle.rs
new file mode 100644
index 0000000000..c911dc7df7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/backend/oracle.rs
@@ -0,0 +1,35 @@
+/* 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 super::{CodeType, TypeIdentifier};
+use crate::interface::FfiType;
+
+/// An object to look up a foreign language code specific renderer for a given type used.
+/// Every `Type` referred to in the `ComponentInterface` should map to a corresponding
+/// `CodeType`.
+///
+/// The mapping may be opaque, but the oracle always knows the answer.
+///
+/// In adddition, the oracle knows how to render identifiers (function names,
+/// class names, variable names etc).
+pub trait CodeOracle {
+ fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType>;
+
+ /// Get the idiomatic rendering of a class name (for enums, records, errors, etc).
+ fn class_name(&self, nm: &str) -> String;
+
+ /// Get the idiomatic rendering of a function name.
+ fn fn_name(&self, nm: &str) -> String;
+
+ /// Get the idiomatic rendering of a variable name.
+ fn var_name(&self, nm: &str) -> String;
+
+ /// Get the idiomatic rendering of an individual enum variant.
+ fn enum_variant_name(&self, nm: &str) -> String;
+
+ /// Get the idiomatic rendering of an error name.
+ fn error_name(&self, nm: &str) -> String;
+
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String;
+}
diff --git a/third_party/rust/uniffi_bindgen/src/backend/types.rs b/third_party/rust/uniffi_bindgen/src/backend/types.rs
new file mode 100644
index 0000000000..217523c290
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/backend/types.rs
@@ -0,0 +1,218 @@
+/* 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/. */
+
+//! # Backend traits
+//!
+//! This module provides a number of traits useful for implementing a backend for Uniffi.
+//!
+//! A `CodeType` is needed for each type that will cross the FFI. It should provide helper machinery
+//! in the target language to lift from and lower into a value of that type into a primitive type
+//! (the FfiType), and foreign language expressions that call into the machinery. This helper code
+//! can be provided by a template file.
+//!
+//! A `CodeDeclaration` is needed for each type that is declared in the UDL file. This has access to
+//! the [crate::interface::ComponentInterface], which is the closest thing to an Intermediate Representation.
+//!
+//! `CodeDeclaration`s provide the target language's version of the UDL type, including forwarding method calls
+//! into Rust. It is likely if you're implementing a `CodeDeclaration` for this purpose, it will also need to cross
+//! the FFI, and you'll also need a `CodeType`.
+//!
+//! `CodeDeclaration`s can also be used to conditionally include code: e.g. only include the CallbackInterfaceRuntime
+//! if the user has used at least one callback interface.
+//!
+//! Each backend has a wrapper template for each file it needs to generate. This should collect the `CodeDeclaration`s that
+//! the backend and `ComponentInterface` between them specify and use them to stitch together a file in the target language.
+//!
+//! The `CodeOracle` provides methods to map the `Type` values found in the `ComponentInterface` to the `CodeType`s specified
+//! by the backend. It also provides methods for transforming identifiers into the coding standard for the target language.
+//!
+//! Each backend will have its own `filter` module, which is used by the askama templates used in all `CodeType`s and `CodeDeclaration`s.
+//! This filter provides methods to generate expressions and identifiers in the target language. These are all forwarded to the oracle.
+
+use super::{CodeOracle, Literal};
+use crate::interface::*;
+
+/// A Trait to emit foreign language code to handle referenced types.
+/// A type which is specified in the UDL (i.e. a member of the component interface)
+/// will have a `CodeDeclaration` as well, but for types used e.g. primitive types, Strings, etc
+/// only a `CodeType` is needed.
+pub trait CodeType {
+ /// The language specific label used to reference this type. This will be used in
+ /// method signatures and property declarations.
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String;
+
+ /// A representation of this type label that can be used as part of another
+ /// identifier. e.g. `read_foo()`, or `FooInternals`.
+ ///
+ /// This is especially useful when creating specialized objects or methods to deal
+ /// with this type only.
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ self.type_label(oracle)
+ }
+
+ /// A representation of the given literal for this type.
+ /// N.B. `Literal` is aliased from `interface::Literal`, so may not be whole suited to this task.
+ fn literal(&self, oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unimplemented!("Unimplemented for {}", self.type_label(oracle))
+ }
+
+ /// Name of the FfiConverter
+ ///
+ /// This is the object that contains the lower, write, lift, and read methods for this type.
+ /// Depending on the binding this will either be a singleton or a class with static methods.
+ ///
+ /// This is the newer way of handling these methods and replaces the lower, write, lift, and
+ /// read CodeType methods. Currently only used by Kotlin, but the plan is to move other
+ /// backends to using this.
+ fn ffi_converter_name(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&format!("FfiConverter{}", self.canonical_name(oracle)))
+ }
+
+ /// An expression for lowering a value into something we can pass over the FFI.
+ fn lower(&self, oracle: &dyn CodeOracle) -> String {
+ format!("{}.lower", self.ffi_converter_name(oracle))
+ }
+
+ /// An expression for writing a value into a byte buffer.
+ fn write(&self, oracle: &dyn CodeOracle) -> String {
+ format!("{}.write", self.ffi_converter_name(oracle))
+ }
+
+ /// An expression for lifting a value from something we received over the FFI.
+ fn lift(&self, oracle: &dyn CodeOracle) -> String {
+ format!("{}.lift", self.ffi_converter_name(oracle))
+ }
+
+ /// An expression for reading a value from a byte buffer.
+ fn read(&self, oracle: &dyn CodeOracle) -> String {
+ format!("{}.read", self.ffi_converter_name(oracle))
+ }
+
+ /// A list of imports that are needed if this type is in use.
+ /// Classes are imported exactly once.
+ fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+ None
+ }
+
+ /// Function to run at startup
+ fn initialization_fn(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+ None
+ }
+
+ /// An expression to coerce the given variable to the expected type.
+ fn coerce(&self, oracle: &dyn CodeOracle, _nm: &str) -> String {
+ panic!("Unimplemented for {}", self.type_label(oracle));
+ }
+}
+
+/// This trait is used to implement `CodeType` for `Type` and type-like structs (`Record`, `Enum`, `Field`,
+/// etc). We forward all method calls to a `Box<dyn CodeType>`, which we get by calling
+/// `CodeOracle.find()`.
+pub trait CodeTypeDispatch {
+ fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType>;
+}
+
+impl CodeTypeDispatch for Type {
+ fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
+ oracle.find(self)
+ }
+}
+
+impl CodeTypeDispatch for Record {
+ fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
+ oracle.find(&self.type_())
+ }
+}
+
+impl CodeTypeDispatch for Enum {
+ fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
+ oracle.find(&self.type_())
+ }
+}
+
+impl CodeTypeDispatch for Error {
+ fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
+ oracle.find(&self.type_())
+ }
+}
+
+impl CodeTypeDispatch for Object {
+ fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
+ oracle.find(&self.type_())
+ }
+}
+
+impl CodeTypeDispatch for CallbackInterface {
+ fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
+ oracle.find(&self.type_())
+ }
+}
+
+impl CodeTypeDispatch for Field {
+ fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
+ oracle.find(self.type_())
+ }
+}
+
+impl CodeTypeDispatch for Argument {
+ fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
+ oracle.find(self.type_())
+ }
+}
+
+// Needed to handle &&Type and &&&Type values, which we sometimes end up with in the template code
+impl<T, C> CodeTypeDispatch for T
+where
+ T: std::ops::Deref<Target = C>,
+ C: CodeTypeDispatch,
+{
+ fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
+ self.deref().code_type_impl(oracle)
+ }
+}
+
+impl<T: CodeTypeDispatch> CodeType for T {
+ // The above code implements `CodeTypeDispatch` for `Type` and type-like structs (`Record`,
+ // `Enum`, `Field`, etc). Now we can leverage that to implement `CodeType` for all of them.
+ // This allows for simpler template code (`field|lower` instead of `field.type_()|lower`)
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ self.code_type_impl(oracle).type_label(oracle)
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ self.code_type_impl(oracle).canonical_name(oracle)
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ self.code_type_impl(oracle).literal(oracle, literal)
+ }
+
+ fn lower(&self, oracle: &dyn CodeOracle) -> String {
+ self.code_type_impl(oracle).lower(oracle)
+ }
+
+ fn write(&self, oracle: &dyn CodeOracle) -> String {
+ self.code_type_impl(oracle).write(oracle)
+ }
+
+ fn lift(&self, oracle: &dyn CodeOracle) -> String {
+ self.code_type_impl(oracle).lift(oracle)
+ }
+
+ fn read(&self, oracle: &dyn CodeOracle) -> String {
+ self.code_type_impl(oracle).read(oracle)
+ }
+
+ fn imports(&self, oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+ self.code_type_impl(oracle).imports(oracle)
+ }
+
+ fn initialization_fn(&self, oracle: &dyn CodeOracle) -> Option<String> {
+ self.code_type_impl(oracle).initialization_fn(oracle)
+ }
+
+ fn coerce(&self, oracle: &dyn CodeOracle, nm: &str) -> String {
+ self.code_type_impl(oracle).coerce(oracle, nm)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
new file mode 100644
index 0000000000..c0c7ab29b9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
@@ -0,0 +1,33 @@
+/* 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, Literal};
+
+pub struct CallbackInterfaceCodeType {
+ id: String,
+}
+
+impl CallbackInterfaceCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for CallbackInterfaceCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn initialization_fn(&self, oracle: &dyn CodeOracle) -> Option<String> {
+ Some(format!("{}.register", self.ffi_converter_name(oracle)))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
new file mode 100644
index 0000000000..7d5f77141f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
@@ -0,0 +1,94 @@
+/* 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, Literal, TypeIdentifier};
+use paste::paste;
+
+fn render_literal(oracle: &dyn CodeOracle, literal: &Literal, inner: &TypeIdentifier) -> String {
+ match literal {
+ Literal::Null => "null".into(),
+ Literal::EmptySequence => "listOf()".into(),
+ Literal::EmptyMap => "mapOf()".into(),
+
+ // For optionals
+ _ => oracle.find(inner).literal(oracle, literal),
+ }
+}
+
+macro_rules! impl_code_type_for_compound {
+ ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => {
+ paste! {
+ pub struct $T {
+ inner: TypeIdentifier,
+ }
+
+ impl $T {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+ fn inner(&self) -> &TypeIdentifier {
+ &self.inner
+ }
+ }
+
+ impl CodeType for $T {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ format!($type_label_pattern, oracle.find(self.inner()).type_label(oracle))
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!($canonical_name_pattern, oracle.find(self.inner()).canonical_name(oracle))
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, literal, self.inner())
+ }
+ }
+ }
+ }
+ }
+
+impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}");
+impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}");
+
+pub struct MapCodeType {
+ key: TypeIdentifier,
+ value: TypeIdentifier,
+}
+
+impl MapCodeType {
+ pub fn new(key: TypeIdentifier, value: TypeIdentifier) -> Self {
+ Self { key, value }
+ }
+
+ fn key(&self) -> &TypeIdentifier {
+ &self.key
+ }
+
+ fn value(&self) -> &TypeIdentifier {
+ &self.value
+ }
+}
+
+impl CodeType for MapCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Map<{}, {}>",
+ self.key().type_label(oracle),
+ self.value().type_label(oracle),
+ )
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Map{}{}",
+ self.key().type_label(oracle),
+ self.value().type_label(oracle),
+ )
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, literal, &self.value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs
new file mode 100644
index 0000000000..4a5636a228
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs
@@ -0,0 +1,29 @@
+/* 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, Literal};
+
+pub struct CustomCodeType {
+ name: String,
+}
+
+impl CustomCodeType {
+ pub fn new(name: String) -> Self {
+ CustomCodeType { name }
+ }
+}
+
+impl CodeType for CustomCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.name)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!("Can't have a literal of a custom type");
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs
new file mode 100644
index 0000000000..941008e370
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs
@@ -0,0 +1,37 @@
+/* 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, Literal};
+
+pub struct EnumCodeType {
+ id: String,
+}
+
+impl EnumCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for EnumCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ if let Literal::Enum(v, _) = literal {
+ format!(
+ "{}.{}",
+ self.type_label(oracle),
+ oracle.enum_variant_name(v)
+ )
+ } else {
+ unreachable!();
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.rs
new file mode 100644
index 0000000000..3ab5f0ac9f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.rs
@@ -0,0 +1,29 @@
+/* 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, Literal};
+
+pub struct ErrorCodeType {
+ id: String,
+}
+
+impl ErrorCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ErrorCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.error_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
new file mode 100644
index 0000000000..32d47cb3f1
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
@@ -0,0 +1,29 @@
+/* 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, Literal};
+
+pub struct ExternalCodeType {
+ name: String,
+}
+
+impl ExternalCodeType {
+ pub fn new(name: String) -> Self {
+ Self { 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)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!("Can't have a literal of an external type");
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs
new file mode 100644
index 0000000000..57b9a145e7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs
@@ -0,0 +1,32 @@
+/* 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, Literal};
+use paste::paste;
+
+macro_rules! impl_code_type_for_miscellany {
+ ($T:ty, $class_name:literal, $canonical_name:literal) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ $class_name.into()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ $canonical_name.into()
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!()
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_miscellany!(TimestampCodeType, "java.time.Instant", "Timestamp");
+
+impl_code_type_for_miscellany!(DurationCodeType, "java.time.Duration", "Duration");
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
new file mode 100644
index 0000000000..8fe12f30c7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
@@ -0,0 +1,417 @@
+/* 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 std::borrow::Borrow;
+use std::cell::RefCell;
+use std::collections::{BTreeSet, HashMap, HashSet};
+
+use anyhow::{Context, Result};
+use askama::Template;
+use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
+use serde::{Deserialize, Serialize};
+
+use crate::backend::{CodeOracle, CodeType, TemplateExpression, TypeIdentifier};
+use crate::interface::*;
+use crate::MergeWith;
+
+mod callback_interface;
+mod compounds;
+mod custom;
+mod enum_;
+mod error;
+mod external;
+mod miscellany;
+mod object;
+mod primitives;
+mod record;
+
+// config options to customize the generated Kotlin.
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+pub struct Config {
+ package_name: Option<String>,
+ cdylib_name: Option<String>,
+ #[serde(default)]
+ custom_types: HashMap<String, CustomTypeConfig>,
+ #[serde(default)]
+ external_packages: HashMap<String, String>,
+}
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+pub struct CustomTypeConfig {
+ imports: Option<Vec<String>>,
+ type_name: Option<String>,
+ into_custom: TemplateExpression,
+ from_custom: TemplateExpression,
+}
+
+impl Config {
+ pub fn package_name(&self) -> String {
+ if let Some(package_name) = &self.package_name {
+ package_name.clone()
+ } else {
+ "uniffi".into()
+ }
+ }
+
+ pub fn cdylib_name(&self) -> String {
+ if let Some(cdylib_name) = &self.cdylib_name {
+ cdylib_name.clone()
+ } else {
+ "uniffi".into()
+ }
+ }
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ package_name: Some(format!("uniffi.{}", ci.namespace())),
+ cdylib_name: Some(format!("uniffi_{}", ci.namespace())),
+ custom_types: HashMap::new(),
+ external_packages: HashMap::new(),
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ package_name: self.package_name.merge_with(&other.package_name),
+ cdylib_name: self.cdylib_name.merge_with(&other.cdylib_name),
+ custom_types: self.custom_types.merge_with(&other.custom_types),
+ external_packages: self.external_packages.merge_with(&other.external_packages),
+ }
+ }
+}
+
+// Generate kotlin bindings for the given ComponentInterface, as a string.
+pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
+ KotlinWrapper::new(config.clone(), ci)
+ .render()
+ .context("failed to render kotlin bindings")
+}
+
+/// A struct to record a Kotlin import statement.
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub enum ImportRequirement {
+ /// The name we are importing.
+ Import { name: String },
+ /// Import the name with the specified local name.
+ ImportAs { name: String, as_name: String },
+}
+
+impl ImportRequirement {
+ /// Render the Kotlin import statement.
+ fn render(&self) -> String {
+ match &self {
+ ImportRequirement::Import { name } => format!("import {name}"),
+ ImportRequirement::ImportAs { name, as_name } => {
+ format!("import {name} as {as_name}")
+ }
+ }
+ }
+}
+
+/// Renders Kotlin helper code for all types
+///
+/// This template is a bit different than others in that it stores internal state from the render
+/// process. Make sure to only call `render()` once.
+#[derive(Template)]
+#[template(syntax = "kt", escape = "none", path = "Types.kt")]
+pub struct TypeRenderer<'a> {
+ kotlin_config: &'a Config,
+ ci: &'a ComponentInterface,
+ // Track included modules for the `include_once()` macro
+ include_once_names: RefCell<HashSet<String>>,
+ // Track imports added with the `add_import()` macro
+ imports: RefCell<BTreeSet<ImportRequirement>>,
+}
+
+impl<'a> TypeRenderer<'a> {
+ fn new(kotlin_config: &'a Config, ci: &'a ComponentInterface) -> Self {
+ Self {
+ kotlin_config,
+ ci,
+ include_once_names: RefCell::new(HashSet::new()),
+ imports: RefCell::new(BTreeSet::new()),
+ }
+ }
+
+ // Get the package name for an external type
+ fn external_type_package_name(&self, crate_name: &str) -> String {
+ match self.kotlin_config.external_packages.get(crate_name) {
+ Some(name) => name.clone(),
+ None => crate_name.to_string(),
+ }
+ }
+
+ // The following methods are used by the `Types.kt` macros.
+
+ // Helper for the including a template, but only once.
+ //
+ // The first time this is called with a name it will return true, indicating that we should
+ // include the template. Subsequent calls will return false.
+ fn include_once_check(&self, name: &str) -> bool {
+ self.include_once_names
+ .borrow_mut()
+ .insert(name.to_string())
+ }
+
+ // Helper to add an import statement
+ //
+ // Call this inside your template to cause an import statement to be added at the top of the
+ // file. Imports will be sorted and de-deuped.
+ //
+ // Returns an empty string so that it can be used inside an askama `{{ }}` block.
+ fn add_import(&self, name: &str) -> &str {
+ self.imports.borrow_mut().insert(ImportRequirement::Import {
+ name: name.to_owned(),
+ });
+ ""
+ }
+
+ // Like add_import, but arranges for `import name as as_name`
+ fn add_import_as(&self, name: &str, as_name: &str) -> &str {
+ self.imports
+ .borrow_mut()
+ .insert(ImportRequirement::ImportAs {
+ name: name.to_owned(),
+ as_name: as_name.to_owned(),
+ });
+ ""
+ }
+}
+
+#[derive(Template)]
+#[template(syntax = "kt", escape = "none", path = "wrapper.kt")]
+pub struct KotlinWrapper<'a> {
+ config: Config,
+ ci: &'a ComponentInterface,
+ type_helper_code: String,
+ type_imports: BTreeSet<ImportRequirement>,
+}
+
+impl<'a> KotlinWrapper<'a> {
+ pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
+ let type_renderer = TypeRenderer::new(&config, ci);
+ let type_helper_code = type_renderer.render().unwrap();
+ let type_imports = type_renderer.imports.into_inner();
+ Self {
+ config,
+ ci,
+ type_helper_code,
+ type_imports,
+ }
+ }
+
+ pub fn initialization_fns(&self) -> Vec<String> {
+ self.ci
+ .iter_types()
+ .filter_map(|t| t.initialization_fn(&KotlinCodeOracle))
+ .collect()
+ }
+
+ pub fn imports(&self) -> Vec<ImportRequirement> {
+ self.type_imports.iter().cloned().collect()
+ }
+}
+
+#[derive(Clone)]
+pub struct KotlinCodeOracle;
+
+impl KotlinCodeOracle {
+ // Map `Type` instances to a `Box<dyn CodeType>` for that type.
+ //
+ // There is a companion match in `templates/Types.kt` which performs a similar function for the
+ // template code.
+ //
+ // - When adding additional types here, make sure to also add a match arm to the `Types.kt` template.
+ // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> {
+ match type_ {
+ Type::UInt8 => Box::new(primitives::UInt8CodeType),
+ Type::Int8 => Box::new(primitives::Int8CodeType),
+ Type::UInt16 => Box::new(primitives::UInt16CodeType),
+ Type::Int16 => Box::new(primitives::Int16CodeType),
+ Type::UInt32 => Box::new(primitives::UInt32CodeType),
+ Type::Int32 => Box::new(primitives::Int32CodeType),
+ Type::UInt64 => Box::new(primitives::UInt64CodeType),
+ Type::Int64 => Box::new(primitives::Int64CodeType),
+ Type::Float32 => Box::new(primitives::Float32CodeType),
+ Type::Float64 => Box::new(primitives::Float64CodeType),
+ Type::Boolean => Box::new(primitives::BooleanCodeType),
+ Type::String => Box::new(primitives::StringCodeType),
+
+ Type::Timestamp => Box::new(miscellany::TimestampCodeType),
+ Type::Duration => Box::new(miscellany::DurationCodeType),
+
+ Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
+ Type::Object(id) => Box::new(object::ObjectCodeType::new(id)),
+ Type::Record(id) => Box::new(record::RecordCodeType::new(id)),
+ Type::Error(id) => Box::new(error::ErrorCodeType::new(id)),
+ Type::CallbackInterface(id) => {
+ Box::new(callback_interface::CallbackInterfaceCodeType::new(id))
+ }
+ 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 { 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")
+ }
+ }
+ }
+}
+
+impl CodeOracle for KotlinCodeOracle {
+ fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+ self.create_code_type(type_.clone())
+ }
+
+ /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
+ fn class_name(&self, nm: &str) -> String {
+ nm.to_string().to_upper_camel_case()
+ }
+
+ /// Get the idiomatic Kotlin rendering of a function name.
+ fn fn_name(&self, nm: &str) -> String {
+ format!("`{}`", nm.to_string().to_lower_camel_case())
+ }
+
+ /// Get the idiomatic Kotlin rendering of a variable name.
+ fn var_name(&self, nm: &str) -> String {
+ format!("`{}`", nm.to_string().to_lower_camel_case())
+ }
+
+ /// Get the idiomatic Kotlin rendering of an individual enum variant.
+ fn enum_variant_name(&self, nm: &str) -> String {
+ nm.to_string().to_shouty_snake_case()
+ }
+
+ /// Get the idiomatic Kotlin rendering of an exception name
+ ///
+ /// This replaces "Error" at the end of the name with "Exception". Rust code typically uses
+ /// "Error" for any type of error but in the Java world, "Error" means a non-recoverable error
+ /// and is distinguished from an "Exception".
+ fn error_name(&self, nm: &str) -> String {
+ // errors are a class in kotlin.
+ let name = self.class_name(nm);
+ match name.strip_suffix("Error") {
+ None => name,
+ Some(stripped) => format!("{stripped}Exception"),
+ }
+ }
+
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ // Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not
+ // support them yet. Thus, we use the signed variants to represent both signed and unsigned
+ // types from the component API.
+ FfiType::Int8 | FfiType::UInt8 => "Byte".to_string(),
+ FfiType::Int16 | FfiType::UInt16 => "Short".to_string(),
+ FfiType::Int32 | FfiType::UInt32 => "Int".to_string(),
+ FfiType::Int64 | FfiType::UInt64 => "Long".to_string(),
+ FfiType::Float32 => "Float".to_string(),
+ FfiType::Float64 => "Double".to_string(),
+ FfiType::RustArcPtr(_) => "Pointer".to_string(),
+ FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
+ Some(suffix) => format!("RustBuffer{}.ByValue", suffix),
+ None => "RustBuffer.ByValue".to_string(),
+ },
+ FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
+ FfiType::ForeignCallback => "ForeignCallback".to_string(),
+ }
+ }
+}
+
+pub mod filters {
+ use super::*;
+
+ fn oracle() -> &'static KotlinCodeOracle {
+ &KotlinCodeOracle
+ }
+
+ pub fn type_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.type_label(oracle()))
+ }
+
+ pub fn canonical_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.canonical_name(oracle()))
+ }
+
+ pub fn ffi_converter_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.ffi_converter_name(oracle()))
+ }
+
+ pub fn lower_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lower", codetype.ffi_converter_name(oracle())))
+ }
+
+ pub fn allocation_size_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!(
+ "{}.allocationSize",
+ codetype.ffi_converter_name(oracle())
+ ))
+ }
+
+ pub fn write_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.write", codetype.ffi_converter_name(oracle())))
+ }
+
+ pub fn lift_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lift", codetype.ffi_converter_name(oracle())))
+ }
+
+ pub fn read_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.read", codetype.ffi_converter_name(oracle())))
+ }
+
+ pub fn render_literal(
+ literal: &Literal,
+ codetype: &impl CodeType,
+ ) -> Result<String, askama::Error> {
+ Ok(codetype.literal(oracle(), literal))
+ }
+
+ /// Get the Kotlin syntax for representing a given low-level `FfiType`.
+ pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_type_label(type_))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
+ pub fn class_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().class_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a function name.
+ pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().fn_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a variable name.
+ pub fn var_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().var_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of an individual enum variant.
+ pub fn enum_variant(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().enum_variant_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of an exception name, replacing
+ /// `Error` with `Exception`.
+ pub fn exception_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().error_name(nm))
+ }
+
+ /// Remove the "`" chars we put around function/variable names
+ ///
+ /// These are used to avoid name clashes with kotlin identifiers, but sometimes you want to
+ /// render the name unquoted. One example is the message property for errors where we want to
+ /// display the name for the user.
+ pub fn unquote(nm: &str) -> Result<String, askama::Error> {
+ Ok(nm.trim_matches('`').to_string())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
new file mode 100644
index 0000000000..7a1cddbafd
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
@@ -0,0 +1,29 @@
+/* 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, Literal};
+
+pub struct ObjectCodeType {
+ id: String,
+}
+
+impl ObjectCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ObjectCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
new file mode 100644
index 0000000000..afc8fc556d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
@@ -0,0 +1,79 @@
+/* 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, Literal};
+use crate::interface::{types::Type, Radix};
+use paste::paste;
+
+fn render_literal(_oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ fn typed_number(type_: &Type, num_str: String) -> String {
+ match type_ {
+ // Bytes, Shorts and Ints can all be inferred from the type.
+ Type::Int8 | Type::Int16 | Type::Int32 => num_str,
+ Type::Int64 => format!("{num_str}L"),
+
+ Type::UInt8 | Type::UInt16 | Type::UInt32 => format!("{num_str}u"),
+ Type::UInt64 => format!("{num_str}uL"),
+
+ Type::Float32 => format!("{num_str}f"),
+ Type::Float64 => num_str,
+ _ => panic!("Unexpected literal: {num_str} is not a number"),
+ }
+ }
+
+ match literal {
+ Literal::Boolean(v) => format!("{v}"),
+ Literal::String(s) => format!("\"{s}\""),
+ Literal::Int(i, radix, type_) => typed_number(
+ type_,
+ match radix {
+ Radix::Octal => format!("{i:#x}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ ),
+ Literal::UInt(i, radix, type_) => typed_number(
+ type_,
+ match radix {
+ Radix::Octal => format!("{i:#x}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ ),
+ Literal::Float(string, type_) => typed_number(type_, string.clone()),
+
+ _ => unreachable!("Literal"),
+ }
+}
+
+macro_rules! impl_code_type_for_primitive {
+ ($T:ty, $class_name:literal) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ $class_name.into()
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, &literal)
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_primitive!(BooleanCodeType, "Boolean");
+impl_code_type_for_primitive!(StringCodeType, "String");
+impl_code_type_for_primitive!(Int8CodeType, "Byte");
+impl_code_type_for_primitive!(Int16CodeType, "Short");
+impl_code_type_for_primitive!(Int32CodeType, "Int");
+impl_code_type_for_primitive!(Int64CodeType, "Long");
+impl_code_type_for_primitive!(UInt8CodeType, "UByte");
+impl_code_type_for_primitive!(UInt16CodeType, "UShort");
+impl_code_type_for_primitive!(UInt32CodeType, "UInt");
+impl_code_type_for_primitive!(UInt64CodeType, "ULong");
+impl_code_type_for_primitive!(Float32CodeType, "Float");
+impl_code_type_for_primitive!(Float64CodeType, "Double");
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs
new file mode 100644
index 0000000000..f923f102c4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs
@@ -0,0 +1,29 @@
+/* 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, Literal};
+
+pub struct RecordCodeType {
+ id: String,
+}
+
+impl RecordCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for RecordCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs
new file mode 100644
index 0000000000..c372eab83b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs
@@ -0,0 +1,43 @@
+/* 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 anyhow::Result;
+use camino::{Utf8Path, Utf8PathBuf};
+use fs_err::{self as fs, File};
+use std::{io::Write, process::Command};
+
+pub mod gen_kotlin;
+pub use gen_kotlin::{generate_bindings, Config};
+mod test;
+
+use super::super::interface::ComponentInterface;
+pub use test::run_test;
+
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+) -> Result<()> {
+ let mut kt_file = full_bindings_path(config, out_dir);
+ fs::create_dir_all(&kt_file)?;
+ kt_file.push(format!("{}.kt", ci.namespace()));
+ let mut f = File::create(&kt_file)?;
+ write!(f, "{}", generate_bindings(config, ci)?)?;
+ if try_format_code {
+ if let Err(e) = Command::new("ktlint").arg("-F").arg(&kt_file).output() {
+ println!(
+ "Warning: Unable to auto-format {} using ktlint: {:?}",
+ kt_file.file_name().unwrap(),
+ e
+ )
+ }
+ }
+ Ok(())
+}
+
+fn full_bindings_path(config: &Config, out_dir: &Utf8Path) -> Utf8PathBuf {
+ let package_path: Utf8PathBuf = config.package_name().split('.').collect();
+ Utf8PathBuf::from(out_dir).join(package_path)
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
new file mode 100644
index 0000000000..8cfa2ce000
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterBoolean: FfiConverter<Boolean, Byte> {
+ override fun lift(value: Byte): Boolean {
+ return value.toInt() != 0
+ }
+
+ override fun read(buf: ByteBuffer): Boolean {
+ return lift(buf.get())
+ }
+
+ override fun lower(value: Boolean): Byte {
+ return if (value) 1.toByte() else 0.toByte()
+ }
+
+ override fun allocationSize(value: Boolean) = 1
+
+ override fun write(value: Boolean, buf: ByteBuffer) {
+ buf.put(lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
new file mode 100644
index 0000000000..3f5d72a608
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
@@ -0,0 +1,74 @@
+internal typealias Handle = Long
+internal class ConcurrentHandleMap<T>(
+ private val leftMap: MutableMap<Handle, T> = mutableMapOf(),
+ private val rightMap: MutableMap<T, Handle> = mutableMapOf()
+) {
+ private val lock = java.util.concurrent.locks.ReentrantLock()
+ private val currentHandle = AtomicLong(0L)
+ private val stride = 1L
+
+ fun insert(obj: T): Handle =
+ lock.withLock {
+ rightMap[obj] ?:
+ currentHandle.getAndAdd(stride)
+ .also { handle ->
+ leftMap[handle] = obj
+ rightMap[obj] = handle
+ }
+ }
+
+ fun get(handle: Handle) = lock.withLock {
+ leftMap[handle]
+ }
+
+ fun delete(handle: Handle) {
+ this.remove(handle)
+ }
+
+ fun remove(handle: Handle): T? =
+ lock.withLock {
+ leftMap.remove(handle)?.let { obj ->
+ rightMap.remove(obj)
+ obj
+ }
+ }
+}
+
+interface ForeignCallback : com.sun.jna.Callback {
+ public fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int
+}
+
+// Magic number for the Rust proxy to call using the same mechanism as every other method,
+// to free the callback once it's dropped by Rust.
+internal const val IDX_CALLBACK_FREE = 0
+
+public abstract class FfiConverterCallbackInterface<CallbackInterface>(
+ protected val foreignCallback: ForeignCallback
+): FfiConverter<CallbackInterface, Handle> {
+ private val handleMap = ConcurrentHandleMap<CallbackInterface>()
+
+ // Registers the foreign callback with the Rust side.
+ // This method is generated for each callback interface.
+ internal abstract fun register(lib: _UniFFILib)
+
+ fun drop(handle: Handle): RustBuffer.ByValue {
+ return handleMap.remove(handle).let { RustBuffer.ByValue() }
+ }
+
+ override fun lift(value: Handle): CallbackInterface {
+ return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug")
+ }
+
+ override fun read(buf: ByteBuffer) = lift(buf.getLong())
+
+ override fun lower(value: CallbackInterface) =
+ handleMap.insert(value).also {
+ assert(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." }
+ }
+
+ override fun allocationSize(value: CallbackInterface) = 8
+
+ override fun write(value: CallbackInterface, buf: ByteBuffer) {
+ buf.putLong(lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
new file mode 100644
index 0000000000..c7c4d2b973
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
@@ -0,0 +1,130 @@
+{%- let cbi = ci.get_callback_interface_definition(name).unwrap() %}
+{%- let type_name = cbi|type_name %}
+{%- let foreign_callback = format!("ForeignCallback{}", canonical_type_name) %}
+
+{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %}
+{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
+{{- self.add_import("java.util.concurrent.locks.ReentrantLock") }}
+{{- self.add_import("kotlin.concurrent.withLock") }}
+
+// Declaration and FfiConverters for {{ type_name }} Callback Interface
+
+public interface {{ type_name }} {
+ {% for meth in cbi.methods() -%}
+ fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %}: {{ return_type|type_name -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+}
+
+// The ForeignCallback that is passed to Rust.
+internal class {{ foreign_callback }} : ForeignCallback {
+ @Suppress("TooGenericExceptionCaught")
+ override fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int {
+ val cb = {{ ffi_converter_name }}.lift(handle)
+ return when (method) {
+ IDX_CALLBACK_FREE -> {
+ {{ ffi_converter_name }}.drop(handle)
+ // No return value.
+ // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ 0
+ }
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
+ {{ loop.index }} -> {
+ // Call the method, write to outBuf and return a status code
+ // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` for info
+ try {
+ {%- match meth.throws_type() %}
+ {%- when Some(error_type) %}
+ try {
+ val buffer = this.{{ method_name }}(cb, args)
+ // Success
+ outBuf.setValue(buffer)
+ 1
+ } catch (e: {{ error_type|type_name }}) {
+ // Expected error
+ val buffer = {{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e)
+ outBuf.setValue(buffer)
+ -2
+ }
+ {%- else %}
+ val buffer = this.{{ method_name }}(cb, args)
+ // Success
+ outBuf.setValue(buffer)
+ 1
+ {%- endmatch %}
+ } catch (e: Throwable) {
+ // Unexpected error
+ try {
+ // Try to serialize the error into a string
+ outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString()))
+ } catch (e: Throwable) {
+ // If that fails, then it's time to give up and just return
+ }
+ -1
+ }
+ }
+ {% endfor %}
+ else -> {
+ // An unexpected error happened.
+ // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ try {
+ // Try to serialize the error into a string
+ outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index"))
+ } catch (e: Throwable) {
+ // If that fails, then it's time to give up and just return
+ }
+ -1
+ }
+ }
+ }
+
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
+ private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, args: RustBuffer.ByValue): RustBuffer.ByValue =
+ try {
+ {#- Unpacking args from the RustBuffer #}
+ {%- if meth.arguments().len() != 0 -%}
+ {#- Calling the concrete callback object #}
+ val buf = args.asByteBuffer() ?: throw InternalException("No ByteBuffer in RustBuffer; this is a Uniffi bug")
+ kotlinCallbackInterface.{{ meth.name()|fn_name }}(
+ {% for arg in meth.arguments() -%}
+ {{ arg|read_fn }}(buf)
+ {%- if !loop.last %}, {% endif %}
+ {% endfor -%}
+ )
+ {% else %}
+ kotlinCallbackInterface.{{ meth.name()|fn_name }}()
+ {% endif -%}
+
+ {#- Packing up the return value into a RustBuffer #}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) -%}
+ .let {
+ {{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(it)
+ }
+ {%- else -%}
+ .let { RustBuffer.ByValue() }
+ {% endmatch -%}
+ // TODO catch errors and report them back to Rust.
+ // https://github.com/mozilla/uniffi-rs/issues/351
+ } finally {
+ RustBuffer.free(args)
+ }
+
+ {% endfor %}
+}
+
+// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust.
+public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ type_name }}>(
+ foreignCallback = {{ foreign_callback }}()
+) {
+ override fun register(lib: _UniFFILib) {
+ rustCall() { status ->
+ lib.{{ cbi.ffi_init_callback().name() }}(this.foreignCallback, status)
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
new file mode 100644
index 0000000000..c7807f2f33
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
@@ -0,0 +1,62 @@
+{%- match kotlin_config.custom_types.get(name.as_str()) %}
+{%- when None %}
+{#- Define the type using typealiases to the builtin #}
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias {{ name }} = {{ builtin|type_name }}
+public typealias {{ ffi_converter_name }} = {{ builtin|ffi_converter_name }}
+
+{%- when Some with (config) %}
+
+{%- let ffi_type_name=builtin.ffi_type().borrow()|ffi_type_name %}
+
+{# When the config specifies a different type name, create a typealias for it #}
+{%- match config.type_name %}
+{%- when Some(concrete_type_name) %}
+/**
+ * Typealias from the type name used in the UDL file to the custom type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias {{ name }} = {{ concrete_type_name }}
+{%- else %}
+{%- endmatch %}
+
+{%- match config.imports %}
+{%- when Some(imports) %}
+{%- for import_name in imports %}
+{{ self.add_import(import_name) }}
+{%- endfor %}
+{%- else %}
+{%- endmatch %}
+
+public object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_name }}> {
+ override fun lift(value: {{ ffi_type_name }}): {{ name }} {
+ val builtinValue = {{ builtin|lift_fn }}(value)
+ return {{ config.into_custom.render("builtinValue") }}
+ }
+
+ override fun lower(value: {{ name }}): {{ ffi_type_name }} {
+ val builtinValue = {{ config.from_custom.render("value") }}
+ return {{ builtin|lower_fn }}(builtinValue)
+ }
+
+ override fun read(buf: ByteBuffer): {{ name }} {
+ val builtinValue = {{ builtin|read_fn }}(buf)
+ return {{ config.into_custom.render("builtinValue") }}
+ }
+
+ override fun allocationSize(value: {{ name }}): Int {
+ val builtinValue = {{ config.from_custom.render("value") }}
+ return {{ builtin|allocation_size_fn }}(builtinValue)
+ }
+
+ override fun write(value: {{ name }}, buf: ByteBuffer) {
+ val builtinValue = {{ config.from_custom.render("value") }}
+ {{ builtin|write_fn }}(builtinValue, buf)
+ }
+}
+{%- endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
new file mode 100644
index 0000000000..4237c6f9a8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
@@ -0,0 +1,36 @@
+public object FfiConverterDuration: FfiConverterRustBuffer<java.time.Duration> {
+ override fun read(buf: ByteBuffer): java.time.Duration {
+ // Type mismatch (should be u64) but we check for overflow/underflow below
+ val seconds = buf.getLong()
+ // Type mismatch (should be u32) but we check for overflow/underflow below
+ val nanoseconds = buf.getInt().toLong()
+ if (seconds < 0) {
+ throw java.time.DateTimeException("Duration exceeds minimum or maximum value supported by uniffi")
+ }
+ if (nanoseconds < 0) {
+ throw java.time.DateTimeException("Duration nanoseconds exceed minimum or maximum supported by uniffi")
+ }
+ return java.time.Duration.ofSeconds(seconds, nanoseconds)
+ }
+
+ // 8 bytes for seconds, 4 bytes for nanoseconds
+ override fun allocationSize(value: java.time.Duration) = 12
+
+ override fun write(value: java.time.Duration, buf: ByteBuffer) {
+ if (value.seconds < 0) {
+ // Rust does not support negative Durations
+ throw IllegalArgumentException("Invalid duration, must be non-negative")
+ }
+
+ if (value.nano < 0) {
+ // Java docs provide guarantee that nano will always be positive, so this should be impossible
+ // See: https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html
+ throw IllegalArgumentException("Invalid duration, nano value must be non-negative")
+ }
+
+ // Type mismatch (should be u64) but since Rust doesn't support negative durations we should be OK
+ buf.putLong(value.seconds)
+ // Type mismatch (should be u32) but since values will always be between 0 and 999,999,999 it should be OK
+ buf.putInt(value.nano)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
new file mode 100644
index 0000000000..be198ac7b9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
@@ -0,0 +1,107 @@
+{#
+// Kotlin's `enum class` construct doesn't support variants with associated data,
+// but is a little nicer for consumers than its `sealed class` enum pattern.
+// So, we switch here, using `enum class` for enums with no associated data
+// and `sealed class` for the general case.
+#}
+{%- let e = ci.get_enum_definition(name).unwrap() %}
+
+{%- if e.is_flat() %}
+
+enum class {{ type_name }} {
+ {% for variant in e.variants() -%}
+ {{ variant.name()|enum_variant }}{% if loop.last %};{% else %},{% endif %}
+ {%- endfor %}
+}
+
+public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
+ override fun read(buf: ByteBuffer) = try {
+ {{ type_name }}.values()[buf.getInt() - 1]
+ } catch (e: IndexOutOfBoundsException) {
+ throw RuntimeException("invalid enum value, something is very wrong!!", e)
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = 4
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ buf.putInt(value.ordinal + 1)
+ }
+}
+
+{% else %}
+
+sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} {
+ {% for variant in e.variants() -%}
+ {% if !variant.has_fields() -%}
+ object {{ variant.name()|class_name }} : {{ type_name }}()
+ {% else -%}
+ data class {{ variant.name()|class_name }}(
+ {% for field in variant.fields() -%}
+ val {{ field.name()|var_name }}: {{ field|type_name}}{% if loop.last %}{% else %}, {% endif %}
+ {% endfor -%}
+ ) : {{ type_name }}()
+ {%- endif %}
+ {% endfor %}
+
+ {% if contains_object_references %}
+ @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
+ override fun destroy() {
+ when(this) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|class_name }} -> {
+ {%- if variant.has_fields() %}
+ {% call kt::destroy_fields(variant) %}
+ {% else -%}
+ // Nothing to destroy
+ {%- endif %}
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+ {% endif %}
+}
+
+public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}>{
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ return when(buf.getInt()) {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} -> {{ type_name }}.{{ variant.name()|class_name }}{% if variant.has_fields() %}(
+ {% for field in variant.fields() -%}
+ {{ field|read_fn }}(buf),
+ {% endfor -%}
+ ){%- endif -%}
+ {%- endfor %}
+ else -> throw RuntimeException("invalid enum value, something is very wrong!!")
+ }
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|class_name }} -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ {%- for field in variant.fields() %}
+ + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
+ {%- endfor %}
+ )
+ }
+ {%- endfor %}
+ }
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|class_name }} -> {
+ buf.putInt({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ Unit
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+}
+
+{% endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
new file mode 100644
index 0000000000..9ddef9bb73
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
@@ -0,0 +1,109 @@
+{%- let e = ci.get_error_definition(name).unwrap() %}
+
+{% if e.is_flat() %}
+sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} {
+ // Each variant is a nested class
+ // Flat enums carries a string error message, so no special implementation is necessary.
+ {% for variant in e.variants() -%}
+ class {{ variant.name()|exception_name }}(message: String) : {{ type_name }}(message)
+ {% endfor %}
+
+ companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ e|lift_fn }}(error_buf)
+ }
+}
+{%- else %}
+sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} {
+ // Each variant is a nested class
+ {% for variant in e.variants() -%}
+ {%- let variant_name = variant.name()|exception_name %}
+ class {{ variant_name }}(
+ {% for field in variant.fields() -%}
+ val {{ field.name()|var_name }}: {{ field|type_name}}{% if loop.last %}{% else %}, {% endif %}
+ {% endfor -%}
+ ) : {{ type_name }}() {
+ override val message
+ get() = "{%- for field in variant.fields() %}{{ field.name()|var_name|unquote }}=${ {{field.name()|var_name }} }{% if !loop.last %}, {% endif %}{% endfor %}"
+ }
+ {% endfor %}
+
+ companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ e|lift_fn }}(error_buf)
+ }
+
+ {% if contains_object_references %}
+ @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
+ override fun destroy() {
+ when(this) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|exception_name }} -> {
+ {%- if variant.has_fields() %}
+ {% call kt::destroy_fields(variant) %}
+ {% else -%}
+ // Nothing to destroy
+ {%- endif %}
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+ {% endif %}
+}
+{%- endif %}
+
+public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}> {
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ {% if e.is_flat() %}
+ return when(buf.getInt()) {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} -> {{ type_name }}.{{ variant.name()|exception_name }}({{ TypeIdentifier::String.borrow()|read_fn }}(buf))
+ {%- endfor %}
+ else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
+ }
+ {% else %}
+
+ return when(buf.getInt()) {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} -> {{ type_name }}.{{ variant.name()|exception_name }}({% if variant.has_fields() %}
+ {% for field in variant.fields() -%}
+ {{ field|read_fn }}(buf),
+ {% endfor -%}
+ {%- endif -%})
+ {%- endfor %}
+ else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
+ }
+ {%- endif %}
+ }
+
+ override fun allocationSize(value: {{ type_name }}): Int {
+ {%- if e.is_flat() %}
+ return 4
+ {%- else %}
+ return when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|exception_name }} -> (
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ 4
+ {%- for field in variant.fields() %}
+ + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
+ {%- endfor %}
+ )
+ {%- endfor %}
+ }
+ {%- endif %}
+ }
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|exception_name }} -> {
+ buf.putInt({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ Unit
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
new file mode 100644
index 0000000000..21d96dcdf1
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
@@ -0,0 +1,9 @@
+{%- let package_name=self.external_type_package_name(crate_name) %}
+{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %}
+{%- let fully_qualified_ffi_converter_name = "{}.FfiConverterType{}"|format(package_name, name) %}
+{%- let fully_qualified_rustbuffer_name = "{}.RustBuffer"|format(package_name) %}
+{%- let local_rustbuffer_name = "RustBuffer{}"|format(name) %}
+
+{{- self.add_import(fully_qualified_type_name) }}
+{{- self.add_import(fully_qualified_ffi_converter_name) }}
+{{ self.add_import_as(fully_qualified_rustbuffer_name, local_rustbuffer_name) }}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
new file mode 100644
index 0000000000..3b2c9d225a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
@@ -0,0 +1,71 @@
+// The FfiConverter interface handles converter types to and from the FFI
+//
+// All implementing objects should be public to support external types. When a
+// type is external we need to import it's FfiConverter.
+public interface FfiConverter<KotlinType, FfiType> {
+ // Convert an FFI type to a Kotlin type
+ fun lift(value: FfiType): KotlinType
+
+ // Convert an Kotlin type to an FFI type
+ fun lower(value: KotlinType): FfiType
+
+ // Read a Kotlin type from a `ByteBuffer`
+ fun read(buf: ByteBuffer): KotlinType
+
+ // Calculate bytes to allocate when creating a `RustBuffer`
+ //
+ // This must return at least as many bytes as the write() function will
+ // write. It can return more bytes than needed, for example when writing
+ // Strings we can't know the exact bytes needed until we the UTF-8
+ // encoding, so we pessimistically allocate the largest size possible (3
+ // bytes per codepoint). Allocating extra bytes is not really a big deal
+ // because the `RustBuffer` is short-lived.
+ fun allocationSize(value: KotlinType): Int
+
+ // Write a Kotlin type to a `ByteBuffer`
+ fun write(value: KotlinType, buf: ByteBuffer)
+
+ // Lower a value into a `RustBuffer`
+ //
+ // This method lowers a value into a `RustBuffer` rather than the normal
+ // FfiType. It's used by the callback interface code. Callback interface
+ // returns are always serialized into a `RustBuffer` regardless of their
+ // normal FFI type.
+ fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue {
+ val rbuf = RustBuffer.alloc(allocationSize(value))
+ try {
+ val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+ write(value, bbuf)
+ rbuf.writeField("len", bbuf.position())
+ return rbuf
+ } catch (e: Throwable) {
+ RustBuffer.free(rbuf)
+ throw e
+ }
+ }
+
+ // Lift a value from a `RustBuffer`.
+ //
+ // This here mostly because of the symmetry with `lowerIntoRustBuffer()`.
+ // It's currently only used by the `FfiConverterRustBuffer` class below.
+ fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType {
+ val byteBuf = rbuf.asByteBuffer()!!
+ try {
+ val item = read(byteBuf)
+ if (byteBuf.hasRemaining()) {
+ throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!")
+ }
+ return item
+ } finally {
+ RustBuffer.free(rbuf)
+ }
+ }
+}
+
+// FfiConverter that uses `RustBuffer` as the FfiType
+public interface FfiConverterRustBuffer<KotlinType>: FfiConverter<KotlinType, RustBuffer.ByValue> {
+ override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value)
+ override fun lower(value: KotlinType) = lowerIntoRustBuffer(value)
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
new file mode 100644
index 0000000000..eafec5d122
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterFloat: FfiConverter<Float, Float> {
+ override fun lift(value: Float): Float {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Float {
+ return buf.getFloat()
+ }
+
+ override fun lower(value: Float): Float {
+ return value
+ }
+
+ override fun allocationSize(value: Float) = 4
+
+ override fun write(value: Float, buf: ByteBuffer) {
+ buf.putFloat(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
new file mode 100644
index 0000000000..9fc2892c95
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterDouble: FfiConverter<Double, Double> {
+ override fun lift(value: Double): Double {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Double {
+ return buf.getDouble()
+ }
+
+ override fun lower(value: Double): Double {
+ return value
+ }
+
+ override fun allocationSize(value: Double) = 8
+
+ override fun write(value: Double, buf: ByteBuffer) {
+ buf.putDouble(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
new file mode 100644
index 0000000000..d7e61ef4c7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
@@ -0,0 +1,66 @@
+// A handful of classes and functions to support the generated data structures.
+// This would be a good candidate for isolating in its own ffi-support lib.
+// Error runtime.
+@Structure.FieldOrder("code", "error_buf")
+internal open class RustCallStatus : Structure() {
+ @JvmField var code: Int = 0
+ @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
+
+ fun isSuccess(): Boolean {
+ return code == 0
+ }
+
+ fun isError(): Boolean {
+ return code == 1
+ }
+
+ fun isPanic(): Boolean {
+ return code == 2
+ }
+}
+
+class InternalException(message: String) : Exception(message)
+
+// Each top-level error class has a companion object that can lift the error from the call status's rust buffer
+interface CallStatusErrorHandler<E> {
+ fun lift(error_buf: RustBuffer.ByValue): E;
+}
+
+// Helpers for calling Rust
+// In practice we usually need to be synchronized to call this safely, so it doesn't
+// synchronize itself
+
+// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err
+private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U {
+ var status = RustCallStatus();
+ val return_value = callback(status)
+ if (status.isSuccess()) {
+ return return_value
+ } else if (status.isError()) {
+ throw errorHandler.lift(status.error_buf)
+ } else if (status.isPanic()) {
+ // when the rust code sees a panic, it tries to construct a rustbuffer
+ // with the message. but if that code panics, then it just sends back
+ // an empty buffer.
+ if (status.error_buf.len > 0) {
+ throw InternalException({{ TypeIdentifier::String.borrow()|lift_fn }}(status.error_buf))
+ } else {
+ throw InternalException("Rust panic")
+ }
+ } else {
+ throw InternalException("Unknown rust call status: $status.code")
+ }
+}
+
+// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
+object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
+ override fun lift(error_buf: RustBuffer.ByValue): InternalException {
+ RustBuffer.free(error_buf)
+ return InternalException("Unexpected CALL_ERROR")
+ }
+}
+
+// Call a rust function that returns a plain value
+private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U {
+ return rustCallWithError(NullCallStatusErrorHandler, callback);
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
new file mode 100644
index 0000000000..75564276be
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterShort: FfiConverter<Short, Short> {
+ override fun lift(value: Short): Short {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Short {
+ return buf.getShort()
+ }
+
+ override fun lower(value: Short): Short {
+ return value
+ }
+
+ override fun allocationSize(value: Short) = 2
+
+ override fun write(value: Short, buf: ByteBuffer) {
+ buf.putShort(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
new file mode 100644
index 0000000000..b7a8131c8b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterInt: FfiConverter<Int, Int> {
+ override fun lift(value: Int): Int {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Int {
+ return buf.getInt()
+ }
+
+ override fun lower(value: Int): Int {
+ return value
+ }
+
+ override fun allocationSize(value: Int) = 4
+
+ override fun write(value: Int, buf: ByteBuffer) {
+ buf.putInt(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
new file mode 100644
index 0000000000..601cfc7c2c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterLong: FfiConverter<Long, Long> {
+ override fun lift(value: Long): Long {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Long {
+ return buf.getLong()
+ }
+
+ override fun lower(value: Long): Long {
+ return value
+ }
+
+ override fun allocationSize(value: Long) = 8
+
+ override fun write(value: Long, buf: ByteBuffer) {
+ buf.putLong(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
new file mode 100644
index 0000000000..9237768dbf
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterByte: FfiConverter<Byte, Byte> {
+ override fun lift(value: Byte): Byte {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Byte {
+ return buf.get()
+ }
+
+ override fun lower(value: Byte): Byte {
+ return value
+ }
+
+ override fun allocationSize(value: Byte) = 1
+
+ override fun write(value: Byte, buf: ByteBuffer) {
+ buf.put(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
new file mode 100644
index 0000000000..c8cbecb68b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
@@ -0,0 +1,35 @@
+{%- let key_type_name = key_type|type_name %}
+{%- let value_type_name = value_type|type_name %}
+public object {{ ffi_converter_name }}: FfiConverterRustBuffer<Map<{{ key_type_name }}, {{ value_type_name }}>> {
+ override fun read(buf: ByteBuffer): Map<{{ key_type_name }}, {{ value_type_name }}> {
+ // TODO: Once Kotlin's `buildMap` API is stabilized we should use it here.
+ val items : MutableMap<{{ key_type_name }}, {{ value_type_name }}> = mutableMapOf()
+ val len = buf.getInt()
+ repeat(len) {
+ val k = {{ key_type|read_fn }}(buf)
+ val v = {{ value_type|read_fn }}(buf)
+ items[k] = v
+ }
+ return items
+ }
+
+ override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int {
+ val spaceForMapSize = 4
+ val spaceForChildren = value.map { (k, v) ->
+ {{ key_type|allocation_size_fn }}(k) +
+ {{ value_type|allocation_size_fn }}(v)
+ }.sum()
+ return spaceForMapSize + spaceForChildren
+ }
+
+ override fun write(value: Map<{{ key_type_name }}, {{ value_type_name }}>, buf: ByteBuffer) {
+ buf.putInt(value.size)
+ // The parens on `(k, v)` here ensure we're calling the right method,
+ // which is important for compatibility with older android devices.
+ // Ref https://blog.danlew.net/2017/03/16/kotlin-puzzler-whose-line-is-it-anyways/
+ value.forEach { (k, v) ->
+ {{ key_type|write_fn }}(k, buf)
+ {{ value_type|write_fn }}(v, buf)
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
new file mode 100644
index 0000000000..df96ee5cbb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
@@ -0,0 +1,40 @@
+@Synchronized
+private fun findLibraryName(componentName: String): String {
+ val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride")
+ if (libOverride != null) {
+ return libOverride
+ }
+ return "{{ config.cdylib_name() }}"
+}
+
+private inline fun <reified Lib : Library> loadIndirect(
+ componentName: String
+): Lib {
+ return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
+}
+
+// A JNA Library to expose the extern-C FFI definitions.
+// This is an implementation detail which will be called internally by the public API.
+
+internal interface _UniFFILib : Library {
+ companion object {
+ internal val INSTANCE: _UniFFILib by lazy {
+ loadIndirect<_UniFFILib>(componentName = "{{ ci.namespace() }}")
+ {% let initialization_fns = self.initialization_fns() %}
+ {%- if !initialization_fns.is_empty() -%}
+ .also { lib: _UniFFILib ->
+ {% for fn in initialization_fns -%}
+ {{ fn }}(lib)
+ {% endfor -%}
+ }
+ {% endif %}
+ }
+ }
+
+ {% for func in ci.iter_ffi_function_definitions() -%}
+ fun {{ func.name() }}(
+ {%- call kt::arg_list_ffi_decl(func) %}
+ ){%- match func.return_type() -%}{%- when Some with (type_) %}: {{ type_.borrow()|ffi_type_name }}{% when None %}: Unit{% endmatch %}
+
+ {% endfor %}
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
new file mode 100644
index 0000000000..b9352c690f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
@@ -0,0 +1,161 @@
+// Interface implemented by anything that can contain an object reference.
+//
+// Such types expose a `destroy()` method that must be called to cleanly
+// dispose of the contained objects. Failure to call this method may result
+// in memory leaks.
+//
+// The easiest way to ensure this method is called is to use the `.use`
+// helper method to execute a block and destroy the object at the end.
+interface Disposable {
+ fun destroy()
+ companion object {
+ fun destroy(vararg args: Any?) {
+ args.filterIsInstance<Disposable>()
+ .forEach(Disposable::destroy)
+ }
+ }
+}
+
+inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
+ try {
+ block(this)
+ } finally {
+ try {
+ // N.B. our implementation is on the nullable type `Disposable?`.
+ this?.destroy()
+ } catch (e: Throwable) {
+ // swallow
+ }
+ }
+
+// The base class for all UniFFI Object types.
+//
+// This class provides core operations for working with the Rust `Arc<T>` pointer to
+// the live Rust struct on the other side of the FFI.
+//
+// There's some subtlety here, because we have to be careful not to operate on a Rust
+// struct after it has been dropped, and because we must expose a public API for freeing
+// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
+//
+// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct.
+// Method calls need to read this pointer from the object's state and pass it in to
+// the Rust FFI.
+//
+// * When an `FFIObject` is no longer needed, its pointer should be passed to a
+// special destructor function provided by the Rust FFI, which will drop the
+// underlying Rust struct.
+//
+// * Given an `FFIObject` instance, calling code is expected to call the special
+// `destroy` method in order to free it after use, either by calling it explicitly
+// or by using a higher-level helper like the `use` method. Failing to do so will
+// leak the underlying Rust struct.
+//
+// * We can't assume that calling code will do the right thing, and must be prepared
+// to handle Kotlin method calls executing concurrently with or even after a call to
+// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
+//
+// * We must never allow Rust code to operate on the underlying Rust struct after
+// the destructor has been called, and must never call the destructor more than once.
+// Doing so may trigger memory unsafety.
+//
+// If we try to implement this with mutual exclusion on access to the pointer, there is the
+// possibility of a race between a method call and a concurrent call to `destroy`:
+//
+// * Thread A starts a method call, reads the value of the pointer, but is interrupted
+// before it can pass the pointer over the FFI to Rust.
+// * Thread B calls `destroy` and frees the underlying Rust struct.
+// * Thread A resumes, passing the already-read pointer value to Rust and triggering
+// a use-after-free.
+//
+// One possible solution would be to use a `ReadWriteLock`, with each method call taking
+// a read lock (and thus allowed to run concurrently) and the special `destroy` method
+// taking a write lock (and thus blocking on live method calls). However, we aim not to
+// generate methods with any hidden blocking semantics, and a `destroy` method that might
+// block if called incorrectly seems to meet that bar.
+//
+// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track
+// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
+// has been called. These are updated according to the following rules:
+//
+// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
+// The initial value for the flag is false.
+//
+// * At the start of each method call, we atomically check the counter.
+// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
+// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
+//
+// * At the end of each method call, we atomically decrement and check the counter.
+// If it has reached zero then we destroy the underlying Rust struct.
+//
+// * When `destroy` is called, we atomically flip the flag from false to true.
+// If the flag was already true we silently fail.
+// Otherwise we atomically decrement and check the counter.
+// If it has reached zero then we destroy the underlying Rust struct.
+//
+// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
+// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
+//
+// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
+// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
+// of the underlying Rust code.
+//
+// In the future we may be able to replace some of this with automatic finalization logic, such as using
+// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is
+// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also
+// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1],
+// so there would still be some complexity here).
+//
+// Sigh...all of this for want of a robust finalization mechanism.
+//
+// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
+//
+abstract class FFIObject(
+ protected val pointer: Pointer
+): Disposable, AutoCloseable {
+
+ private val wasDestroyed = AtomicBoolean(false)
+ private val callCounter = AtomicLong(1)
+
+ open protected fun freeRustArcPtr() {
+ // To be overridden in subclasses.
+ }
+
+ override fun destroy() {
+ // Only allow a single call to this method.
+ // TODO: maybe we should log a warning if called more than once?
+ if (this.wasDestroyed.compareAndSet(false, true)) {
+ // This decrement always matches the initial count of 1 given at creation time.
+ if (this.callCounter.decrementAndGet() == 0L) {
+ this.freeRustArcPtr()
+ }
+ }
+ }
+
+ @Synchronized
+ override fun close() {
+ this.destroy()
+ }
+
+ internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
+ // Check and increment the call counter, to keep the object alive.
+ // This needs a compare-and-set retry loop in case of concurrent updates.
+ do {
+ val c = this.callCounter.get()
+ if (c == 0L) {
+ throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
+ }
+ if (c == Long.MAX_VALUE) {
+ throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
+ }
+ } while (! this.callCounter.compareAndSet(c, c + 1L))
+ // Now we can safely do the method call without the pointer being freed concurrently.
+ try {
+ return block(this.pointer)
+ } finally {
+ // This decrement always matches the increment we performed above.
+ if (this.callCounter.decrementAndGet() == 0L) {
+ this.freeRustArcPtr()
+ }
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
new file mode 100644
index 0000000000..5d654d9fae
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
@@ -0,0 +1,100 @@
+{%- let obj = ci.get_object_definition(name).unwrap() %}
+{%- if self.include_once_check("ObjectRuntime.kt") %}{% include "ObjectRuntime.kt" %}{% endif %}
+{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
+{{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
+
+public interface {{ type_name }}Interface {
+ {% for meth in obj.methods() -%}
+ {%- match meth.throws_type() -%}
+ {%- when Some with (throwable) %}
+ @Throws({{ throwable|type_name }}::class)
+ {%- else -%}
+ {%- endmatch %}
+ fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %}: {{ return_type|type_name -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+}
+
+class {{ type_name }}(
+ pointer: Pointer
+) : FFIObject(pointer), {{ type_name }}Interface {
+
+ {%- match obj.primary_constructor() %}
+ {%- when Some with (cons) %}
+ constructor({% call kt::arg_list_decl(cons) -%}) :
+ this({% call kt::to_ffi_call(cons) %})
+ {%- when None %}
+ {%- endmatch %}
+
+ /**
+ * Disconnect the object from the underlying Rust object.
+ *
+ * It can be called more than once, but once called, interacting with the object
+ * causes an `IllegalStateException`.
+ *
+ * Clients **must** call this method once done with the object, or cause a memory leak.
+ */
+ override protected fun freeRustArcPtr() {
+ rustCall() { status ->
+ _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.pointer, status)
+ }
+ }
+
+ {% for meth in obj.methods() -%}
+ {%- match meth.throws_type() -%}
+ {%- when Some with (throwable) %}
+ @Throws({{ throwable|type_name }}::class)
+ {%- else -%}
+ {%- endmatch %}
+ {%- match meth.return_type() -%}
+
+ {%- when Some with (return_type) -%}
+ override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name }} =
+ callWithPointer {
+ {%- call kt::to_ffi_call_with_prefix("it", meth) %}
+ }.let {
+ {{ return_type|lift_fn }}(it)
+ }
+
+ {%- when None -%}
+ override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) =
+ callWithPointer {
+ {%- call kt::to_ffi_call_with_prefix("it", meth) %}
+ }
+ {% endmatch %}
+ {% endfor %}
+
+ {% if !obj.alternate_constructors().is_empty() -%}
+ companion object {
+ {% for cons in obj.alternate_constructors() -%}
+ fun {{ cons.name()|fn_name }}({% call kt::arg_list_decl(cons) %}): {{ type_name }} =
+ {{ type_name }}({% call kt::to_ffi_call(cons) %})
+ {% endfor %}
+ }
+ {% endif %}
+}
+
+public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
+ override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it }
+
+ override fun lift(value: Pointer): {{ type_name }} {
+ return {{ type_name }}(value)
+ }
+
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ // The Rust code always writes pointers as 8 bytes, and will
+ // fail to compile if they don't fit.
+ return lift(Pointer(buf.getLong()))
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = 8
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ // The Rust code always expects pointers written as 8 bytes,
+ // and will fail to compile if they don't fit.
+ buf.putLong(Pointer.nativeValue(lower(value)))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
new file mode 100644
index 0000000000..6830b51863
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
@@ -0,0 +1,27 @@
+{%- let inner_type_name = inner_type|type_name %}
+
+public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_name }}?> {
+ override fun read(buf: ByteBuffer): {{ inner_type_name }}? {
+ if (buf.get().toInt() == 0) {
+ return null
+ }
+ return {{ inner_type|read_fn }}(buf)
+ }
+
+ override fun allocationSize(value: {{ inner_type_name }}?): Int {
+ if (value == null) {
+ return 1
+ } else {
+ return 1 + {{ inner_type|allocation_size_fn }}(value)
+ }
+ }
+
+ override fun write(value: {{ inner_type_name }}?, buf: ByteBuffer) {
+ if (value == null) {
+ buf.put(0)
+ } else {
+ buf.put(1)
+ {{ inner_type|write_fn }}(value, buf)
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
new file mode 100644
index 0000000000..a17f4d42eb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
@@ -0,0 +1,41 @@
+{%- let rec = ci.get_record_definition(name).unwrap() %}
+
+data class {{ type_name }} (
+ {%- for field in rec.fields() %}
+ var {{ field.name()|var_name }}: {{ field|type_name -}}
+ {%- match field.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|render_literal(field) }}
+ {%- else %}
+ {%- endmatch -%}
+ {% if !loop.last %}, {% endif %}
+ {%- endfor %}
+) {% if contains_object_references %}: Disposable {% endif %}{
+ {% if contains_object_references %}
+ @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
+ override fun destroy() {
+ {% call kt::destroy_fields(rec) %}
+ }
+ {% endif %}
+}
+
+public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ return {{ type_name }}(
+ {%- for field in rec.fields() %}
+ {{ field|read_fn }}(buf),
+ {%- endfor %}
+ )
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = (
+ {%- for field in rec.fields() %}
+ {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%}
+ {%- endfor %}
+ )
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ {%- for field in rec.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
new file mode 100644
index 0000000000..6cc218e940
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
@@ -0,0 +1,66 @@
+// This is a helper for safely working with byte buffers returned from the Rust code.
+// A rust-owned buffer is represented by its capacity, its current length, and a
+// pointer to the underlying data.
+
+@Structure.FieldOrder("capacity", "len", "data")
+open class RustBuffer : Structure() {
+ @JvmField var capacity: Int = 0
+ @JvmField var len: Int = 0
+ @JvmField var data: Pointer? = null
+
+ class ByValue : RustBuffer(), Structure.ByValue
+ class ByReference : RustBuffer(), Structure.ByReference
+
+ companion object {
+ internal fun alloc(size: Int = 0) = rustCall() { status ->
+ _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status).also {
+ if(it.data == null) {
+ throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
+ }
+ }
+ }
+
+ internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
+ _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status)
+ }
+ }
+
+ @Suppress("TooGenericExceptionThrown")
+ fun asByteBuffer() =
+ this.data?.getByteBuffer(0, this.len.toLong())?.also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+}
+
+/**
+ * The equivalent of the `*mut RustBuffer` type.
+ * Required for callbacks taking in an out pointer.
+ *
+ * Size is the sum of all values in the struct.
+ */
+class RustBufferByReference : ByReference(16) {
+ /**
+ * Set the pointed-to `RustBuffer` to the given value.
+ */
+ fun setValue(value: RustBuffer.ByValue) {
+ // NOTE: The offsets are as they are in the C-like struct.
+ val pointer = getPointer()
+ pointer.setInt(0, value.capacity)
+ pointer.setInt(4, value.len)
+ pointer.setPointer(8, value.data)
+ }
+}
+
+// This is a helper for safely passing byte references into the rust code.
+// It's not actually used at the moment, because there aren't many things that you
+// can take a direct pointer to in the JVM, and if we're going to copy something
+// then we might as well copy it into a `RustBuffer`. But it's here for API
+// completeness.
+
+@Structure.FieldOrder("len", "data")
+open class ForeignBytes : Structure() {
+ @JvmField var len: Int = 0
+ @JvmField var data: Pointer? = null
+
+ class ByValue : ForeignBytes(), Structure.ByValue
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
new file mode 100644
index 0000000000..b5c699ba3b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
@@ -0,0 +1,23 @@
+{%- let inner_type_name = inner_type|type_name %}
+
+public object {{ ffi_converter_name }}: FfiConverterRustBuffer<List<{{ inner_type_name }}>> {
+ override fun read(buf: ByteBuffer): List<{{ inner_type_name }}> {
+ val len = buf.getInt()
+ return List<{{ inner_type_name }}>(len) {
+ {{ inner_type|read_fn }}(buf)
+ }
+ }
+
+ override fun allocationSize(value: List<{{ inner_type_name }}>): Int {
+ val sizeForLength = 4
+ val sizeForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum()
+ return sizeForLength + sizeForItems
+ }
+
+ override fun write(value: List<{{ inner_type_name }}>, buf: ByteBuffer) {
+ buf.putInt(value.size)
+ value.forEach {
+ {{ inner_type|write_fn }}(it, buf)
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
new file mode 100644
index 0000000000..d3443215c1
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
@@ -0,0 +1,45 @@
+public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
+ // Note: we don't inherit from FfiConverterRustBuffer, because we use a
+ // special encoding when lowering/lifting. We can use `RustBuffer.len` to
+ // store our length and avoid writing it out to the buffer.
+ override fun lift(value: RustBuffer.ByValue): String {
+ try {
+ val byteArr = ByteArray(value.len)
+ value.asByteBuffer()!!.get(byteArr)
+ return byteArr.toString(Charsets.UTF_8)
+ } finally {
+ RustBuffer.free(value)
+ }
+ }
+
+ override fun read(buf: ByteBuffer): String {
+ val len = buf.getInt()
+ val byteArr = ByteArray(len)
+ buf.get(byteArr)
+ return byteArr.toString(Charsets.UTF_8)
+ }
+
+ override fun lower(value: String): RustBuffer.ByValue {
+ val byteArr = value.toByteArray(Charsets.UTF_8)
+ // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us
+ // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`.
+ val rbuf = RustBuffer.alloc(byteArr.size)
+ rbuf.asByteBuffer()!!.put(byteArr)
+ return rbuf
+ }
+
+ // We aren't sure exactly how many bytes our string will be once it's UTF-8
+ // encoded. Allocate 3 bytes per unicode codepoint which will always be
+ // enough.
+ override fun allocationSize(value: String): Int {
+ val sizeForLength = 4
+ val sizeForString = value.length * 3
+ return sizeForLength + sizeForString
+ }
+
+ override fun write(value: String, buf: ByteBuffer) {
+ val byteArr = value.toByteArray(Charsets.UTF_8)
+ buf.putInt(byteArr.size)
+ buf.put(byteArr)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
new file mode 100644
index 0000000000..21069d7ce8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
@@ -0,0 +1,38 @@
+public object FfiConverterTimestamp: FfiConverterRustBuffer<java.time.Instant> {
+ override fun read(buf: ByteBuffer): java.time.Instant {
+ val seconds = buf.getLong()
+ // Type mismatch (should be u32) but we check for overflow/underflow below
+ val nanoseconds = buf.getInt().toLong()
+ if (nanoseconds < 0) {
+ throw java.time.DateTimeException("Instant nanoseconds exceed minimum or maximum supported by uniffi")
+ }
+ if (seconds >= 0) {
+ return java.time.Instant.EPOCH.plus(java.time.Duration.ofSeconds(seconds, nanoseconds))
+ } else {
+ return java.time.Instant.EPOCH.minus(java.time.Duration.ofSeconds(-seconds, nanoseconds))
+ }
+ }
+
+ // 8 bytes for seconds, 4 bytes for nanoseconds
+ override fun allocationSize(value: java.time.Instant) = 12
+
+ override fun write(value: java.time.Instant, buf: ByteBuffer) {
+ var epochOffset = java.time.Duration.between(java.time.Instant.EPOCH, value)
+
+ var sign = 1
+ if (epochOffset.isNegative()) {
+ sign = -1
+ epochOffset = epochOffset.negated()
+ }
+
+ if (epochOffset.nano < 0) {
+ // Java docs provide guarantee that nano will always be positive, so this should be impossible
+ // See: https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html
+ throw IllegalArgumentException("Invalid timestamp, nano value must be non-negative")
+ }
+
+ buf.putLong(sign * epochOffset.seconds)
+ // Type mismatch (should be u32) but since values will always be between 0 and 999,999,999 it should be OK
+ buf.putInt(epochOffset.nano)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
new file mode 100644
index 0000000000..180a64066a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
@@ -0,0 +1,17 @@
+{%- match func.throws_type() -%}
+{%- when Some with (throwable) %}
+@Throws({{ throwable|type_name }}::class)
+{%- else -%}
+{%- endmatch %}
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+
+fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name }} {
+ return {{ return_type|lift_fn }}({% call kt::to_ffi_call(func) %})
+}
+
+{% when None %}
+
+fun {{ func.name()|fn_name }}({% call kt::arg_list_decl(func) %}) =
+ {% call kt::to_ffi_call(func) %}
+{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
new file mode 100644
index 0000000000..068bc05de5
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
@@ -0,0 +1,94 @@
+{%- import "macros.kt" as kt %}
+
+{%- for type_ in ci.iter_types() %}
+{%- let type_name = type_|type_name %}
+{%- let ffi_converter_name = type_|ffi_converter_name %}
+{%- let canonical_type_name = type_|canonical_name %}
+{%- let contains_object_references = ci.item_contains_object_references(type_) %}
+
+{#
+ # Map `Type` instances to an include statement for that type.
+ #
+ # There is a companion match in `KotlinCodeOracle::create_code_type()` which performs a similar function for the
+ # Rust code.
+ #
+ # - When adding additional types here, make sure to also add a match arm to that function.
+ # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ #}
+{%- match type_ %}
+
+{%- when Type::Boolean %}
+{%- include "BooleanHelper.kt" %}
+
+{%- when Type::Int8 %}
+{%- include "Int8Helper.kt" %}
+
+{%- when Type::Int16 %}
+{%- include "Int16Helper.kt" %}
+
+{%- when Type::Int32 %}
+{%- include "Int32Helper.kt" %}
+
+{%- when Type::Int64 %}
+{%- include "Int64Helper.kt" %}
+
+{%- when Type::UInt8 %}
+{%- include "UInt8Helper.kt" %}
+
+{%- when Type::UInt16 %}
+{%- include "UInt16Helper.kt" %}
+
+{%- when Type::UInt32 %}
+{%- include "UInt32Helper.kt" %}
+
+{%- when Type::UInt64 %}
+{%- include "UInt64Helper.kt" %}
+
+{%- when Type::Float32 %}
+{%- include "Float32Helper.kt" %}
+
+{%- when Type::Float64 %}
+{%- include "Float64Helper.kt" %}
+
+{%- when Type::String %}
+{%- include "StringHelper.kt" %}
+
+{%- when Type::Enum(name) %}
+{% include "EnumTemplate.kt" %}
+
+{%- when Type::Error(name) %}
+{% include "ErrorTemplate.kt" %}
+
+{%- when Type::Object(name) %}
+{% include "ObjectTemplate.kt" %}
+
+{%- when Type::Record(name) %}
+{% include "RecordTemplate.kt" %}
+
+{%- when Type::Optional(inner_type) %}
+{% include "OptionalTemplate.kt" %}
+
+{%- when Type::Sequence(inner_type) %}
+{% include "SequenceTemplate.kt" %}
+
+{%- when Type::Map(key_type, value_type) %}
+{% include "MapTemplate.kt" %}
+
+{%- when Type::CallbackInterface(name) %}
+{% include "CallbackInterfaceTemplate.kt" %}
+
+{%- when Type::Timestamp %}
+{% include "TimestampHelper.kt" %}
+
+{%- when Type::Duration %}
+{% include "DurationHelper.kt" %}
+
+{%- when Type::Custom { name, builtin } %}
+{% include "CustomTypeTemplate.kt" %}
+
+{%- when Type::External { crate_name, name } %}
+{% include "ExternalTypeTemplate.kt" %}
+
+{%- else %}
+{%- endmatch %}
+{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
new file mode 100644
index 0000000000..279a8fa91b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterUShort: FfiConverter<UShort, Short> {
+ override fun lift(value: Short): UShort {
+ return value.toUShort()
+ }
+
+ override fun read(buf: ByteBuffer): UShort {
+ return lift(buf.getShort())
+ }
+
+ override fun lower(value: UShort): Short {
+ return value.toShort()
+ }
+
+ override fun allocationSize(value: UShort) = 2
+
+ override fun write(value: UShort, buf: ByteBuffer) {
+ buf.putShort(value.toShort())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
new file mode 100644
index 0000000000..da7b5b28d6
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterUInt: FfiConverter<UInt, Int> {
+ override fun lift(value: Int): UInt {
+ return value.toUInt()
+ }
+
+ override fun read(buf: ByteBuffer): UInt {
+ return lift(buf.getInt())
+ }
+
+ override fun lower(value: UInt): Int {
+ return value.toInt()
+ }
+
+ override fun allocationSize(value: UInt) = 4
+
+ override fun write(value: UInt, buf: ByteBuffer) {
+ buf.putInt(value.toInt())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
new file mode 100644
index 0000000000..44d27ad36b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterULong: FfiConverter<ULong, Long> {
+ override fun lift(value: Long): ULong {
+ return value.toULong()
+ }
+
+ override fun read(buf: ByteBuffer): ULong {
+ return lift(buf.getLong())
+ }
+
+ override fun lower(value: ULong): Long {
+ return value.toLong()
+ }
+
+ override fun allocationSize(value: ULong) = 8
+
+ override fun write(value: ULong, buf: ByteBuffer) {
+ buf.putLong(value.toLong())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
new file mode 100644
index 0000000000..b6d176603e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterUByte: FfiConverter<UByte, Byte> {
+ override fun lift(value: Byte): UByte {
+ return value.toUByte()
+ }
+
+ override fun read(buf: ByteBuffer): UByte {
+ return lift(buf.get())
+ }
+
+ override fun lower(value: UByte): Byte {
+ return value.toByte()
+ }
+
+ override fun allocationSize(value: UByte) = 1
+
+ override fun write(value: UByte, buf: ByteBuffer) {
+ buf.put(value.toByte())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
new file mode 100644
index 0000000000..32182d5e45
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
@@ -0,0 +1,82 @@
+{#
+// Template to call into rust. Used in several places.
+// Variable names in `arg_list_decl` should match up with arg lists
+// passed to rust via `_arg_list_ffi_call`
+#}
+
+{%- macro to_ffi_call(func) -%}
+ {%- match func.throws_type() %}
+ {%- when Some with (e) %}
+ rustCallWithError({{ e|type_name}})
+ {%- else %}
+ rustCall()
+ {%- endmatch %} { _status ->
+ _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call _arg_list_ffi_call(func) -%}{% if func.arguments().len() > 0 %},{% endif %} _status)
+}
+{%- endmacro -%}
+
+{%- macro to_ffi_call_with_prefix(prefix, func) %}
+ {%- match func.throws_type() %}
+ {%- when Some with (e) %}
+ rustCallWithError({{ e|type_name}})
+ {%- else %}
+ rustCall()
+ {%- endmatch %} { _status ->
+ _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}(
+ {{- prefix }}, {% call _arg_list_ffi_call(func) %}{% if func.arguments().len() > 0 %}, {% endif %} _status)
+}
+{%- endmacro %}
+
+{%- macro _arg_list_ffi_call(func) %}
+ {%- for arg in func.arguments() %}
+ {{- arg|lower_fn }}({{ arg.name()|var_name }})
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in kotlin declarations of methods, functions and constructors.
+// Note the var_name and type_name filters.
+-#}
+
+{% macro arg_list_decl(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name }}: {{ arg|type_name -}}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|render_literal(arg) }}
+ {%- else %}
+ {%- endmatch %}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+{% macro arg_list_protocol(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name }}: {{ arg|type_name -}}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+{#-
+// Arglist as used in the _UniFFILib function declarations.
+// Note unfiltered name but ffi_type_name filters.
+-#}
+{%- macro arg_list_ffi_decl(func) %}
+ {%- for arg in func.arguments() %}
+ {{- arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name -}},
+ {%- endfor %}
+ _uniffi_out_err: RustCallStatus
+{%- endmacro -%}
+
+// Macro for destroying fields
+{%- macro destroy_fields(member) %}
+ Disposable.destroy(
+ {%- for field in member.fields() %}
+ this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%}
+ {% endfor -%})
+{%- endmacro -%}
+
+{%- macro ffi_function_definition(func) %}
+fun {{ func.name()|fn_name }}(
+ {%- call arg_list_ffi_decl(func) %}
+){%- match func.return_type() -%}{%- when Some with (type_) %}: {{ type_|ffi_type_name }}{% when None %}: Unit{% endmatch %}
+{% endmacro %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
new file mode 100644
index 0000000000..ef42d8e616
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
@@ -0,0 +1,48 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+
+@file:Suppress("NAME_SHADOWING")
+
+package {{ config.package_name() }};
+
+// Common helper code.
+//
+// Ideally this would live in a separate .kt file where it can be unittested etc
+// in isolation, and perhaps even published as a re-useable package.
+//
+// However, it's important that the detils of how this helper code works (e.g. the
+// way that different builtin types are passed across the FFI) exactly match what's
+// expected by the Rust code on the other side of the interface. In practice right
+// now that means coming from the exact some version of `uniffi` that was used to
+// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin
+// helpers directly inline like we're doing here.
+
+import com.sun.jna.Library
+import com.sun.jna.Native
+import com.sun.jna.Pointer
+import com.sun.jna.Structure
+import com.sun.jna.ptr.ByReference
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+{%- for req in self.imports() %}
+{{ req.render() }}
+{%- endfor %}
+
+{% include "RustBufferTemplate.kt" %}
+{% include "FfiConverterTemplate.kt" %}
+{% include "Helpers.kt" %}
+
+// Contains loading, initialization code,
+// and the FFI Function declarations in a com.sun.jna.Library.
+{% include "NamespaceLibraryTemplate.kt" %}
+
+// Public interface members begin here.
+{{ type_helper_code }}
+
+{%- for func in ci.function_definitions() %}
+{%- include "TopLevelFunctionTemplate.kt" %}
+{%- endfor %}
+
+
+{% import "macros.kt" as kt %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
new file mode 100644
index 0000000000..e8172ed15b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
@@ -0,0 +1,107 @@
+/* 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 anyhow::{bail, Context, Result};
+use camino::{Utf8Path, Utf8PathBuf};
+use std::env;
+use std::process::Command;
+use uniffi_testing::UniFFITestHelper;
+
+/// Run Kotlin tests for a UniFFI test fixture
+pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
+ let script_path = Utf8Path::new(".").join(script_file);
+ let test_helper = UniFFITestHelper::new(fixture_name).context("UniFFITestHelper::new")?;
+ let out_dir = test_helper
+ .create_out_dir(tmp_dir, &script_path)
+ .context("create_out_dir")?;
+ test_helper
+ .copy_cdylibs_to_out_dir(&out_dir)
+ .context("copy_fixture_library_to_out_dir")?;
+ generate_sources(&test_helper.cdylib_path()?, &out_dir, &test_helper)
+ .context("generate_sources")?;
+ let jar_file = build_jar(fixture_name, &out_dir).context("build_jar")?;
+
+ let status = Command::new("kotlinc")
+ .arg("-classpath")
+ .arg(calc_classpath(vec![&out_dir, &jar_file]))
+ // Enable runtime assertions, for easy testing etc.
+ .arg("-J-ea")
+ // Our test scripts should not produce any warnings.
+ .arg("-Werror")
+ .arg("-script")
+ .arg(script_path)
+ .spawn()
+ .context("Failed to spawn `kotlinc` to run Kotlin script")?
+ .wait()
+ .context("Failed to wait for `kotlinc` when running Kotlin script")?;
+ if !status.success() {
+ anyhow::bail!("running `kotlinc` failed")
+ }
+ Ok(())
+}
+
+fn generate_sources(
+ library_path: &Utf8Path,
+ out_dir: &Utf8Path,
+ test_helper: &UniFFITestHelper,
+) -> Result<()> {
+ for source in test_helper.get_compile_sources()? {
+ println!(
+ "Generating bindings: {:?} {:?} {:?}",
+ source.udl_path, source.config_path, out_dir
+ );
+ crate::generate_bindings(
+ &source.udl_path,
+ source.config_path.as_deref(),
+ vec!["kotlin"],
+ Some(out_dir),
+ Some(library_path),
+ false,
+ )?;
+ }
+ Ok(())
+}
+
+/// Generate kotlin bindings for the given namespace, then use the kotlin
+/// command-line tools to compile them into a .jar file.
+fn build_jar(fixture_name: &str, out_dir: &Utf8Path) -> Result<Utf8PathBuf> {
+ let mut jar_file = Utf8PathBuf::from(out_dir);
+ jar_file.push(format!("{}.jar", fixture_name));
+ let sources = glob::glob(out_dir.join("**/*.kt").as_str())?
+ .flatten()
+ .map(|p| String::from(p.to_string_lossy()))
+ .collect::<Vec<String>>();
+ if sources.is_empty() {
+ bail!("No kotlin sources found in {out_dir}")
+ }
+ println!("building jar from: {:?}", sources);
+
+ let status = Command::new("kotlinc")
+ // Our generated bindings should not produce any warnings; fail tests if they do.
+ .arg("-Werror")
+ .arg("-d")
+ .arg(&jar_file)
+ .arg("-classpath")
+ .arg(calc_classpath(vec![]))
+ .args(sources)
+ .spawn()
+ .context("Failed to spawn `kotlinc` to compile the bindings")?
+ .wait()
+ .context("Failed to wait for `kotlinc` when compiling the bindings")?;
+ if !status.success() {
+ bail!("running `kotlinc` failed")
+ }
+ Ok(jar_file)
+}
+
+fn calc_classpath(extra_paths: Vec<&Utf8Path>) -> String {
+ extra_paths
+ .into_iter()
+ .map(|p| p.to_string())
+ // Add the system classpath as a component, using the fact that env::var returns an Option,
+ // which implement Iterator
+ .chain(env::var("CLASSPATH"))
+ .collect::<Vec<String>>()
+ .join(":")
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/mod.rs
new file mode 100644
index 0000000000..10f6e5fe2c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/mod.rs
@@ -0,0 +1,121 @@
+/* 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/. */
+
+//! Generate foreign language bindings for a uniffi component.
+//!
+//! This module contains all the code for generating foreign language bindings,
+//! along with some helpers for executing foreign language scripts or tests.
+
+use anyhow::{bail, Result};
+use camino::Utf8Path;
+use serde::{Deserialize, Serialize};
+
+use crate::interface::ComponentInterface;
+use crate::MergeWith;
+
+pub mod kotlin;
+pub mod python;
+pub mod ruby;
+pub mod swift;
+
+/// Enumeration of all foreign language targets currently supported by this crate.
+///
+/// The functions in this module will delegate to a language-specific backend based
+/// on the provided `TargetLanguage`. For convenience of calling code we also provide
+/// a few `TryFrom` implementations to help guess the correct target language from
+/// e.g. a file extension of command-line argument.
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+pub enum TargetLanguage {
+ Kotlin,
+ Swift,
+ Python,
+ Ruby,
+}
+
+impl TryFrom<&str> for TargetLanguage {
+ type Error = anyhow::Error;
+ fn try_from(value: &str) -> Result<Self> {
+ Ok(match value.to_ascii_lowercase().as_str() {
+ "kotlin" | "kt" | "kts" => TargetLanguage::Kotlin,
+ "swift" => TargetLanguage::Swift,
+ "python" | "py" => TargetLanguage::Python,
+ "ruby" | "rb" => TargetLanguage::Ruby,
+ _ => bail!("Unknown or unsupported target language: \"{}\"", value),
+ })
+ }
+}
+
+impl TryFrom<&std::ffi::OsStr> for TargetLanguage {
+ type Error = anyhow::Error;
+ fn try_from(value: &std::ffi::OsStr) -> Result<Self> {
+ match value.to_str() {
+ None => bail!("Unreadable target language"),
+ Some(s) => s.try_into(),
+ }
+ }
+}
+
+impl TryFrom<String> for TargetLanguage {
+ type Error = anyhow::Error;
+ fn try_from(value: String) -> Result<Self> {
+ TryFrom::try_from(value.as_str())
+ }
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Config {
+ #[serde(default)]
+ kotlin: kotlin::Config,
+ #[serde(default)]
+ swift: swift::Config,
+ #[serde(default)]
+ python: python::Config,
+ #[serde(default)]
+ ruby: ruby::Config,
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ kotlin: ci.into(),
+ swift: ci.into(),
+ python: ci.into(),
+ ruby: ci.into(),
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ kotlin: self.kotlin.merge_with(&other.kotlin),
+ swift: self.swift.merge_with(&other.swift),
+ python: self.python.merge_with(&other.python),
+ ruby: self.ruby.merge_with(&other.ruby),
+ }
+ }
+}
+
+/// Generate foreign language bindings from a compiled `uniffi` library.
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ language: TargetLanguage,
+ try_format_code: bool,
+) -> Result<()> {
+ match language {
+ TargetLanguage::Kotlin => {
+ kotlin::write_bindings(&config.kotlin, ci, out_dir, try_format_code)?
+ }
+ TargetLanguage::Swift => {
+ swift::write_bindings(&config.swift, ci, out_dir, try_format_code)?
+ }
+ TargetLanguage::Python => {
+ python::write_bindings(&config.python, ci, out_dir, try_format_code)?
+ }
+ TargetLanguage::Ruby => ruby::write_bindings(&config.ruby, ci, out_dir, try_format_code)?,
+ }
+ Ok(())
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs
new file mode 100644
index 0000000000..9359fbaeae
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs
@@ -0,0 +1,33 @@
+/* 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, Literal};
+
+pub struct CallbackInterfaceCodeType {
+ id: String,
+}
+
+impl CallbackInterfaceCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for CallbackInterfaceCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("CallbackInterface{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
new file mode 100644
index 0000000000..80b1da040d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
@@ -0,0 +1,122 @@
+/* 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, Literal, TypeIdentifier};
+
+pub struct OptionalCodeType {
+ inner: TypeIdentifier,
+}
+
+impl OptionalCodeType {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+}
+
+impl CodeType for OptionalCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.find(&self.inner).type_label(oracle)
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Optional{}",
+ oracle.find(&self.inner).canonical_name(oracle),
+ )
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::Null => "None".into(),
+ _ => oracle.find(&self.inner).literal(oracle, literal),
+ }
+ }
+
+ fn coerce(&self, oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!(
+ "(None if {} is None else {})",
+ nm,
+ oracle.find(&self.inner).coerce(oracle, nm)
+ )
+ }
+}
+
+pub struct SequenceCodeType {
+ inner: TypeIdentifier,
+}
+
+impl SequenceCodeType {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+}
+
+impl CodeType for SequenceCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ "list".to_string()
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Sequence{}",
+ oracle.find(&self.inner).canonical_name(oracle),
+ )
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::EmptySequence => "[]".into(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn coerce(&self, oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!(
+ "list({} for x in {})",
+ oracle.find(&self.inner).coerce(oracle, "x"),
+ nm
+ )
+ }
+}
+
+pub struct MapCodeType {
+ key: TypeIdentifier,
+ value: TypeIdentifier,
+}
+
+impl MapCodeType {
+ pub fn new(key: TypeIdentifier, value: TypeIdentifier) -> Self {
+ Self { key, value }
+ }
+}
+
+impl CodeType for MapCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ "dict".to_string()
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Map{}{}",
+ oracle.find(&self.key).canonical_name(oracle),
+ oracle.find(&self.value).canonical_name(oracle),
+ )
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::EmptyMap => "{}".into(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn coerce(&self, oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!(
+ "dict(({}, {}) for (k, v) in {}.items())",
+ self.key.coerce(oracle, "k"),
+ self.value.coerce(oracle, "v"),
+ nm
+ )
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs
new file mode 100644
index 0000000000..d53a946947
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs
@@ -0,0 +1,29 @@
+/* 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 CustomCodeType {
+ name: String,
+}
+
+impl CustomCodeType {
+ pub fn new(name: String) -> Self {
+ Self { name }
+ }
+}
+
+impl CodeType for CustomCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.name)
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs
new file mode 100644
index 0000000000..9a86076770
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs
@@ -0,0 +1,41 @@
+/* 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, Literal};
+
+pub struct EnumCodeType {
+ id: String,
+}
+
+impl EnumCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for EnumCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ if let Literal::Enum(v, _) = literal {
+ format!(
+ "{}.{}",
+ self.type_label(oracle),
+ oracle.enum_variant_name(v)
+ )
+ } else {
+ unreachable!();
+ }
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs
new file mode 100644
index 0000000000..f1567281f2
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs
@@ -0,0 +1,33 @@
+/* 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, Literal};
+
+pub struct ErrorCodeType {
+ id: String,
+}
+
+impl ErrorCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ErrorCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
new file mode 100644
index 0000000000..1739d84cec
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
@@ -0,0 +1,29 @@
+/* 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 {
+ Self { 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)
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.into()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs
new file mode 100644
index 0000000000..c885428bc3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs
@@ -0,0 +1,36 @@
+/* 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, Literal};
+use paste::paste;
+
+macro_rules! impl_code_type_for_miscellany {
+ ($T:ty, $canonical_name:literal) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("{}", $canonical_name)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("{}", $canonical_name)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!()
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_miscellany!(TimestampCodeType, "Timestamp");
+
+impl_code_type_for_miscellany!(DurationCodeType, "Duration");
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
new file mode 100644
index 0000000000..0b2218402a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
@@ -0,0 +1,448 @@
+/* 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 anyhow::{Context, Result};
+use askama::Template;
+use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
+use once_cell::sync::Lazy;
+use serde::{Deserialize, Serialize};
+use std::borrow::Borrow;
+use std::cell::RefCell;
+use std::collections::{BTreeSet, HashMap, HashSet};
+
+use crate::backend::{CodeOracle, CodeType, TemplateExpression, TypeIdentifier};
+use crate::interface::*;
+use crate::MergeWith;
+
+mod callback_interface;
+mod compounds;
+mod custom;
+mod enum_;
+mod error;
+mod external;
+mod miscellany;
+mod object;
+mod primitives;
+mod record;
+
+// Taken from Python's `keyword.py` module.
+static KEYWORDS: Lazy<HashSet<String>> = Lazy::new(|| {
+ let kwlist = vec![
+ "False",
+ "None",
+ "True",
+ "__peg_parser__",
+ "and",
+ "as",
+ "assert",
+ "async",
+ "await",
+ "break",
+ "class",
+ "continue",
+ "def",
+ "del",
+ "elif",
+ "else",
+ "except",
+ "finally",
+ "for",
+ "from",
+ "global",
+ "if",
+ "import",
+ "in",
+ "is",
+ "lambda",
+ "nonlocal",
+ "not",
+ "or",
+ "pass",
+ "raise",
+ "return",
+ "try",
+ "while",
+ "with",
+ "yield",
+ ];
+ HashSet::from_iter(kwlist.into_iter().map(|s| s.to_string()))
+});
+
+// Config options to customize the generated python.
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Config {
+ cdylib_name: Option<String>,
+ #[serde(default)]
+ custom_types: HashMap<String, CustomTypeConfig>,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct CustomTypeConfig {
+ // This `CustomTypeConfig` doesn't have a `type_name` like the others -- which is why we have
+ // separate structs rather than a shared one.
+ imports: Option<Vec<String>>,
+ into_custom: TemplateExpression,
+ from_custom: TemplateExpression,
+}
+
+impl Config {
+ pub fn cdylib_name(&self) -> String {
+ if let Some(cdylib_name) = &self.cdylib_name {
+ cdylib_name.clone()
+ } else {
+ "uniffi".into()
+ }
+ }
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ cdylib_name: Some(format!("uniffi_{}", ci.namespace())),
+ custom_types: HashMap::new(),
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ cdylib_name: self.cdylib_name.merge_with(&other.cdylib_name),
+ custom_types: self.custom_types.merge_with(&other.custom_types),
+ }
+ }
+}
+
+// Generate python bindings for the given ComponentInterface, as a string.
+pub fn generate_python_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
+ PythonWrapper::new(config.clone(), ci)
+ .render()
+ .context("failed to render python bindings")
+}
+
+/// A struct to record a Python import statement.
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub enum ImportRequirement {
+ /// A simple module import.
+ Module { mod_name: String },
+ /// A single symbol from a module.
+ Symbol {
+ mod_name: String,
+ symbol_name: String,
+ },
+ /// A single symbol from a module with the specified local name.
+ SymbolAs {
+ mod_name: String,
+ symbol_name: String,
+ as_name: String,
+ },
+}
+
+impl ImportRequirement {
+ /// Render the Python import statement.
+ fn render(&self) -> String {
+ match &self {
+ ImportRequirement::Module { mod_name } => format!("import {mod_name}"),
+ ImportRequirement::Symbol {
+ mod_name,
+ symbol_name,
+ } => format!("from {mod_name} import {symbol_name}"),
+ ImportRequirement::SymbolAs {
+ mod_name,
+ symbol_name,
+ as_name,
+ } => format!("from {mod_name} import {symbol_name} as {as_name}"),
+ }
+ }
+}
+
+/// Renders Python helper code for all types
+///
+/// This template is a bit different than others in that it stores internal state from the render
+/// process. Make sure to only call `render()` once.
+#[derive(Template)]
+#[template(syntax = "py", escape = "none", path = "Types.py")]
+pub struct TypeRenderer<'a> {
+ python_config: &'a Config,
+ ci: &'a ComponentInterface,
+ // Track included modules for the `include_once()` macro
+ include_once_names: RefCell<HashSet<String>>,
+ // Track imports added with the `add_import()` macro
+ imports: RefCell<BTreeSet<ImportRequirement>>,
+}
+
+impl<'a> TypeRenderer<'a> {
+ fn new(python_config: &'a Config, ci: &'a ComponentInterface) -> Self {
+ Self {
+ python_config,
+ ci,
+ include_once_names: RefCell::new(HashSet::new()),
+ imports: RefCell::new(BTreeSet::new()),
+ }
+ }
+
+ // The following methods are used by the `Types.py` macros.
+
+ // Helper for the including a template, but only once.
+ //
+ // The first time this is called with a name it will return true, indicating that we should
+ // include the template. Subsequent calls will return false.
+ fn include_once_check(&self, name: &str) -> bool {
+ self.include_once_names
+ .borrow_mut()
+ .insert(name.to_string())
+ }
+
+ // Helper to add an import statement
+ //
+ // Call this inside your template to cause an import statement to be added at the top of the
+ // file. Imports will be sorted and de-deuped.
+ //
+ // Returns an empty string so that it can be used inside an askama `{{ }}` block.
+ fn add_import(&self, name: &str) -> &str {
+ self.imports.borrow_mut().insert(ImportRequirement::Module {
+ mod_name: name.to_owned(),
+ });
+ ""
+ }
+
+ // Like add_import, but arranges for `from module import name`.
+ fn add_import_of(&self, mod_name: &str, name: &str) -> &str {
+ self.imports.borrow_mut().insert(ImportRequirement::Symbol {
+ mod_name: mod_name.to_owned(),
+ symbol_name: name.to_owned(),
+ });
+ ""
+ }
+
+ // Like add_import, but arranges for `from module import name as other`.
+ fn add_import_of_as(&self, mod_name: &str, symbol_name: &str, as_name: &str) -> &str {
+ self.imports
+ .borrow_mut()
+ .insert(ImportRequirement::SymbolAs {
+ mod_name: mod_name.to_owned(),
+ symbol_name: symbol_name.to_owned(),
+ as_name: as_name.to_owned(),
+ });
+ ""
+ }
+}
+
+#[derive(Template)]
+#[template(syntax = "py", escape = "none", path = "wrapper.py")]
+pub struct PythonWrapper<'a> {
+ ci: &'a ComponentInterface,
+ config: Config,
+ type_helper_code: String,
+ type_imports: BTreeSet<ImportRequirement>,
+}
+impl<'a> PythonWrapper<'a> {
+ pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
+ let type_renderer = TypeRenderer::new(&config, ci);
+ let type_helper_code = type_renderer.render().unwrap();
+ let type_imports = type_renderer.imports.into_inner();
+ Self {
+ config,
+ ci,
+ type_helper_code,
+ type_imports,
+ }
+ }
+
+ pub fn imports(&self) -> Vec<ImportRequirement> {
+ self.type_imports.iter().cloned().collect()
+ }
+}
+
+fn fixup_keyword(name: String) -> String {
+ if KEYWORDS.contains(&name) {
+ format!("_{name}")
+ } else {
+ name
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct PythonCodeOracle;
+
+impl PythonCodeOracle {
+ // Map `Type` instances to a `Box<dyn CodeType>` for that type.
+ //
+ // There is a companion match in `templates/Types.py` which performs a similar function for the
+ // template code.
+ //
+ // - When adding additional types here, make sure to also add a match arm to the `Types.py` template.
+ // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> {
+ match type_ {
+ Type::UInt8 => Box::new(primitives::UInt8CodeType),
+ Type::Int8 => Box::new(primitives::Int8CodeType),
+ Type::UInt16 => Box::new(primitives::UInt16CodeType),
+ Type::Int16 => Box::new(primitives::Int16CodeType),
+ Type::UInt32 => Box::new(primitives::UInt32CodeType),
+ Type::Int32 => Box::new(primitives::Int32CodeType),
+ Type::UInt64 => Box::new(primitives::UInt64CodeType),
+ Type::Int64 => Box::new(primitives::Int64CodeType),
+ Type::Float32 => Box::new(primitives::Float32CodeType),
+ Type::Float64 => Box::new(primitives::Float64CodeType),
+ Type::Boolean => Box::new(primitives::BooleanCodeType),
+ Type::String => Box::new(primitives::StringCodeType),
+
+ Type::Timestamp => Box::new(miscellany::TimestampCodeType),
+ Type::Duration => Box::new(miscellany::DurationCodeType),
+
+ Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
+ Type::Object(id) => Box::new(object::ObjectCodeType::new(id)),
+ Type::Record(id) => Box::new(record::RecordCodeType::new(id)),
+ Type::Error(id) => Box::new(error::ErrorCodeType::new(id)),
+ Type::CallbackInterface(id) => {
+ Box::new(callback_interface::CallbackInterfaceCodeType::new(id))
+ }
+
+ 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 { 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")
+ }
+ }
+ }
+}
+
+impl CodeOracle for PythonCodeOracle {
+ fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+ self.create_code_type(type_.clone())
+ }
+
+ /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
+ fn class_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_upper_camel_case())
+ }
+
+ /// Get the idiomatic Python rendering of a function name.
+ fn fn_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of a variable name.
+ fn var_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of an individual enum variant.
+ fn enum_variant_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_shouty_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of an exception name
+ /// This replaces "Error" at the end of the name with "Exception".
+ fn error_name(&self, nm: &str) -> String {
+ let name = fixup_keyword(self.class_name(nm));
+ match name.strip_suffix("Error") {
+ None => name,
+ Some(stripped) => format!("{stripped}Exception"),
+ }
+ }
+
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::Int8 => "ctypes.c_int8".to_string(),
+ FfiType::UInt8 => "ctypes.c_uint8".to_string(),
+ FfiType::Int16 => "ctypes.c_int16".to_string(),
+ FfiType::UInt16 => "ctypes.c_uint16".to_string(),
+ FfiType::Int32 => "ctypes.c_int32".to_string(),
+ FfiType::UInt32 => "ctypes.c_uint32".to_string(),
+ FfiType::Int64 => "ctypes.c_int64".to_string(),
+ FfiType::UInt64 => "ctypes.c_uint64".to_string(),
+ FfiType::Float32 => "ctypes.c_float".to_string(),
+ FfiType::Float64 => "ctypes.c_double".to_string(),
+ FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(),
+ FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
+ Some(suffix) => format!("RustBuffer{}", suffix),
+ None => "RustBuffer".to_string(),
+ },
+ FfiType::ForeignBytes => "ForeignBytes".to_string(),
+ FfiType::ForeignCallback => "FOREIGN_CALLBACK_T".to_string(),
+ }
+ }
+}
+
+pub mod filters {
+ use super::*;
+
+ fn oracle() -> &'static PythonCodeOracle {
+ &PythonCodeOracle
+ }
+
+ pub fn type_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.type_label(oracle))
+ }
+
+ pub fn ffi_converter_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.ffi_converter_name(oracle))
+ }
+
+ pub fn canonical_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.canonical_name(oracle))
+ }
+
+ pub fn lift_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lift", ffi_converter_name(codetype)?))
+ }
+
+ pub fn lower_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lower", ffi_converter_name(codetype)?))
+ }
+
+ pub fn read_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.read", ffi_converter_name(codetype)?))
+ }
+
+ pub fn write_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.write", ffi_converter_name(codetype)?))
+ }
+
+ pub fn literal_py(
+ literal: &Literal,
+ codetype: &impl CodeType,
+ ) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.literal(oracle, literal))
+ }
+
+ /// Get the Python syntax for representing a given low-level `FfiType`.
+ pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_type_label(type_))
+ }
+
+ /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
+ pub fn class_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().class_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of a function name.
+ pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().fn_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of a variable name.
+ pub fn var_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().var_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of an individual enum variant.
+ pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().enum_variant_name(nm))
+ }
+
+ pub fn coerce_py(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(oracle.find(type_).coerce(oracle, nm))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs
new file mode 100644
index 0000000000..7744865ec0
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs
@@ -0,0 +1,33 @@
+/* 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, Literal};
+
+pub struct ObjectCodeType {
+ id: String,
+}
+
+impl ObjectCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ObjectCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs
new file mode 100644
index 0000000000..de7918fb16
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs
@@ -0,0 +1,69 @@
+/* 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, Literal};
+use crate::interface::Radix;
+use paste::paste;
+
+fn render_literal(_oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::Boolean(v) => {
+ if *v {
+ "True".into()
+ } else {
+ "False".into()
+ }
+ }
+ Literal::String(s) => format!("\"{s}\""),
+ // https://docs.python.org/3/reference/lexical_analysis.html#integer-literals
+ Literal::Int(i, radix, _) => match radix {
+ Radix::Octal => format!("int(0o{i:o})"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ Literal::UInt(i, radix, _) => match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ Literal::Float(string, _type_) => string.clone(),
+
+ _ => unreachable!("Literal"),
+ }
+}
+
+macro_rules! impl_code_type_for_primitive {
+ ($T:ty, $class_name:literal, $coerce_code:expr) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ $class_name.into()
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, &literal)
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!($coerce_code, nm)
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_primitive!(BooleanCodeType, "Bool", "bool({})");
+impl_code_type_for_primitive!(StringCodeType, "String", "{}");
+impl_code_type_for_primitive!(Int8CodeType, "Int8", "int({})");
+impl_code_type_for_primitive!(Int16CodeType, "Int16", "int({})");
+impl_code_type_for_primitive!(Int32CodeType, "Int32", "int({})");
+impl_code_type_for_primitive!(Int64CodeType, "Int64", "int({})");
+impl_code_type_for_primitive!(UInt8CodeType, "UInt8", "int({})");
+impl_code_type_for_primitive!(UInt16CodeType, "UInt16", "int({})");
+impl_code_type_for_primitive!(UInt32CodeType, "UInt32", "int({})");
+impl_code_type_for_primitive!(UInt64CodeType, "UInt64", "int({})");
+impl_code_type_for_primitive!(Float32CodeType, "Float", "float({})");
+impl_code_type_for_primitive!(Float64CodeType, "Double", "float({})");
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs
new file mode 100644
index 0000000000..1dba8e49fe
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs
@@ -0,0 +1,33 @@
+/* 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, Literal};
+
+pub struct RecordCodeType {
+ id: String,
+}
+
+impl RecordCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for RecordCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs
new file mode 100644
index 0000000000..3a1cfaa78d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs
@@ -0,0 +1,40 @@
+/* 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 std::{io::Write, process::Command};
+
+use anyhow::Result;
+use camino::Utf8Path;
+use fs_err::File;
+
+pub mod gen_python;
+mod test;
+
+use super::super::interface::ComponentInterface;
+pub use gen_python::{generate_python_bindings, Config};
+pub use test::run_test;
+
+// Generate python bindings for the given ComponentInterface, in the given output directory.
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+) -> Result<()> {
+ let py_file = out_dir.join(format!("{}.py", ci.namespace()));
+ let mut f = File::create(&py_file)?;
+ write!(f, "{}", generate_python_bindings(config, ci)?)?;
+
+ if try_format_code {
+ if let Err(e) = Command::new("yapf").arg(&py_file).output() {
+ println!(
+ "Warning: Unable to auto-format {} using yapf: {:?}",
+ py_file.file_name().unwrap(),
+ e
+ )
+ }
+ }
+
+ Ok(())
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
new file mode 100644
index 0000000000..d7b64b91f7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
@@ -0,0 +1,16 @@
+class FfiConverterBool:
+ @classmethod
+ def read(cls, buf):
+ return cls.lift(buf.readU8())
+
+ @classmethod
+ def write(cls, value, buf):
+ buf.writeU8(cls.lower(value))
+
+ @staticmethod
+ def lift(value):
+ return int(value) != 0
+
+ @staticmethod
+ def lower(value):
+ return 1 if value else 0
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
new file mode 100644
index 0000000000..e8a0ec56d9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
@@ -0,0 +1,73 @@
+import threading
+
+class ConcurrentHandleMap:
+ """
+ A map where inserting, getting and removing data is synchronized with a lock.
+ """
+
+ def __init__(self):
+ # type Handle = int
+ self._left_map = {} # type: Dict[Handle, Any]
+ self._right_map = {} # type: Dict[Any, Handle]
+
+ self._lock = threading.Lock()
+ self._current_handle = 0
+ self._stride = 1
+
+
+ def insert(self, obj):
+ with self._lock:
+ if obj in self._right_map:
+ return self._right_map[obj]
+ else:
+ handle = self._current_handle
+ self._current_handle += self._stride
+ self._left_map[handle] = obj
+ self._right_map[obj] = handle
+ return handle
+
+ def get(self, handle):
+ with self._lock:
+ return self._left_map.get(handle)
+
+ def remove(self, handle):
+ with self._lock:
+ if handle in self._left_map:
+ obj = self._left_map.pop(handle)
+ del self._right_map[obj]
+ return obj
+
+# Magic number for the Rust proxy to call using the same mechanism as every other method,
+# to free the callback once it's dropped by Rust.
+IDX_CALLBACK_FREE = 0
+
+class FfiConverterCallbackInterface:
+ _handle_map = ConcurrentHandleMap()
+
+ def __init__(self, cb):
+ self._foreign_callback = cb
+
+ def drop(self, handle):
+ self.__class__._handle_map.remove(handle)
+
+ @classmethod
+ def lift(cls, handle):
+ obj = cls._handle_map.get(handle)
+ if not obj:
+ raise InternalError("The object in the handle map has been dropped already")
+
+ return obj
+
+ @classmethod
+ def read(cls, buf):
+ handle = buf.readU64()
+ cls.lift(handle)
+
+ @classmethod
+ def lower(cls, cb):
+ handle = cls._handle_map.insert(cb)
+ return handle
+
+ @classmethod
+ def write(cls, cb, buf):
+ buf.writeU64(cls.lower(cb))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
new file mode 100644
index 0000000000..cf4411b8a0
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
@@ -0,0 +1,104 @@
+{%- let cbi = ci.get_callback_interface_definition(id).unwrap() %}
+{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %}
+
+{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %}
+
+# Declaration and FfiConverters for {{ type_name }} Callback Interface
+
+class {{ type_name }}:
+ {% for meth in cbi.methods() -%}
+ def {{ meth.name()|fn_name }}({% call py::arg_list_decl(meth) %}):
+ raise NotImplementedError
+
+ {% endfor %}
+
+def py_{{ foreign_callback }}(handle, method, args, buf_ptr):
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
+ def {{ method_name }}(python_callback, args):
+ {#- Unpacking args from the RustBuffer #}
+ {%- if meth.arguments().len() != 0 -%}
+ {#- Calling the concrete callback object #}
+ with args.consumeWithStream() as buf:
+ rval = python_callback.{{ meth.name()|fn_name }}(
+ {% for arg in meth.arguments() -%}
+ {{ arg|read_fn }}(buf)
+ {%- if !loop.last %}, {% endif %}
+ {% endfor -%}
+ )
+ {% else %}
+ rval = python_callback.{{ meth.name()|fn_name }}()
+ {% endif -%}
+
+ {#- Packing up the return value into a RustBuffer #}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) -%}
+ with RustBuffer.allocWithBuilder() as builder:
+ {{ return_type|write_fn }}(rval, builder)
+ return builder.finalize()
+ {%- else -%}
+ return RustBuffer.alloc(0)
+ {% endmatch -%}
+ {% endfor %}
+
+ cb = {{ ffi_converter_name }}.lift(handle)
+ if not cb:
+ raise InternalError("No callback in handlemap; this is a Uniffi bug")
+
+ if method == IDX_CALLBACK_FREE:
+ {{ ffi_converter_name }}.drop(handle)
+ # No return value.
+ # See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return 0
+
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
+ if method == {{ loop.index }}:
+ # Call the method and handle any errors
+ # See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` for details
+ try:
+ {%- match meth.throws_type() %}
+ {%- when Some(err) %}
+ try:
+ # Successful return
+ buf_ptr[0] = {{ method_name }}(cb, args)
+ return 1
+ except {{ err|type_name }} as e:
+ # Catch errors declared in the UDL file
+ with RustBuffer.allocWithBuilder() as builder:
+ {{ err|write_fn }}(e, builder)
+ buf_ptr[0] = builder.finalize()
+ return -2
+ {%- else %}
+ # Successful return
+ buf_ptr[0] = {{ method_name }}(cb, args)
+ return 1
+ {%- endmatch %}
+ except BaseException as e:
+ # Catch unexpected errors
+ try:
+ # Try to serialize the exception into a String
+ buf_ptr[0] = {{ Type::String.borrow()|lower_fn }}(repr(e))
+ except:
+ # If that fails, just give up
+ pass
+ return -1
+ {% endfor %}
+
+ # This should never happen, because an out of bounds method index won't
+ # ever be used. Once we can catch errors, we should return an InternalException.
+ # https://github.com/mozilla/uniffi-rs/issues/351
+
+ # An unexpected error happened.
+ # See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return -1
+
+# We need to keep this function reference alive:
+# if they get GC'd while in use then UniFFI internals could attempt to call a function
+# that is in freed memory.
+# That would be...uh...bad. Yeah, that's the word. Bad.
+{{ foreign_callback }} = FOREIGN_CALLBACK_T(py_{{ foreign_callback }})
+
+# The FfiConverter which transforms the Callbacks in to Handles to pass to Rust.
+rust_call(lambda err: _UniFFILib.{{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err))
+{{ ffi_converter_name }} = FfiConverterCallbackInterface({{ foreign_callback }})
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
new file mode 100644
index 0000000000..9ce6a21ced
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
@@ -0,0 +1,52 @@
+{%- match python_config.custom_types.get(name.as_str()) %}
+{% when None %}
+{#- No custom type config, just forward all methods to our builtin type #}
+class FfiConverterType{{ name }}:
+ @staticmethod
+ def write(value, buf):
+ {{ builtin|ffi_converter_name }}.write(value, buf)
+
+ @staticmethod
+ def read(buf):
+ return {{ builtin|ffi_converter_name }}.read(buf)
+
+ @staticmethod
+ def lift(value):
+ return {{ builtin|ffi_converter_name }}.lift(value)
+
+ @staticmethod
+ def lower(value):
+ return {{ builtin|ffi_converter_name }}.lower(value)
+
+{%- when Some(config) %}
+
+{%- match config.imports %}
+{%- when Some(imports) %}
+{%- for import_name in imports %}
+{{ self.add_import(import_name) }}
+{%- endfor %}
+{%- else %}
+{%- endmatch %}
+
+{#- Custom type config supplied, use it to convert the builtin type #}
+class FfiConverterType{{ name }}:
+ @staticmethod
+ def write(value, buf):
+ builtin_value = {{ config.from_custom.render("value") }}
+ {{ builtin|write_fn }}(builtin_value, buf)
+
+ @staticmethod
+ def read(buf):
+ builtin_value = {{ builtin|read_fn }}(buf)
+ return {{ config.into_custom.render("builtin_value") }}
+
+ @staticmethod
+ def lift(value):
+ builtin_value = {{ builtin|lift_fn }}(value)
+ return {{ config.into_custom.render("builtin_value") }}
+
+ @staticmethod
+ def lower(value):
+ builtin_value = {{ config.from_custom.render("value") }}
+ return {{ builtin|lower_fn }}(builtin_value)
+{%- endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
new file mode 100644
index 0000000000..16fdd986f3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
@@ -0,0 +1,19 @@
+# The Duration type.
+# There is a loss of precision when converting from Rust durations,
+# which are accurate to the nanosecond,
+# to Python durations, which are only accurate to the microsecond.
+class FfiConverterDuration(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ seconds = buf.readU64()
+ microseconds = buf.readU32() / 1.0e3
+ return datetime.timedelta(seconds=seconds, microseconds=microseconds)
+
+ @staticmethod
+ def write(value, buf):
+ seconds = value.seconds + value.days * 24 * 3600
+ nanoseconds = value.microseconds * 1000
+ if seconds < 0:
+ raise ValueError("Invalid duration, must be non-negative")
+ buf.writeI64(seconds)
+ buf.writeU32(nanoseconds)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
new file mode 100644
index 0000000000..5c366d8855
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
@@ -0,0 +1,93 @@
+{#
+# Python has a built-in `enum` module which is nice to use, but doesn't support
+# variants with associated data. So, we switch here, and generate a stdlib `enum`
+# when none of the variants have associated data, or a generic nested-class
+# construct when they do.
+#}
+{%- let e = ci.get_enum_definition(name).unwrap() %}
+{% if e.is_flat() %}
+
+class {{ type_name }}(enum.Enum):
+ {% for variant in e.variants() -%}
+ {{ variant.name()|enum_variant_py }} = {{ loop.index }}
+ {% endfor %}
+{% else %}
+
+class {{ type_name }}:
+ def __init__(self):
+ raise RuntimeError("{{ type_name }} cannot be instantiated directly")
+
+ # Each enum variant is a nested class of the enum itself.
+ {% for variant in e.variants() -%}
+ class {{ variant.name()|enum_variant_py }}(object):
+ def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}):
+ {% if variant.has_fields() %}
+ {%- for field in variant.fields() %}
+ self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
+ {%- endfor %}
+ {% else %}
+ pass
+ {% endif %}
+
+ def __str__(self):
+ return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
+
+ def __eq__(self, other):
+ if not other.is_{{ variant.name()|var_name }}():
+ return False
+ {%- for field in variant.fields() %}
+ if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}:
+ return False
+ {%- endfor %}
+ return True
+ {% endfor %}
+
+ # For each variant, we have an `is_NAME` method for easily checking
+ # whether an instance is that variant.
+ {% for variant in e.variants() -%}
+ def is_{{ variant.name()|var_name }}(self):
+ return isinstance(self, {{ type_name }}.{{ variant.name()|enum_variant_py }})
+ {% endfor %}
+
+# Now, a little trick - we make each nested variant class be a subclass of the main
+# enum class, so that method calls and instance checks etc will work intuitively.
+# We might be able to do this a little more neatly with a metaclass, but this'll do.
+{% for variant in e.variants() -%}
+{{ type_name }}.{{ variant.name()|enum_variant_py }} = type("{{ type_name }}.{{ variant.name()|enum_variant_py }}", ({{ type_name }}.{{variant.name()|enum_variant_py}}, {{ type_name }},), {})
+{% endfor %}
+
+{% endif %}
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ variant = buf.readI32()
+
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}:
+ {%- if e.is_flat() %}
+ return {{ type_name }}.{{variant.name()|enum_variant_py}}
+ {%- else %}
+ return {{ type_name }}.{{variant.name()|enum_variant_py}}(
+ {%- for field in variant.fields() %}
+ {{ field|read_fn }}(buf),
+ {%- endfor %}
+ )
+ {%- endif %}
+ {%- endfor %}
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ def write(value, buf):
+ {%- for variant in e.variants() %}
+ {%- if e.is_flat() %}
+ if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}:
+ buf.writeI32({{ loop.index }})
+ {%- else %}
+ if value.is_{{ variant.name()|var_name }}():
+ buf.writeI32({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ {%- endif %}
+ {%- endfor %}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
new file mode 100644
index 0000000000..305ae88618
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
@@ -0,0 +1,75 @@
+{%- let e = ci.get_error_definition(name).unwrap() %}
+
+# {{ type_name }}
+# We want to define each variant as a nested class that's also a subclass,
+# which is tricky in Python. To accomplish this we're going to create each
+# class separated, then manually add the child classes to the base class's
+# __dict__. All of this happens in dummy class to avoid polluting the module
+# namespace.
+class UniFFIExceptionTmpNamespace:
+ class {{ type_name }}(Exception):
+ pass
+ {% for variant in e.variants() %}
+ {%- let variant_type_name = variant.name()|class_name %}
+
+ {%- if e.is_flat() %}
+ class {{ variant_type_name }}({{ type_name }}):
+ def __str__(self):
+ return "{{ type_name }}.{{ variant_type_name }}({})".format(repr(super().__str__()))
+ {%- else %}
+ class {{ variant_type_name }}({{ type_name }}):
+ def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}):
+ {%- if variant.has_fields() %}
+ {%- for field in variant.fields() %}
+ self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
+ {%- endfor %}
+ {%- else %}
+ pass
+ {%- endif %}
+
+ def __str__(self):
+ {%- if variant.has_fields() %}
+ field_parts = [
+ {%- for field in variant.fields() %}
+ '{{ field.name()|var_name }}={!r}'.format(self.{{ field.name()|var_name }}),
+ {%- endfor %}
+ ]
+ return "{{ type_name }}.{{ variant_type_name }}({})".format(', '.join(field_parts))
+ {%- else %}
+ return "{{ type_name }}.{{ variant_type_name }}()"
+ {%- endif %}
+ {%- endif %}
+
+ {{ type_name }}.{{ variant_type_name }} = {{ variant_type_name }}
+ {%- endfor %}
+{{ type_name }} = UniFFIExceptionTmpNamespace.{{ type_name }}
+del UniFFIExceptionTmpNamespace
+
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ variant = buf.readI32()
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}:
+ return {{ type_name }}.{{ variant.name()|class_name }}(
+ {%- if e.is_flat() %}
+ {{ Type::String.borrow()|read_fn }}(buf),
+ {%- else %}
+ {%- for field in variant.fields() %}
+ {{ field.name()|var_name }}={{ field|read_fn }}(buf),
+ {%- endfor %}
+ {%- endif %}
+ )
+ {%- endfor %}
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ @staticmethod
+ def write(value, buf):
+ {%- for variant in e.variants() %}
+ if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}):
+ buf.writeI32({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ {%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
new file mode 100644
index 0000000000..a5f2584c62
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
@@ -0,0 +1,7 @@
+{%- let mod_name = crate_name|fn_name %}
+
+{%- let ffi_converter_name = "FfiConverterType{}"|format(name) %}
+{{ self.add_import_of(mod_name, ffi_converter_name) }}
+
+{%- let rustbuffer_local_name = "RustBuffer{}"|format(name) %}
+{{ self.add_import_of_as(mod_name, "RustBuffer", rustbuffer_local_name) }}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
new file mode 100644
index 0000000000..e5783dd158
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterFloat(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readFloat()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeFloat(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
new file mode 100644
index 0000000000..57c2131eb8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterDouble(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readDouble()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeDouble(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
new file mode 100644
index 0000000000..fbce501981
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
@@ -0,0 +1,67 @@
+# A handful of classes and functions to support the generated data structures.
+# This would be a good candidate for isolating in its own ffi-support lib.
+
+class InternalError(Exception):
+ pass
+
+class RustCallStatus(ctypes.Structure):
+ """
+ Error runtime.
+ """
+ _fields_ = [
+ ("code", ctypes.c_int8),
+ ("error_buf", RustBuffer),
+ ]
+
+ # These match the values from the uniffi::rustcalls module
+ CALL_SUCCESS = 0
+ CALL_ERROR = 1
+ CALL_PANIC = 2
+
+ def __str__(self):
+ if self.code == RustCallStatus.CALL_SUCCESS:
+ return "RustCallStatus(CALL_SUCCESS)"
+ elif self.code == RustCallStatus.CALL_ERROR:
+ return "RustCallStatus(CALL_ERROR)"
+ elif self.code == RustCallStatus.CALL_PANIC:
+ return "RustCallStatus(CALL_PANIC)"
+ else:
+ return "RustCallStatus(<invalid code>)"
+
+def rust_call(fn, *args):
+ # Call a rust function
+ return rust_call_with_error(None, fn, *args)
+
+def rust_call_with_error(error_ffi_converter, fn, *args):
+ # Call a rust function and handle any errors
+ #
+ # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code.
+ # error_ffi_converter must be set to the FfiConverter for the error class that corresponds to the result.
+ call_status = RustCallStatus(code=RustCallStatus.CALL_SUCCESS, error_buf=RustBuffer(0, 0, None))
+
+ args_with_error = args + (ctypes.byref(call_status),)
+ result = fn(*args_with_error)
+ if call_status.code == RustCallStatus.CALL_SUCCESS:
+ return result
+ elif call_status.code == RustCallStatus.CALL_ERROR:
+ if error_ffi_converter is None:
+ call_status.error_buf.free()
+ raise InternalError("rust_call_with_error: CALL_ERROR, but error_ffi_converter is None")
+ else:
+ raise error_ffi_converter.lift(call_status.error_buf)
+ elif call_status.code == RustCallStatus.CALL_PANIC:
+ # When the rust code sees a panic, it tries to construct a RustBuffer
+ # with the message. But if that code panics, then it just sends back
+ # an empty buffer.
+ if call_status.error_buf.len > 0:
+ msg = FfiConverterString.lift(call_status.error_buf)
+ else:
+ msg = "Unknown rust panic"
+ raise InternalError(msg)
+ else:
+ raise InternalError("Invalid RustCallStatus code: {}".format(
+ call_status.code))
+
+# A function pointer for a callback as defined by UniFFI.
+# Rust definition `fn(handle: u64, method: u32, args: RustBuffer, buf_ptr: *mut RustBuffer) -> int`
+FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, RustBuffer, ctypes.POINTER(RustBuffer))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
new file mode 100644
index 0000000000..b3d9602746
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt16(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI16()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI16(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
new file mode 100644
index 0000000000..47a54d8c4a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt32(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI32()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI32(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
new file mode 100644
index 0000000000..471b9967ce
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt64(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI64()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI64(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
new file mode 100644
index 0000000000..524fea7ea9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt8(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI8()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI8(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
new file mode 100644
index 0000000000..7f42daa7a9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
@@ -0,0 +1,27 @@
+{%- let key_ffi_converter = key_type|ffi_converter_name %}
+{%- let value_ffi_converter = value_type|ffi_converter_name %}
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @classmethod
+ def write(cls, items, buf):
+ buf.writeI32(len(items))
+ for (key, value) in items.items():
+ {{ key_ffi_converter }}.write(key, buf)
+ {{ value_ffi_converter }}.write(value, buf)
+
+ @classmethod
+ def read(cls, buf):
+ count = buf.readI32()
+ if count < 0:
+ raise InternalError("Unexpected negative map size")
+
+ # It would be nice to use a dict comprehension,
+ # but in Python 3.7 and before the evaluation order is not according to spec,
+ # so we we're reading the value before the key.
+ # This loop makes the order explicit: first reading the key, then the value.
+ d = {}
+ for i in range(count):
+ key = {{ key_ffi_converter }}.read(buf)
+ val = {{ value_ffi_converter }}.read(buf)
+ d[key] = val
+ return d
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
new file mode 100644
index 0000000000..4867ecb9b4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
@@ -0,0 +1,39 @@
+# This is how we find and load the dynamic library provided by the component.
+# For now we just look it up by name.
+#
+# XXX TODO: This will probably grow some magic for resolving megazording in future.
+# E.g. we might start by looking for the named component in `libuniffi.so` and if
+# that fails, fall back to loading it separately from `lib${componentName}.so`.
+
+from pathlib import Path
+
+def loadIndirect():
+ if sys.platform == "darwin":
+ libname = "lib{}.dylib"
+ elif sys.platform.startswith("win"):
+ # As of python3.8, ctypes does not seem to search $PATH when loading DLLs.
+ # We could use `os.add_dll_directory` to configure the search path, but
+ # it doesn't feel right to mess with application-wide settings. Let's
+ # assume that the `.dll` is next to the `.py` file and load by full path.
+ libname = os.path.join(
+ os.path.dirname(__file__),
+ "{}.dll",
+ )
+ else:
+ # Anything else must be an ELF platform - Linux, *BSD, Solaris/illumos
+ libname = "lib{}.so"
+
+ lib = libname.format("{{ config.cdylib_name() }}")
+ path = str(Path(__file__).parent / lib)
+ return ctypes.cdll.LoadLibrary(path)
+
+# A ctypes library to expose the extern-C FFI definitions.
+# This is an implementation detail which will be called internally by the public API.
+
+_UniFFILib = loadIndirect()
+{%- for func in ci.iter_ffi_function_definitions() %}
+_UniFFILib.{{ func.name() }}.argtypes = (
+ {%- call py::arg_list_ffi_decl(func) -%}
+)
+_UniFFILib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}None{% endmatch %}
+{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
new file mode 100644
index 0000000000..cc4cc35b4e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
@@ -0,0 +1,74 @@
+{%- let obj = ci.get_object_definition(name).unwrap() %}
+
+class {{ type_name }}(object):
+ {%- match obj.primary_constructor() %}
+ {%- when Some with (cons) %}
+ def __init__(self, {% call py::arg_list_decl(cons) -%}):
+ {%- call py::setup_args_extra_indent(cons) %}
+ self._pointer = {% call py::to_ffi_call(cons) %}
+ {%- when None %}
+ {%- endmatch %}
+
+ def __del__(self):
+ # In case of partial initialization of instances.
+ pointer = getattr(self, "_pointer", None)
+ if pointer is not None:
+ rust_call(_UniFFILib.{{ obj.ffi_object_free().name() }}, pointer)
+
+ # Used by alternative constructors or any methods which return this type.
+ @classmethod
+ def _make_instance_(cls, pointer):
+ # Lightly yucky way to bypass the usual __init__ logic
+ # and just create a new instance with the required pointer.
+ inst = cls.__new__(cls)
+ inst._pointer = pointer
+ return inst
+
+ {% for cons in obj.alternate_constructors() -%}
+ @classmethod
+ def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}):
+ {%- call py::setup_args_extra_indent(cons) %}
+ # Call the (fallible) function before creating any half-baked object instances.
+ pointer = {% call py::to_ffi_call(cons) %}
+ return cls._make_instance_(pointer)
+ {% endfor %}
+
+ {% for meth in obj.methods() -%}
+ {%- match meth.return_type() -%}
+
+ {%- when Some with (return_type) -%}
+ def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}):
+ {%- call py::setup_args_extra_indent(meth) %}
+ return {{ return_type|lift_fn }}(
+ {% call py::to_ffi_call_with_prefix("self._pointer", meth) %}
+ )
+
+ {%- when None -%}
+ def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}):
+ {%- call py::setup_args_extra_indent(meth) %}
+ {% call py::to_ffi_call_with_prefix("self._pointer", meth) %}
+ {% endmatch %}
+ {% endfor %}
+
+
+class {{ ffi_converter_name }}:
+ @classmethod
+ def read(cls, buf):
+ ptr = buf.readU64()
+ if ptr == 0:
+ raise InternalError("Raw pointer value was null")
+ return cls.lift(ptr)
+
+ @classmethod
+ def write(cls, value, buf):
+ if not isinstance(value, {{ type_name }}):
+ raise TypeError("Expected {{ type_name }} instance, {} found".format(value.__class__.__name__))
+ buf.writeU64(cls.lower(value))
+
+ @staticmethod
+ def lift(value):
+ return {{ type_name }}._make_instance_(value)
+
+ @staticmethod
+ def lower(value):
+ return value._pointer
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
new file mode 100644
index 0000000000..70f705362f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
@@ -0,0 +1,21 @@
+{%- let inner_ffi_converter = inner_type|ffi_converter_name %}
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @classmethod
+ def write(cls, value, buf):
+ if value is None:
+ buf.writeU8(0)
+ return
+
+ buf.writeU8(1)
+ {{ inner_ffi_converter }}.write(value, buf)
+
+ @classmethod
+ def read(cls, buf):
+ flag = buf.readU8()
+ if flag == 0:
+ return None
+ elif flag == 1:
+ return {{ inner_ffi_converter }}.read(buf)
+ else:
+ raise InternalError("Unexpected flag byte for optional type")
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
new file mode 100644
index 0000000000..2009d59e8a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
@@ -0,0 +1,45 @@
+{%- let rec = ci.get_record_definition(name).unwrap() %}
+class {{ type_name }}:
+
+ def __init__(self, {% for field in rec.fields() %}
+ {{- field.name()|var_name }}
+ {%- if field.default_value().is_some() %} = DEFAULT{% endif %}
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}):
+ {%- for field in rec.fields() %}
+ {%- let field_name = field.name()|var_name %}
+ {%- match field.default_value() %}
+ {%- when None %}
+ self.{{ field_name }} = {{ field_name }}
+ {%- when Some with(literal) %}
+ if {{ field_name }} is DEFAULT:
+ self.{{ field_name }} = {{ literal|literal_py(field) }}
+ else:
+ self.{{ field_name }} = {{ field_name }}
+ {%- endmatch %}
+ {%- endfor %}
+
+ def __str__(self):
+ return "{{ type_name }}({% for field in rec.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
+
+ def __eq__(self, other):
+ {%- for field in rec.fields() %}
+ if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}:
+ return False
+ {%- endfor %}
+ return True
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ return {{ type_name }}(
+ {%- for field in rec.fields() %}
+ {{ field.name()|var_name }}={{ field|read_fn }}(buf),
+ {%- endfor %}
+ )
+
+ @staticmethod
+ def write(value, buf):
+ {%- for field in rec.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
new file mode 100644
index 0000000000..eafd2346bb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
@@ -0,0 +1,23 @@
+# Types conforming to `FfiConverterPrimitive` pass themselves directly over the FFI.
+class FfiConverterPrimitive:
+ @classmethod
+ def lift(cls, value):
+ return value
+
+ @classmethod
+ def lower(cls, value):
+ return value
+
+# Helper class for wrapper types that will always go through a RustBuffer.
+# Classes should inherit from this and implement the `read` and `write` static methods.
+class FfiConverterRustBuffer:
+ @classmethod
+ def lift(cls, rbuf):
+ with rbuf.consumeWithStream() as stream:
+ return cls.read(stream)
+
+ @classmethod
+ def lower(cls, value):
+ with RustBuffer.allocWithBuilder() as builder:
+ cls.write(value, builder)
+ return builder.finalize()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
new file mode 100644
index 0000000000..95a3b1f706
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
@@ -0,0 +1,190 @@
+
+class RustBuffer(ctypes.Structure):
+ _fields_ = [
+ ("capacity", ctypes.c_int32),
+ ("len", ctypes.c_int32),
+ ("data", ctypes.POINTER(ctypes.c_char)),
+ ]
+
+ @staticmethod
+ def alloc(size):
+ return rust_call(_UniFFILib.{{ ci.ffi_rustbuffer_alloc().name() }}, size)
+
+ @staticmethod
+ def reserve(rbuf, additional):
+ return rust_call(_UniFFILib.{{ ci.ffi_rustbuffer_reserve().name() }}, rbuf, additional)
+
+ def free(self):
+ return rust_call(_UniFFILib.{{ ci.ffi_rustbuffer_free().name() }}, self)
+
+ def __str__(self):
+ return "RustBuffer(capacity={}, len={}, data={})".format(
+ self.capacity,
+ self.len,
+ self.data[0:self.len]
+ )
+
+ @contextlib.contextmanager
+ def allocWithBuilder():
+ """Context-manger to allocate a buffer using a RustBufferBuilder.
+
+ The allocated buffer will be automatically freed if an error occurs, ensuring that
+ we don't accidentally leak it.
+ """
+ builder = RustBufferBuilder()
+ try:
+ yield builder
+ except:
+ builder.discard()
+ raise
+
+ @contextlib.contextmanager
+ def consumeWithStream(self):
+ """Context-manager to consume a buffer using a RustBufferStream.
+
+ The RustBuffer will be freed once the context-manager exits, ensuring that we don't
+ leak it even if an error occurs.
+ """
+ try:
+ s = RustBufferStream(self)
+ yield s
+ if s.remaining() != 0:
+ raise RuntimeError("junk data left in buffer after consuming")
+ finally:
+ self.free()
+
+
+class ForeignBytes(ctypes.Structure):
+ _fields_ = [
+ ("len", ctypes.c_int32),
+ ("data", ctypes.POINTER(ctypes.c_char)),
+ ]
+
+ def __str__(self):
+ return "ForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len])
+
+
+class RustBufferStream(object):
+ """
+ Helper for structured reading of bytes from a RustBuffer
+ """
+
+ def __init__(self, rbuf):
+ self.rbuf = rbuf
+ self.offset = 0
+
+ def remaining(self):
+ return self.rbuf.len - self.offset
+
+ def _unpack_from(self, size, format):
+ if self.offset + size > self.rbuf.len:
+ raise InternalError("read past end of rust buffer")
+ value = struct.unpack(format, self.rbuf.data[self.offset:self.offset+size])[0]
+ self.offset += size
+ return value
+
+ def read(self, size):
+ if self.offset + size > self.rbuf.len:
+ raise InternalError("read past end of rust buffer")
+ data = self.rbuf.data[self.offset:self.offset+size]
+ self.offset += size
+ return data
+
+ def readI8(self):
+ return self._unpack_from(1, ">b")
+
+ def readU8(self):
+ return self._unpack_from(1, ">B")
+
+ def readI16(self):
+ return self._unpack_from(2, ">h")
+
+ def readU16(self):
+ return self._unpack_from(2, ">H")
+
+ def readI32(self):
+ return self._unpack_from(4, ">i")
+
+ def readU32(self):
+ return self._unpack_from(4, ">I")
+
+ def readI64(self):
+ return self._unpack_from(8, ">q")
+
+ def readU64(self):
+ return self._unpack_from(8, ">Q")
+
+ def readFloat(self):
+ v = self._unpack_from(4, ">f")
+ return v
+
+ def readDouble(self):
+ return self._unpack_from(8, ">d")
+
+
+class RustBufferBuilder(object):
+ """
+ Helper for structured writing of bytes into a RustBuffer.
+ """
+
+ def __init__(self):
+ self.rbuf = RustBuffer.alloc(16)
+ self.rbuf.len = 0
+
+ def finalize(self):
+ rbuf = self.rbuf
+ self.rbuf = None
+ return rbuf
+
+ def discard(self):
+ if self.rbuf is not None:
+ rbuf = self.finalize()
+ rbuf.free()
+
+ @contextlib.contextmanager
+ def _reserve(self, numBytes):
+ if self.rbuf.len + numBytes > self.rbuf.capacity:
+ self.rbuf = RustBuffer.reserve(self.rbuf, numBytes)
+ yield None
+ self.rbuf.len += numBytes
+
+ def _pack_into(self, size, format, value):
+ with self._reserve(size):
+ # XXX TODO: I feel like I should be able to use `struct.pack_into` here but can't figure it out.
+ for i, byte in enumerate(struct.pack(format, value)):
+ self.rbuf.data[self.rbuf.len + i] = byte
+
+ def write(self, value):
+ with self._reserve(len(value)):
+ for i, byte in enumerate(value):
+ self.rbuf.data[self.rbuf.len + i] = byte
+
+ def writeI8(self, v):
+ self._pack_into(1, ">b", v)
+
+ def writeU8(self, v):
+ self._pack_into(1, ">B", v)
+
+ def writeI16(self, v):
+ self._pack_into(2, ">h", v)
+
+ def writeU16(self, v):
+ self._pack_into(2, ">H", v)
+
+ def writeI32(self, v):
+ self._pack_into(4, ">i", v)
+
+ def writeU32(self, v):
+ self._pack_into(4, ">I", v)
+
+ def writeI64(self, v):
+ self._pack_into(8, ">q", v)
+
+ def writeU64(self, v):
+ self._pack_into(8, ">Q", v)
+
+ def writeFloat(self, v):
+ self._pack_into(4, ">f", v)
+
+ def writeDouble(self, v):
+ self._pack_into(8, ">d", v)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
new file mode 100644
index 0000000000..6de7bf2d4d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
@@ -0,0 +1,19 @@
+{%- let inner_ffi_converter = inner_type|ffi_converter_name %}
+
+class {{ ffi_converter_name}}(FfiConverterRustBuffer):
+ @classmethod
+ def write(cls, value, buf):
+ items = len(value)
+ buf.writeI32(items)
+ for item in value:
+ {{ inner_ffi_converter }}.write(item, buf)
+
+ @classmethod
+ def read(cls, buf):
+ count = buf.readI32()
+ if count < 0:
+ raise InternalError("Unexpected negative sequence length")
+
+ return [
+ {{ inner_ffi_converter }}.read(buf) for i in range(count)
+ ]
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
new file mode 100644
index 0000000000..f205f7c746
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
@@ -0,0 +1,25 @@
+class FfiConverterString:
+ @staticmethod
+ def read(buf):
+ size = buf.readI32()
+ if size < 0:
+ raise InternalError("Unexpected negative string length")
+ utf8Bytes = buf.read(size)
+ return utf8Bytes.decode("utf-8")
+
+ @staticmethod
+ def write(value, buf):
+ utf8Bytes = value.encode("utf-8")
+ buf.writeI32(len(utf8Bytes))
+ buf.write(utf8Bytes)
+
+ @staticmethod
+ def lift(buf):
+ with buf.consumeWithStream() as stream:
+ return stream.read(stream.remaining()).decode("utf-8")
+
+ @staticmethod
+ def lower(value):
+ with RustBuffer.allocWithBuilder() as builder:
+ builder.write(value.encode("utf-8"))
+ return builder.finalize()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
new file mode 100644
index 0000000000..ac87697551
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
@@ -0,0 +1,30 @@
+# The Timestamp type.
+# There is a loss of precision when converting from Rust timestamps,
+# which are accurate to the nanosecond,
+# to Python datetimes, which have a variable precision due to the use of float as representation.
+class FfiConverterTimestamp(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ seconds = buf.readI64()
+ microseconds = buf.readU32() / 1000
+ # Use fromtimestamp(0) then add the seconds using a timedelta. This
+ # ensures that we get OverflowError rather than ValueError when
+ # seconds is too large.
+ if seconds >= 0:
+ return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) + datetime.timedelta(seconds=seconds, microseconds=microseconds)
+ else:
+ return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) - datetime.timedelta(seconds=-seconds, microseconds=microseconds)
+
+ @staticmethod
+ def write(value, buf):
+ if value >= datetime.datetime.fromtimestamp(0, datetime.timezone.utc):
+ sign = 1
+ delta = value - datetime.datetime.fromtimestamp(0, datetime.timezone.utc)
+ else:
+ sign = -1
+ delta = datetime.datetime.fromtimestamp(0, datetime.timezone.utc) - value
+
+ seconds = delta.seconds + delta.days * 24 * 3600
+ nanoseconds = delta.microseconds * 1000
+ buf.writeI64(sign * seconds)
+ buf.writeU32(nanoseconds)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
new file mode 100644
index 0000000000..2d0e09f22d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
@@ -0,0 +1,13 @@
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+
+def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
+ {%- call py::setup_args(func) %}
+ return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %})
+
+{% when None %}
+
+def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
+ {%- call py::setup_args(func) %}
+ {% call py::to_ffi_call(func) %}
+{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
new file mode 100644
index 0000000000..ce65472a64
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
@@ -0,0 +1,93 @@
+{%- import "macros.py" as py %}
+
+{%- for type_ in ci.iter_types() %}
+{%- let type_name = type_|type_name %}
+{%- let ffi_converter_name = type_|ffi_converter_name %}
+{%- let canonical_type_name = type_|canonical_name %}
+
+{#
+ # Map `Type` instances to an include statement for that type.
+ #
+ # There is a companion match in `PythonCodeOracle::create_code_type()` which performs a similar function for the
+ # Rust code.
+ #
+ # - When adding additional types here, make sure to also add a match arm to that function.
+ # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ #}
+{%- match type_ %}
+
+{%- when Type::Boolean %}
+{%- include "BooleanHelper.py" %}
+
+{%- when Type::Int8 %}
+{%- include "Int8Helper.py" %}
+
+{%- when Type::Int16 %}
+{%- include "Int16Helper.py" %}
+
+{%- when Type::Int32 %}
+{%- include "Int32Helper.py" %}
+
+{%- when Type::Int64 %}
+{%- include "Int64Helper.py" %}
+
+{%- when Type::UInt8 %}
+{%- include "UInt8Helper.py" %}
+
+{%- when Type::UInt16 %}
+{%- include "UInt16Helper.py" %}
+
+{%- when Type::UInt32 %}
+{%- include "UInt32Helper.py" %}
+
+{%- when Type::UInt64 %}
+{%- include "UInt64Helper.py" %}
+
+{%- when Type::Float32 %}
+{%- include "Float32Helper.py" %}
+
+{%- when Type::Float64 %}
+{%- include "Float64Helper.py" %}
+
+{%- when Type::String %}
+{%- include "StringHelper.py" %}
+
+{%- when Type::Enum(name) %}
+{%- include "EnumTemplate.py" %}
+
+{%- when Type::Error(name) %}
+{%- include "ErrorTemplate.py" %}
+
+{%- when Type::Record(name) %}
+{%- include "RecordTemplate.py" %}
+
+{%- when Type::Object(name) %}
+{%- include "ObjectTemplate.py" %}
+
+{%- when Type::Timestamp %}
+{%- include "TimestampHelper.py" %}
+
+{%- when Type::Duration %}
+{%- include "DurationHelper.py" %}
+
+{%- when Type::Optional(inner_type) %}
+{%- include "OptionalTemplate.py" %}
+
+{%- when Type::Sequence(inner_type) %}
+{%- include "SequenceTemplate.py" %}
+
+{%- when Type::Map(key_type, value_type) %}
+{%- include "MapTemplate.py" %}
+
+{%- when Type::CallbackInterface(id) %}
+{%- include "CallbackInterfaceTemplate.py" %}
+
+{%- when Type::Custom { name, builtin } %}
+{%- include "CustomType.py" %}
+
+{%- when Type::External { name, crate_name } %}
+{%- include "ExternalTemplate.py" %}
+
+{%- else %}
+{%- endmatch %}
+{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
new file mode 100644
index 0000000000..f1cd114c0c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt16(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU16()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU16(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
new file mode 100644
index 0000000000..290cd5fe82
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt32(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU32()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU32(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
new file mode 100644
index 0000000000..f57dec1d92
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt64(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU64()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU64(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
new file mode 100644
index 0000000000..3a1582351b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt8(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU8()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU8(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
new file mode 100644
index 0000000000..d11fcae921
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
@@ -0,0 +1,101 @@
+{#
+// Template to call into rust. Used in several places.
+// Variable names in `arg_list_decl` should match up with arg lists
+// passed to rust via `_arg_list_ffi_call`
+#}
+
+{%- macro to_ffi_call(func) -%}
+ {%- match func.throws_type() -%}
+ {%- when Some with (e) -%}
+rust_call_with_error({{ e|ffi_converter_name }},
+ {%- else -%}
+rust_call(
+ {%- endmatch -%}
+ _UniFFILib.{{ func.ffi_func().name() }},
+ {%- call _arg_list_ffi_call(func) -%}
+)
+{%- endmacro -%}
+
+{%- macro to_ffi_call_with_prefix(prefix, func) -%}
+ {%- match func.throws_type() -%}
+ {%- when Some with (e) -%}
+rust_call_with_error(
+ {{ e|ffi_converter_name }},
+ {%- else -%}
+rust_call(
+ {%- endmatch -%}
+ _UniFFILib.{{ func.ffi_func().name() }},
+ {{- prefix }},
+ {%- call _arg_list_ffi_call(func) -%}
+)
+{%- endmacro -%}
+
+{%- macro _arg_list_ffi_call(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg|lower_fn }}({{ arg.name()|var_name }})
+ {%- if !loop.last %},{% endif %}
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in Python declarations of methods, functions and constructors.
+// Note the var_name and type_name filters.
+-#}
+
+{% macro arg_list_decl(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name }}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = DEFAULT
+ {%- else %}
+ {%- endmatch %}
+ {%- if !loop.last %},{% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+{#-
+// Arglist as used in the _UniFFILib function declarations.
+// Note unfiltered name but ffi_type_name filters.
+-#}
+{%- macro arg_list_ffi_decl(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg.type_().borrow()|ffi_type_name }},
+ {%- endfor %}
+ ctypes.POINTER(RustCallStatus),
+{% endmacro -%}
+
+{#
+ # Setup function arguments by initializing default values and passing other
+ # values through coerce.
+ #}
+{%- macro setup_args(func) %}
+ {%- for arg in func.arguments() %}
+ {%- match arg.default_value() %}
+ {%- when None %}
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- when Some with(literal) %}
+ if {{ arg.name()|var_name }} is DEFAULT:
+ {{ arg.name()|var_name }} = {{ literal|literal_py(arg.type_().borrow()) }}
+ else:
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- endmatch %}
+ {% endfor -%}
+{%- endmacro -%}
+
+{#
+ # Exactly the same thing as `setup_args()` but with an extra 4 spaces of
+ # indent so that it works with object methods.
+ #}
+{%- macro setup_args_extra_indent(func) %}
+ {%- for arg in func.arguments() %}
+ {%- match arg.default_value() %}
+ {%- when None %}
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- when Some with(literal) %}
+ if {{ arg.name()|var_name }} is DEFAULT:
+ {{ arg.name()|var_name }} = {{ literal|literal_py(arg.type_().borrow()) }}
+ else:
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- endmatch %}
+ {% endfor -%}
+{%- endmacro -%}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
new file mode 100644
index 0000000000..d30d3c9d12
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
@@ -0,0 +1,71 @@
+# This file was autogenerated by some hot garbage in the `uniffi` crate.
+# Trust me, you don't want to mess with it!
+
+# Tell mypy (a type checker) to ignore all errors from this file.
+# See https://mypy.readthedocs.io/en/stable/config_file.html?highlight=ignore-errors#confval-ignore_errors
+# mypy: ignore-errors
+
+# Common helper code.
+#
+# Ideally this would live in a separate .py file where it can be unittested etc
+# in isolation, and perhaps even published as a re-useable package.
+#
+# However, it's important that the details of how this helper code works (e.g. the
+# way that different builtin types are passed across the FFI) exactly match what's
+# expected by the rust code on the other side of the interface. In practice right
+# now that means coming from the exact some version of `uniffi` that was used to
+# compile the rust component. The easiest way to ensure this is to bundle the Python
+# helpers directly inline like we're doing here.
+
+import os
+import sys
+import ctypes
+import enum
+import struct
+import contextlib
+import datetime
+{%- for req in self.imports() %}
+{{ req.render() }}
+{%- endfor %}
+
+# Used for default argument values
+DEFAULT = object()
+
+{% include "RustBufferTemplate.py" %}
+{% include "Helpers.py" %}
+{% include "RustBufferHelper.py" %}
+
+# Contains loading, initialization code,
+# and the FFI Function declarations in a com.sun.jna.Library.
+{% include "NamespaceLibraryTemplate.py" %}
+
+# Public interface members begin here.
+{{ type_helper_code }}
+
+{%- for func in ci.function_definitions() %}
+{%- include "TopLevelFunctionTemplate.py" %}
+{%- endfor %}
+
+__all__ = [
+ "InternalError",
+ {%- for e in ci.enum_definitions() %}
+ "{{ e|type_name }}",
+ {%- endfor %}
+ {%- for record in ci.record_definitions() %}
+ "{{ record|type_name }}",
+ {%- endfor %}
+ {%- for func in ci.function_definitions() %}
+ "{{ func.name()|fn_name }}",
+ {%- endfor %}
+ {%- for obj in ci.object_definitions() %}
+ "{{ obj|type_name }}",
+ {%- endfor %}
+ {%- for e in ci.error_definitions() %}
+ "{{ e|type_name }}",
+ {%- endfor %}
+ {%- for c in ci.callback_interface_definitions() %}
+ "{{ c.name()|class_name }}",
+ {%- endfor %}
+]
+
+{% import "macros.py" as py %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
new file mode 100644
index 0000000000..d050fc0389
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
@@ -0,0 +1,62 @@
+/* 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 anyhow::{Context, Result};
+use camino::Utf8Path;
+use std::env;
+use std::ffi::OsString;
+use std::process::{Command, Stdio};
+use uniffi_testing::UniFFITestHelper;
+
+/// Run Python tests for a UniFFI test fixture
+pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
+ let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let test_helper = UniFFITestHelper::new(fixture_name).context("UniFFITestHelper::new")?;
+ let out_dir = test_helper
+ .create_out_dir(tmp_dir, &script_path)
+ .context("create_out_dir")?;
+ test_helper
+ .copy_cdylibs_to_out_dir(&out_dir)
+ .context("copy_cdylibs_to_out_dir")?;
+ generate_sources(&test_helper.cdylib_path()?, &out_dir, &test_helper)
+ .context("generate_sources")?;
+
+ let pythonpath = env::var_os("PYTHONPATH").unwrap_or_else(|| OsString::from(""));
+ let pythonpath = env::join_paths(
+ env::split_paths(&pythonpath).chain(vec![out_dir.to_path_buf().into_std_path_buf()]),
+ )?;
+
+ let status = Command::new("python3")
+ .current_dir(out_dir)
+ .env("PYTHONPATH", pythonpath)
+ .arg(script_path)
+ .stderr(Stdio::inherit())
+ .stdout(Stdio::inherit())
+ .spawn()
+ .context("Failed to spawn `python3` when running script")?
+ .wait()
+ .context("Failed to wait for `python3` when running script")?;
+ if !status.success() {
+ anyhow::bail!("running `python3` failed");
+ }
+ Ok(())
+}
+
+fn generate_sources(
+ library_path: &Utf8Path,
+ out_dir: &Utf8Path,
+ test_helper: &UniFFITestHelper,
+) -> Result<()> {
+ for source in test_helper.get_compile_sources()? {
+ crate::generate_bindings(
+ &source.udl_path,
+ source.config_path.as_deref(),
+ vec!["python"],
+ Some(out_dir),
+ Some(library_path),
+ false,
+ )?;
+ }
+ Ok(())
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
new file mode 100644
index 0000000000..9d0fc55bdc
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
@@ -0,0 +1,273 @@
+/* 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 anyhow::Result;
+use askama::Template;
+use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
+use serde::{Deserialize, Serialize};
+use std::borrow::Borrow;
+
+use crate::interface::*;
+use crate::MergeWith;
+
+const RESERVED_WORDS: &[&str] = &[
+ "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else",
+ "elsif", "END", "end", "ensure", "false", "for", "if", "module", "next", "nil", "not", "or",
+ "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless",
+ "until", "when", "while", "yield", "__FILE__", "__LINE__",
+];
+
+fn is_reserved_word(word: &str) -> bool {
+ RESERVED_WORDS.contains(&word)
+}
+
+// Some config options for it the caller wants to customize the generated ruby.
+// Note that this can only be used to control details of the ruby *that do not affect the underlying component*,
+// since the details of the underlying component are entirely determined by the `ComponentInterface`.
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Config {
+ cdylib_name: Option<String>,
+ cdylib_path: Option<String>,
+}
+
+impl Config {
+ pub fn cdylib_name(&self) -> String {
+ self.cdylib_name
+ .clone()
+ .unwrap_or_else(|| "uniffi".to_string())
+ }
+
+ pub fn custom_cdylib_path(&self) -> bool {
+ self.cdylib_path.is_some()
+ }
+
+ pub fn cdylib_path(&self) -> String {
+ self.cdylib_path.clone().unwrap_or_default()
+ }
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ cdylib_name: Some(format!("uniffi_{}", ci.namespace())),
+ cdylib_path: None,
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ cdylib_name: self.cdylib_name.merge_with(&other.cdylib_name),
+ cdylib_path: self.cdylib_path.merge_with(&other.cdylib_path),
+ }
+ }
+}
+
+#[derive(Template)]
+#[template(syntax = "rb", escape = "none", path = "wrapper.rb")]
+pub struct RubyWrapper<'a> {
+ config: Config,
+ ci: &'a ComponentInterface,
+}
+impl<'a> RubyWrapper<'a> {
+ pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
+ Self { config, ci }
+ }
+}
+
+mod filters {
+ use super::*;
+
+ pub fn type_ffi(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ FfiType::Int8 => ":int8".to_string(),
+ FfiType::UInt8 => ":uint8".to_string(),
+ FfiType::Int16 => ":int16".to_string(),
+ FfiType::UInt16 => ":uint16".to_string(),
+ FfiType::Int32 => ":int32".to_string(),
+ FfiType::UInt32 => ":uint32".to_string(),
+ FfiType::Int64 => ":int64".to_string(),
+ FfiType::UInt64 => ":uint64".to_string(),
+ FfiType::Float32 => ":float".to_string(),
+ FfiType::Float64 => ":double".to_string(),
+ FfiType::RustArcPtr(_) => ":pointer".to_string(),
+ FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(),
+ FfiType::ForeignBytes => "ForeignBytes".to_string(),
+ FfiType::ForeignCallback => unimplemented!("Callback interfaces are not implemented"),
+ })
+ }
+
+ pub fn literal_rb(literal: &Literal) -> Result<String, askama::Error> {
+ Ok(match literal {
+ Literal::Boolean(v) => {
+ if *v {
+ "true".into()
+ } else {
+ "false".into()
+ }
+ }
+ // use the double-quote form to match with the other languages, and quote escapes.
+ Literal::String(s) => format!("\"{s}\""),
+ Literal::Null => "nil".into(),
+ Literal::EmptySequence => "[]".into(),
+ Literal::EmptyMap => "{}".into(),
+ Literal::Enum(v, type_) => match type_ {
+ Type::Enum(name) => format!("{}::{}", class_name_rb(name)?, enum_name_rb(v)?),
+ _ => panic!("Unexpected type in enum literal: {type_:?}"),
+ },
+ // https://docs.ruby-lang.org/en/2.0.0/syntax/literals_rdoc.html
+ Literal::Int(i, radix, _) => match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ Literal::UInt(i, radix, _) => match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ Literal::Float(string, _type_) => string.clone(),
+ })
+ }
+
+ pub fn class_name_rb(nm: &str) -> Result<String, askama::Error> {
+ Ok(nm.to_string().to_upper_camel_case())
+ }
+
+ pub fn fn_name_rb(nm: &str) -> Result<String, askama::Error> {
+ Ok(nm.to_string().to_snake_case())
+ }
+
+ pub fn var_name_rb(nm: &str) -> Result<String, askama::Error> {
+ let nm = nm.to_string();
+ let prefix = if is_reserved_word(&nm) { "_" } else { "" };
+
+ Ok(format!("{prefix}{}", nm.to_snake_case()))
+ }
+
+ pub fn enum_name_rb(nm: &str) -> Result<String, askama::Error> {
+ Ok(nm.to_string().to_shouty_snake_case())
+ }
+
+ pub fn coerce_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ Type::Int8
+ | Type::UInt8
+ | Type::Int16
+ | Type::UInt16
+ | Type::Int32
+ | Type::UInt32
+ | Type::Int64
+ | Type::UInt64 => format!("{nm}.to_i"), // TODO: check max/min value
+ Type::Float32 | Type::Float64 => format!("{nm}.to_f"),
+ Type::Boolean => format!("{nm} ? true : false"),
+ Type::Object(_) | Type::Enum(_) | Type::Error(_) | Type::Record(_) => nm.to_string(),
+ Type::String => format!("{nm}.to_s"),
+ Type::Timestamp | Type::Duration => nm.to_string(),
+ Type::CallbackInterface(_) => panic!("No support for coercing callback interfaces yet"),
+ Type::Optional(t) => format!("({nm} ? {} : nil)", coerce_rb(nm, t)?),
+ Type::Sequence(t) => {
+ let coerce_code = coerce_rb("v", t)?;
+ if coerce_code == "v" {
+ nm.to_string()
+ } else {
+ format!("{nm}.map {{ |v| {coerce_code} }}")
+ }
+ }
+ Type::Map(_k, t) => {
+ let k_coerce_code = coerce_rb("k", &Type::String)?;
+ let v_coerce_code = coerce_rb("v", t)?;
+
+ if k_coerce_code == "k" && v_coerce_code == "v" {
+ nm.to_string()
+ } else {
+ format!(
+ "{}.each.with_object({{}}) {{ |(k, v), res| res[{}] = {} }}",
+ nm, k_coerce_code, v_coerce_code,
+ )
+ }
+ }
+ Type::External { .. } => panic!("No support for external types, yet"),
+ Type::Custom { .. } => panic!("No support for custom types, yet"),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling coerce_rb")
+ }
+ })
+ }
+
+ pub fn lower_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ Type::Int8
+ | Type::UInt8
+ | Type::Int16
+ | Type::UInt16
+ | Type::Int32
+ | Type::UInt32
+ | Type::Int64
+ | Type::UInt64
+ | Type::Float32
+ | Type::Float64 => nm.to_string(),
+ Type::Boolean => format!("({nm} ? 1 : 0)"),
+ Type::String => format!("RustBuffer.allocFromString({nm})"),
+ Type::Object(name) => format!("({}._uniffi_lower {nm})", class_name_rb(name)?),
+ Type::CallbackInterface(_) => panic!("No support for lowering callback interfaces yet"),
+ Type::Error(_) => panic!("No support for lowering errors, yet"),
+ Type::Enum(_)
+ | Type::Record(_)
+ | Type::Optional(_)
+ | Type::Sequence(_)
+ | Type::Timestamp
+ | Type::Duration
+ | Type::Map(_, _) => format!(
+ "RustBuffer.alloc_from_{}({})",
+ class_name_rb(&type_.canonical_name())?,
+ nm
+ ),
+ Type::External { .. } => panic!("No support for lowering external types, yet"),
+ Type::Custom { .. } => panic!("No support for lowering custom types, yet"),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling lower_rb")
+ }
+ })
+ }
+
+ pub fn lift_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ Type::Int8
+ | Type::UInt8
+ | Type::Int16
+ | Type::UInt16
+ | Type::Int32
+ | Type::UInt32
+ | Type::Int64
+ | Type::UInt64 => format!("{nm}.to_i"),
+ Type::Float32 | Type::Float64 => format!("{nm}.to_f"),
+ Type::Boolean => format!("1 == {nm}"),
+ Type::String => format!("{nm}.consumeIntoString"),
+ Type::Object(name) => format!("{}._uniffi_allocate({nm})", class_name_rb(name)?),
+ Type::CallbackInterface(_) => panic!("No support for lifting callback interfaces, yet"),
+ Type::Error(_) => panic!("No support for lowering errors, yet"),
+ Type::Enum(_)
+ | Type::Record(_)
+ | Type::Optional(_)
+ | Type::Sequence(_)
+ | Type::Timestamp
+ | Type::Duration
+ | Type::Map(_, _) => format!(
+ "{}.consumeInto{}",
+ nm,
+ class_name_rb(&type_.canonical_name())?
+ ),
+ Type::External { .. } => panic!("No support for lifting external types, yet"),
+ Type::Custom { .. } => panic!("No support for lifting custom types, yet"),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling lift_rb")
+ }
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs
new file mode 100644
index 0000000000..9ae5d1816f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs
@@ -0,0 +1,47 @@
+use super::{is_reserved_word, Config};
+
+#[test]
+fn when_reserved_word() {
+ assert!(is_reserved_word("end"));
+}
+
+#[test]
+fn when_not_reserved_word() {
+ assert!(!is_reserved_word("ruby"));
+}
+
+#[test]
+fn cdylib_name() {
+ let config = Config {
+ cdylib_name: None,
+ cdylib_path: None,
+ };
+
+ assert_eq!("uniffi", config.cdylib_name());
+
+ let config = Config {
+ cdylib_name: Some("todolist".to_string()),
+ cdylib_path: None,
+ };
+
+ assert_eq!("todolist", config.cdylib_name());
+}
+
+#[test]
+fn cdylib_path() {
+ let config = Config {
+ cdylib_name: None,
+ cdylib_path: None,
+ };
+
+ assert_eq!("", config.cdylib_path());
+ assert!(!config.custom_cdylib_path());
+
+ let config = Config {
+ cdylib_name: None,
+ cdylib_path: Some("/foo/bar".to_string()),
+ };
+
+ assert_eq!("/foo/bar", config.cdylib_path());
+ assert!(config.custom_cdylib_path());
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs
new file mode 100644
index 0000000000..e0d789f42f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs
@@ -0,0 +1,50 @@
+/* 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 std::{io::Write, process::Command};
+
+use anyhow::{Context, Result};
+use camino::Utf8Path;
+use fs_err::File;
+
+pub mod gen_ruby;
+mod test;
+pub use gen_ruby::{Config, RubyWrapper};
+pub use test::run_test;
+
+use super::super::interface::ComponentInterface;
+
+// Generate ruby bindings for the given ComponentInterface, in the given output directory.
+
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+) -> Result<()> {
+ let rb_file = out_dir.join(format!("{}.rb", ci.namespace()));
+ let mut f = File::create(&rb_file)?;
+ write!(f, "{}", generate_ruby_bindings(config, ci)?)?;
+
+ if try_format_code {
+ if let Err(e) = Command::new("rubocop").arg("-A").arg(&rb_file).output() {
+ println!(
+ "Warning: Unable to auto-format {} using rubocop: {:?}",
+ rb_file.file_name().unwrap(),
+ e
+ )
+ }
+ }
+
+ Ok(())
+}
+
+// Generate ruby bindings for the given ComponentInterface, as a string.
+
+pub fn generate_ruby_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
+ use askama::Template;
+ RubyWrapper::new(config.clone(), ci)
+ .render()
+ .context("failed to render ruby bindings")
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb
new file mode 100644
index 0000000000..23b701f6a7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb
@@ -0,0 +1,59 @@
+{% if e.is_flat() %}
+
+class {{ e.name()|class_name_rb }}
+ {% for variant in e.variants() -%}
+ {{ variant.name()|enum_name_rb }} = {{ loop.index }}
+ {% endfor %}
+end
+
+{% else %}
+
+class {{ e.name()|class_name_rb }}
+ def initialize
+ raise RuntimeError, '{{ e.name()|class_name_rb }} cannot be instantiated directly'
+ end
+
+ # Each enum variant is a nested class of the enum itself.
+ {% for variant in e.variants() -%}
+ class {{ variant.name()|enum_name_rb }}
+ {% if variant.has_fields() %}
+ attr_reader {% for field in variant.fields() %}:{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{%- endfor %}
+ {% endif %}
+ def initialize({% for field in variant.fields() %}{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
+ {% if variant.has_fields() %}
+ {%- for field in variant.fields() %}
+ @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }}
+ {%- endfor %}
+ {% else %}
+ {% endif %}
+ end
+
+ def to_s
+ "{{ e.name()|class_name_rb }}::{{ variant.name()|enum_name_rb }}({% for field in variant.fields() %}{{ field.name() }}=#{@{{ field.name() }}}{% if loop.last %}{% else %}, {% endif %}{% endfor %})"
+ end
+
+ def ==(other)
+ if !other.{{ variant.name()|var_name_rb }}?
+ return false
+ end
+ {%- for field in variant.fields() %}
+ if @{{ field.name()|var_name_rb }} != other.{{ field.name()|var_name_rb }}
+ return false
+ end
+ {%- endfor %}
+
+ true
+ end
+
+ # For each variant, we have an `NAME?` method for easily checking
+ # whether an instance is that variant.
+ {% for variant in e.variants() %}
+ def {{ variant.name()|var_name_rb }}?
+ instance_of? {{ e.name()|class_name_rb }}::{{ variant.name()|enum_name_rb }}
+ end
+ {% endfor %}
+ end
+ {% endfor %}
+end
+
+{% endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb
new file mode 100644
index 0000000000..3a64e9ffeb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb
@@ -0,0 +1,117 @@
+class RustCallStatus < FFI::Struct
+ layout :code, :int8,
+ :error_buf, RustBuffer
+
+ def code
+ self[:code]
+ end
+
+ def error_buf
+ self[:error_buf]
+ end
+
+ def to_s
+ "RustCallStatus(code=#{self[:code]})"
+ end
+end
+
+# These match the values from the uniffi::rustcalls module
+CALL_SUCCESS = 0
+CALL_ERROR = 1
+CALL_PANIC = 2
+{%- for e in ci.error_definitions() %}
+{% if e.is_flat() %}
+class {{ e.name()|class_name_rb }}
+ {%- for variant in e.variants() %}
+ {{ variant.name()|class_name_rb }} = Class.new StandardError
+ {%- endfor %}
+{% else %}
+module {{ e.name()|class_name_rb }}
+ {%- for variant in e.variants() %}
+ class {{ variant.name()|class_name_rb }} < StandardError
+ def initialize({% for field in variant.fields() %}{{ field.name()|var_name_rb }}{% if !loop.last %}, {% endif %}{% endfor %})
+ {%- for field in variant.fields() %}
+ @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }}
+ {%- endfor %}
+ super()
+ end
+ {%- if variant.has_fields() %}
+
+ attr_reader {% for field in variant.fields() %}:{{ field.name()|var_name_rb }}{% if !loop.last %}, {% endif %}{% endfor %}
+ {% endif %}
+
+ def to_s
+ "#{self.class.name}({% for field in variant.fields() %}{{ field.name()|var_name_rb }}=#{@{{ field.name()|var_name_rb }}.inspect}{% if !loop.last %}, {% endif %}{% endfor %})"
+ end
+ end
+ {%- endfor %}
+{% endif %}
+end
+{%- endfor %}
+
+# Map error modules to the RustBuffer method name that reads them
+ERROR_MODULE_TO_READER_METHOD = {
+{%- for e in ci.error_definitions() %}
+{%- let typ=ci.get_type(e.name()).unwrap() %}
+{%- let canonical_type_name = typ.canonical_name().borrow()|class_name_rb %}
+ {{ e.name()|class_name_rb }} => :read{{ canonical_type_name }},
+{%- endfor %}
+}
+
+private_constant :ERROR_MODULE_TO_READER_METHOD, :CALL_SUCCESS, :CALL_ERROR, :CALL_PANIC,
+ :RustCallStatus
+
+def self.consume_buffer_into_error(error_module, rust_buffer)
+ rust_buffer.consumeWithStream do |stream|
+ reader_method = ERROR_MODULE_TO_READER_METHOD[error_module]
+ return stream.send(reader_method)
+ end
+end
+
+class InternalError < StandardError
+end
+
+def self.rust_call(fn_name, *args)
+ # Call a rust function
+ rust_call_with_error(nil, fn_name, *args)
+end
+
+def self.rust_call_with_error(error_module, fn_name, *args)
+ # Call a rust function and handle errors
+ #
+ # Use this when the rust function returns a Result<>. error_module must be the error_module that corresponds to that Result.
+
+
+ # Note: RustCallStatus.new zeroes out the struct, which is exactly what we
+ # want to pass to Rust (code=0, error_buf=RustBuffer(len=0, capacity=0,
+ # data=NULL))
+ status = RustCallStatus.new
+ args << status
+
+ result = UniFFILib.public_send(fn_name, *args)
+
+ case status.code
+ when CALL_SUCCESS
+ result
+ when CALL_ERROR
+ if error_module.nil?
+ status.error_buf.free
+ raise InternalError, "CALL_ERROR with no error_module set"
+ else
+ raise consume_buffer_into_error(error_module, status.error_buf)
+ end
+ when CALL_PANIC
+ # When the rust code sees a panic, it tries to construct a RustBuffer
+ # with the message. But if that code panics, then it just sends back
+ # an empty buffer.
+ if status.error_buf.len > 0
+ raise InternalError, status.error_buf.consumeIntoString()
+ else
+ raise InternalError, "Rust panic"
+ end
+ else
+ raise InternalError, "Unknown call status: #{status.code}"
+ end
+end
+
+private_class_method :consume_buffer_into_error
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb
new file mode 100644
index 0000000000..858b42bf91
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb
@@ -0,0 +1,17 @@
+# This is how we find and load the dynamic library provided by the component.
+# For now we just look it up by name.
+module UniFFILib
+ extend FFI::Library
+
+ {% if config.custom_cdylib_path() %}
+ ffi_lib {{ config.cdylib_path() }}
+ {% else %}
+ ffi_lib '{{ config.cdylib_name() }}'
+ {% endif %}
+
+ {% for func in ci.iter_ffi_function_definitions() -%}
+ attach_function :{{ func.name() }},
+ {%- call rb::arg_list_ffi_decl(func) %},
+ {% match func.return_type() %}{% when Some with (type_) %}{{ type_|type_ffi }}{% when None %}:void{% endmatch %}
+ {% endfor %}
+end
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb
new file mode 100644
index 0000000000..677c5c729b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb
@@ -0,0 +1,73 @@
+class {{ obj.name()|class_name_rb }}
+
+ # A private helper for initializing instances of the class from a raw pointer,
+ # bypassing any initialization logic and ensuring they are GC'd properly.
+ def self._uniffi_allocate(pointer)
+ pointer.autorelease = false
+ inst = allocate
+ inst.instance_variable_set :@pointer, pointer
+ ObjectSpace.define_finalizer(inst, _uniffi_define_finalizer_by_pointer(pointer, inst.object_id))
+ return inst
+ end
+
+ # A private helper for registering an object finalizer.
+ # N.B. it's important that this does not capture a reference
+ # to the actual instance, only its underlying pointer.
+ def self._uniffi_define_finalizer_by_pointer(pointer, object_id)
+ Proc.new do |_id|
+ {{ ci.namespace()|class_name_rb }}.rust_call(
+ :{{ obj.ffi_object_free().name() }},
+ pointer
+ )
+ end
+ end
+
+ # A private helper for lowering instances into a raw pointer.
+ # This does an explicit typecheck, because accidentally lowering a different type of
+ # object in a place where this type is expected, could lead to memory unsafety.
+ def self._uniffi_lower(inst)
+ if not inst.is_a? self
+ raise TypeError.new "Expected a {{ obj.name()|class_name_rb }} instance, got #{inst}"
+ end
+ return inst.instance_variable_get :@pointer
+ end
+
+ {%- match obj.primary_constructor() %}
+ {%- when Some with (cons) %}
+ def initialize({% call rb::arg_list_decl(cons) -%})
+ {%- call rb::coerce_args_extra_indent(cons) %}
+ pointer = {% call rb::to_ffi_call(cons) %}
+ @pointer = pointer
+ ObjectSpace.define_finalizer(self, self.class._uniffi_define_finalizer_by_pointer(pointer, self.object_id))
+ end
+ {%- when None %}
+ {%- endmatch %}
+
+ {% for cons in obj.alternate_constructors() -%}
+ def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %})
+ {%- call rb::coerce_args_extra_indent(cons) %}
+ # Call the (fallible) function before creating any half-baked object instances.
+ # Lightly yucky way to bypass the usual "initialize" logic
+ # and just create a new instance with the required pointer.
+ return _uniffi_allocate({% call rb::to_ffi_call(cons) %})
+ end
+ {% endfor %}
+
+ {% for meth in obj.methods() -%}
+ {%- match meth.return_type() -%}
+
+ {%- when Some with (return_type) -%}
+ def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %})
+ {%- call rb::coerce_args_extra_indent(meth) %}
+ result = {% call rb::to_ffi_call_with_prefix("@pointer", meth) %}
+ return {{ "result"|lift_rb(return_type) }}
+ end
+
+ {%- when None -%}
+ def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %})
+ {%- call rb::coerce_args_extra_indent(meth) %}
+ {% call rb::to_ffi_call_with_prefix("@pointer", meth) %}
+ end
+ {% endmatch %}
+ {% endfor %}
+end
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb
new file mode 100644
index 0000000000..c940b31060
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb
@@ -0,0 +1,20 @@
+# Record type {{ rec.name() }}
+class {{ rec.name()|class_name_rb }}
+ attr_reader {% for field in rec.fields() %}:{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{%- endfor %}
+
+ def initialize({% for field in rec.fields() %}{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
+ {%- for field in rec.fields() %}
+ @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }}
+ {%- endfor %}
+ end
+
+ def ==(other)
+ {%- for field in rec.fields() %}
+ if @{{ field.name()|var_name_rb }} != other.{{ field.name()|var_name_rb }}
+ return false
+ end
+ {%- endfor %}
+
+ true
+ end
+end
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb
new file mode 100644
index 0000000000..e4b3910b6c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb
@@ -0,0 +1,246 @@
+
+# Helper for structured writing of values into a RustBuffer.
+class RustBufferBuilder
+ def initialize
+ @rust_buf = RustBuffer.alloc 16
+ @rust_buf.len = 0
+ end
+
+ def finalize
+ rbuf = @rust_buf
+
+ @rust_buf = nil
+
+ rbuf
+ end
+
+ def discard
+ return if @rust_buf.nil?
+
+ rbuf = finalize
+ rbuf.free
+ end
+
+ def write(value)
+ reserve(value.bytes.size) do
+ @rust_buf.data.put_array_of_char @rust_buf.len, value.bytes
+ end
+ end
+
+ {% for typ in ci.iter_types() -%}
+ {%- let canonical_type_name = typ.canonical_name().borrow()|class_name_rb -%}
+ {%- match typ -%}
+
+ {% when Type::Int8 -%}
+
+ def write_I8(v)
+ pack_into(1, 'c', v)
+ end
+
+ {% when Type::UInt8 -%}
+
+ def write_U8(v)
+ pack_into(1, 'c', v)
+ end
+
+ {% when Type::Int16 -%}
+
+ def write_I16(v)
+ pack_into(2, 's>', v)
+ end
+
+ {% when Type::UInt16 -%}
+
+ def write_U16(v)
+ pack_into(2, 'S>', v)
+ end
+
+ {% when Type::Int32 -%}
+
+ def write_I32(v)
+ pack_into(4, 'l>', v)
+ end
+
+ {% when Type::UInt32 -%}
+
+ def write_U32(v)
+ pack_into(4, 'L>', v)
+ end
+
+ {% when Type::Int64 -%}
+
+ def write_I64(v)
+ pack_into(8, 'q>', v)
+ end
+
+ {% when Type::UInt64 -%}
+
+ def write_U64(v)
+ pack_into(8, 'Q>', v)
+ end
+
+ {% when Type::Float32 -%}
+
+ def write_F32(v)
+ pack_into(4, 'g', v)
+ end
+
+ {% when Type::Float64 -%}
+
+ def write_F64(v)
+ pack_into(8, 'G', v)
+ end
+
+ {% when Type::Boolean -%}
+
+ def write_Bool(v)
+ pack_into(1, 'c', v ? 1 : 0)
+ end
+
+ {% when Type::String -%}
+
+ def write_String(v)
+ v = v.to_s
+ pack_into 4, 'l>', v.bytes.size
+ write v
+ end
+
+ {% when Type::Timestamp -%}
+ # The Timestamp type.
+ ONE_SECOND_IN_NANOSECONDS = 10**9
+
+ def write_{{ canonical_type_name }}(v)
+ seconds = v.tv_sec
+ nanoseconds = v.tv_nsec
+
+ # UniFFi conventions assume that nanoseconds part has to represent nanoseconds portion of
+ # duration between epoch and the timestamp moment. Ruby `Time#tv_nsec` returns the number of
+ # nanoseconds for the subsecond part, which is sort of opposite to "duration" meaning.
+ # Hence we need to convert value returned by `Time#tv_nsec` back and forth with the following
+ # logic:
+ if seconds < 0 && nanoseconds != 0
+ # In order to get duration nsec we shift by 1 second:
+ nanoseconds = ONE_SECOND_IN_NANOSECONDS - nanoseconds
+
+ # Then we compensate 1 second shift:
+ seconds += 1
+ end
+
+ pack_into 8, 'q>', seconds
+ pack_into 4, 'L>', nanoseconds
+ end
+
+ {% when Type::Duration -%}
+ # The Duration type.
+
+ def write_{{ canonical_type_name }}(v)
+ seconds = v.tv_sec
+ nanoseconds = v.tv_nsec
+
+ raise ArgumentError, 'Invalid duration, must be non-negative' if seconds < 0
+
+ pack_into 8, 'Q>', seconds
+ pack_into 4, 'L>', nanoseconds
+ end
+
+ {% when Type::Object with (object_name) -%}
+ # The Object type {{ object_name }}.
+
+ def write_{{ canonical_type_name }}(obj)
+ pointer = {{ object_name|class_name_rb}}._uniffi_lower obj
+ pack_into(8, 'Q>', pointer.address)
+ end
+
+ {% when Type::Enum with (enum_name) -%}
+ {%- let e = ci.get_enum_definition(enum_name).unwrap() -%}
+ # The Enum type {{ enum_name }}.
+
+ def write_{{ canonical_type_name }}(v)
+ {%- if e.is_flat() %}
+ pack_into(4, 'l>', v)
+ {%- else -%}
+ {%- for variant in e.variants() %}
+ if v.{{ variant.name()|var_name_rb }}?
+ pack_into(4, 'l>', {{ loop.index }})
+ {%- for field in variant.fields() %}
+ self.write_{{ field.type_().canonical_name().borrow()|class_name_rb }}(v.{{ field.name() }})
+ {%- endfor %}
+ end
+ {%- endfor %}
+ {%- endif %}
+ end
+
+ {% when Type::Record with (record_name) -%}
+ {%- let rec = ci.get_record_definition(record_name).unwrap() -%}
+ # The Record type {{ record_name }}.
+
+ def write_{{ canonical_type_name }}(v)
+ {%- for field in rec.fields() %}
+ self.write_{{ field.type_().canonical_name().borrow()|class_name_rb }}(v.{{ field.name()|var_name_rb }})
+ {%- endfor %}
+ end
+
+ {% when Type::Optional with (inner_type) -%}
+ # The Optional<T> type for {{ inner_type.canonical_name() }}.
+
+ def write_{{ canonical_type_name }}(v)
+ if v.nil?
+ pack_into(1, 'c', 0)
+ else
+ pack_into(1, 'c', 1)
+ self.write_{{ inner_type.canonical_name().borrow()|class_name_rb }}(v)
+ end
+ end
+
+ {% when Type::Sequence with (inner_type) -%}
+ # The Sequence<T> type for {{ inner_type.canonical_name() }}.
+
+ def write_{{ canonical_type_name }}(items)
+ pack_into(4, 'l>', items.size)
+
+ items.each do |item|
+ self.write_{{ inner_type.canonical_name().borrow()|class_name_rb }}(item)
+ end
+ end
+
+ {% when Type::Map with (k, inner_type) -%}
+ # The Map<T> type for {{ inner_type.canonical_name() }}.
+
+ def write_{{ canonical_type_name }}(items)
+ pack_into(4, 'l>', items.size)
+
+ items.each do |k, v|
+ write_String(k)
+ self.write_{{ inner_type.canonical_name().borrow()|class_name_rb }}(v)
+ end
+ end
+
+ {%- else -%}
+ # This type is not yet supported in the Ruby backend.
+ def write_{{ canonical_type_name }}(v)
+ raise InternalError('RustBufferStream.write() not implemented yet for {{ canonical_type_name }}')
+ end
+
+ {%- endmatch -%}
+ {%- endfor %}
+
+ private
+
+ def reserve(num_bytes)
+ if @rust_buf.len + num_bytes > @rust_buf.capacity
+ @rust_buf = RustBuffer.reserve(@rust_buf, num_bytes)
+ end
+
+ yield
+
+ @rust_buf.len += num_bytes
+ end
+
+ def pack_into(size, format, value)
+ reserve(size) do
+ @rust_buf.data.put_array_of_char @rust_buf.len, [value].pack(format).bytes
+ end
+ end
+end
+
+private_constant :RustBufferBuilder
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb
new file mode 100644
index 0000000000..f48fbffad4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb
@@ -0,0 +1,301 @@
+
+# Helper for structured reading of values from a RustBuffer.
+class RustBufferStream
+
+ def initialize(rbuf)
+ @rbuf = rbuf
+ @offset = 0
+ end
+
+ def remaining
+ @rbuf.len - @offset
+ end
+
+ def read(size)
+ raise InternalError, 'read past end of rust buffer' if @offset + size > @rbuf.len
+
+ data = @rbuf.data.get_bytes @offset, size
+
+ @offset += size
+
+ data
+ end
+
+ {% for typ in ci.iter_types() -%}
+ {%- let canonical_type_name = typ.canonical_name().borrow()|class_name_rb -%}
+ {%- match typ -%}
+
+ {% when Type::Int8 -%}
+
+ def readI8
+ unpack_from 1, 'c'
+ end
+
+ {% when Type::UInt8 -%}
+
+ def readU8
+ unpack_from 1, 'c'
+ end
+
+ {% when Type::Int16 -%}
+
+ def readI16
+ unpack_from 2, 's>'
+ end
+
+ {% when Type::UInt16 -%}
+
+ def readU16
+ unpack_from 2, 'S>'
+ end
+
+ {% when Type::Int32 -%}
+
+ def readI32
+ unpack_from 4, 'l>'
+ end
+
+ {% when Type::UInt32 -%}
+
+ def readU32
+ unpack_from 4, 'L>'
+ end
+
+ {% when Type::Int64 -%}
+
+ def readI64
+ unpack_from 8, 'q>'
+ end
+
+ {% when Type::UInt64 -%}
+
+ def readU64
+ unpack_from 8, 'Q>'
+ end
+
+ {% when Type::Float32 -%}
+
+ def readF32
+ unpack_from 4, 'g'
+ end
+
+ {% when Type::Float64 -%}
+
+ def readF64
+ unpack_from 8, 'G'
+ end
+
+ {% when Type::Boolean -%}
+
+ def readBool
+ v = unpack_from 1, 'c'
+
+ return false if v == 0
+ return true if v == 1
+
+ raise InternalError, 'Unexpected byte for Boolean type'
+ end
+
+ {% when Type::String -%}
+
+ def readString
+ size = unpack_from 4, 'l>'
+
+ raise InternalError, 'Unexpected negative string length' if size.negative?
+
+ read(size).force_encoding(Encoding::UTF_8)
+ end
+
+ {% when Type::Timestamp -%}
+ # The Timestamp type.
+ ONE_SECOND_IN_NANOSECONDS = 10**9
+
+ def read{{ canonical_type_name }}
+ seconds = unpack_from 8, 'q>'
+ nanoseconds = unpack_from 4, 'L>'
+
+ # UniFFi conventions assume that nanoseconds part has to represent nanoseconds portion of
+ # duration between epoch and the timestamp moment. Ruby `Time#tv_nsec` returns the number of
+ # nanoseconds for the subsecond part, which is sort of opposite to "duration" meaning.
+ # Hence we need to convert value returned by `Time#tv_nsec` back and forth with the following
+ # logic:
+ if seconds < 0 && nanoseconds != 0
+ # In order to get duration nsec we shift by 1 second:
+ nanoseconds = ONE_SECOND_IN_NANOSECONDS - nanoseconds
+
+ # Then we compensate 1 second shift:
+ seconds -= 1
+ end
+
+ Time.at(seconds, nanoseconds, :nanosecond, in: '+00:00').utc
+ end
+
+ {% when Type::Duration -%}
+ # The Duration type.
+
+ def read{{ canonical_type_name }}
+ seconds = unpack_from 8, 'q>'
+ nanoseconds = unpack_from 4, 'L>'
+
+ Time.at(seconds, nanoseconds, :nanosecond, in: '+00:00').utc
+ end
+
+ {% when Type::Object with (object_name) -%}
+ # The Object type {{ object_name }}.
+
+ def read{{ canonical_type_name }}
+ pointer = FFI::Pointer.new unpack_from 8, 'Q>'
+ return {{ object_name|class_name_rb }}._uniffi_allocate(pointer)
+ end
+
+ {% when Type::Enum with (enum_name) -%}
+ {%- let e = ci.get_enum_definition(enum_name).unwrap() -%}
+ # The Enum type {{ enum_name }}.
+
+ def read{{ canonical_type_name }}
+ variant = unpack_from 4, 'l>'
+ {% if e.is_flat() -%}
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}
+ return {{ enum_name|class_name_rb }}::{{ variant.name()|enum_name_rb }}
+ end
+ {%- endfor %}
+
+ raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}'
+ {%- else -%}
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}
+ {%- if variant.has_fields() %}
+ return {{ enum_name|class_name_rb }}::{{ variant.name()|enum_name_rb }}.new(
+ {%- for field in variant.fields() %}
+ self.read{{ field.type_().canonical_name().borrow()|class_name_rb }}(){% if loop.last %}{% else %},{% endif %}
+ {%- endfor %}
+ )
+ {%- else %}
+ return {{ enum_name|class_name_rb }}::{{ variant.name()|enum_name_rb }}.new
+ {% endif %}
+ end
+ {%- endfor %}
+ raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}'
+ {%- endif %}
+ end
+
+ {% when Type::Error with (error_name) -%}
+ {%- let e = ci.get_error_definition(error_name).unwrap().wrapped_enum() %}
+
+ # The Error type {{ error_name }}
+
+ def read{{ canonical_type_name }}
+ variant = unpack_from 4, 'l>'
+ {% if e.is_flat() -%}
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}
+ return {{ error_name|class_name_rb }}::{{ variant.name()|class_name_rb }}.new(
+ readString()
+ )
+ end
+ {%- endfor %}
+
+ raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}'
+ {%- else -%}
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}
+ {%- if variant.has_fields() %}
+ return {{ error_name|class_name_rb }}::{{ variant.name()|class_name_rb }}.new(
+ {%- for field in variant.fields() %}
+ read{{ field.type_().canonical_name().borrow()|class_name_rb }}(){% if loop.last %}{% else %},{% endif %}
+ {%- endfor %}
+ )
+ {%- else %}
+ return {{ error_name|class_name_rb }}::{{ variant.name()|class_name_rb }}.new
+ {%- endif %}
+ end
+ {%- endfor %}
+
+ raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}'
+ {%- endif %}
+ end
+
+ {% when Type::Record with (record_name) -%}
+ {%- let rec = ci.get_record_definition(record_name).unwrap() -%}
+ # The Record type {{ record_name }}.
+
+ def read{{ canonical_type_name }}
+ {{ rec.name()|class_name_rb }}.new(
+ {%- for field in rec.fields() %}
+ read{{ field.type_().canonical_name().borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %}
+ {%- endfor %}
+ )
+ end
+
+ {% when Type::Optional with (inner_type) -%}
+ # The Optional<T> type for {{ inner_type.canonical_name() }}.
+
+ def read{{ canonical_type_name }}
+ flag = unpack_from 1, 'c'
+
+ if flag == 0
+ return nil
+ elsif flag == 1
+ return read{{ inner_type.canonical_name().borrow()|class_name_rb }}
+ else
+ raise InternalError, 'Unexpected flag byte for {{ canonical_type_name }}'
+ end
+ end
+
+ {% when Type::Sequence with (inner_type) -%}
+ # The Sequence<T> type for {{ inner_type.canonical_name() }}.
+
+ def read{{ canonical_type_name }}
+ count = unpack_from 4, 'l>'
+
+ raise InternalError, 'Unexpected negative sequence length' if count.negative?
+
+ items = []
+
+ count.times do
+ items.append read{{ inner_type.canonical_name().borrow()|class_name_rb }}
+ end
+
+ items
+ end
+
+ {% when Type::Map with (k, inner_type) -%}
+ # The Map<T> type for {{ inner_type.canonical_name() }}.
+
+ def read{{ canonical_type_name }}
+ count = unpack_from 4, 'l>'
+ raise InternalError, 'Unexpected negative map size' if count.negative?
+
+ items = {}
+ count.times do
+ key = readString
+ items[key] = read{{ inner_type.canonical_name().borrow()|class_name_rb }}
+ end
+
+ items
+ end
+ {%- else -%}
+ # This type is not yet supported in the Ruby backend.
+ def read{{ canonical_type_name }}
+ raise InternalError, 'RustBufferStream.read not implemented yet for {{ canonical_type_name }}'
+ end
+
+ {%- endmatch -%}
+ {%- endfor %}
+
+ def unpack_from(size, format)
+ raise InternalError, 'read past end of rust buffer' if @offset + size > @rbuf.len
+
+ value = @rbuf.data.get_bytes(@offset, size).unpack format
+
+ @offset += size
+
+ # TODO: verify this
+ raise 'more than one element!!!' if value.size > 1
+
+ value[0]
+ end
+end
+
+private_constant :RustBufferStream
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb
new file mode 100644
index 0000000000..deed864572
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb
@@ -0,0 +1,218 @@
+class RustBuffer < FFI::Struct
+ layout :capacity, :int32,
+ :len, :int32,
+ :data, :pointer
+
+ def self.alloc(size)
+ return {{ ci.namespace()|class_name_rb }}.rust_call(:{{ ci.ffi_rustbuffer_alloc().name() }}, size)
+ end
+
+ def self.reserve(rbuf, additional)
+ return {{ ci.namespace()|class_name_rb }}.rust_call(:{{ ci.ffi_rustbuffer_reserve().name() }}, rbuf, additional)
+ end
+
+ def free
+ {{ ci.namespace()|class_name_rb }}.rust_call(:{{ ci.ffi_rustbuffer_free().name() }}, self)
+ end
+
+ def capacity
+ self[:capacity]
+ end
+
+ def len
+ self[:len]
+ end
+
+ def len=(value)
+ self[:len] = value
+ end
+
+ def data
+ self[:data]
+ end
+
+ def to_s
+ "RustBuffer(capacity=#{capacity}, len=#{len}, data=#{data.read_bytes len})"
+ end
+
+ # The allocated buffer will be automatically freed if an error occurs, ensuring that
+ # we don't accidentally leak it.
+ def self.allocWithBuilder
+ builder = RustBufferBuilder.new
+
+ begin
+ yield builder
+ rescue => e
+ builder.discard
+ raise e
+ end
+ end
+
+ # The RustBuffer will be freed once the context-manager exits, ensuring that we don't
+ # leak it even if an error occurs.
+ def consumeWithStream
+ stream = RustBufferStream.new self
+
+ yield stream
+
+ raise RuntimeError, 'junk data left in buffer after consuming' if stream.remaining != 0
+ ensure
+ free
+ end
+
+ {%- for typ in ci.iter_types() -%}
+ {%- let canonical_type_name = typ.canonical_name() -%}
+ {%- match typ -%}
+
+ {% when Type::String -%}
+ # The primitive String type.
+
+ def self.allocFromString(value)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write value.encode('utf-8')
+ return builder.finalize
+ end
+ end
+
+ def consumeIntoString
+ consumeWithStream do |stream|
+ return stream.read(stream.remaining).force_encoding(Encoding::UTF_8)
+ end
+ end
+
+ {% when Type::Timestamp -%}
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Duration -%}
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Record with (record_name) -%}
+ {%- let rec = ci.get_record_definition(record_name).unwrap() -%}
+ # The Record type {{ record_name }}.
+
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Enum with (enum_name) -%}
+ {%- let e = ci.get_enum_definition(enum_name).unwrap() -%}
+ # The Enum type {{ enum_name }}.
+
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Optional with (inner_type) -%}
+ # The Optional<T> type for {{ inner_type.canonical_name() }}.
+
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize()
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Sequence with (inner_type) -%}
+ # The Sequence<T> type for {{ inner_type.canonical_name() }}.
+
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize()
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Map with (k, inner_type) -%}
+ # The Map<T> type for {{ inner_type.canonical_name() }}.
+
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {%- else -%}
+ {#- No code emitted for types that don't lower into a RustBuffer -#}
+ {%- endmatch -%}
+ {%- endfor %}
+end
+
+module UniFFILib
+ class ForeignBytes < FFI::Struct
+ layout :len, :int32,
+ :data, :pointer
+
+ def len
+ self[:len]
+ end
+
+ def data
+ self[:data]
+ end
+
+ def to_s
+ "ForeignBytes(len=#{len}, data=#{data.read_bytes(len)})"
+ end
+ end
+end
+
+private_constant :UniFFILib
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb
new file mode 100644
index 0000000000..13214cf31b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb
@@ -0,0 +1,16 @@
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+
+def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%})
+ {%- call rb::coerce_args(func) %}
+ result = {% call rb::to_ffi_call(func) %}
+ return {{ "result"|lift_rb(return_type) }}
+end
+
+{% when None %}
+
+def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%})
+ {%- call rb::coerce_args(func) %}
+ {% call rb::to_ffi_call(func) %}
+end
+{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb
new file mode 100644
index 0000000000..c2faf63104
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb
@@ -0,0 +1,73 @@
+{#
+// Template to call into rust. Used in several places.
+// Variable names in `arg_list_decl` should match up with arg lists
+// passed to rust via `_arg_list_ffi_call` (we use `var_name_rb` in `lower_rb`)
+#}
+
+{%- macro to_ffi_call(func) -%}
+ {%- match func.throws_name() -%}
+ {%- when Some with (e) -%}
+ {{ ci.namespace()|class_name_rb }}.rust_call_with_error({{ e|class_name_rb }},
+ {%- else -%}
+ {{ ci.namespace()|class_name_rb }}.rust_call(
+ {%- endmatch -%}
+ :{{ func.ffi_func().name() }},
+ {%- call _arg_list_ffi_call(func) -%}
+)
+{%- endmacro -%}
+
+{%- macro to_ffi_call_with_prefix(prefix, func) -%}
+ {%- match func.throws_name() -%}
+ {%- when Some with (e) -%}
+ {{ ci.namespace()|class_name_rb }}.rust_call_with_error({{ e|class_name_rb }},
+ {%- else -%}
+ {{ ci.namespace()|class_name_rb }}.rust_call(
+ {%- endmatch -%}
+ :{{ func.ffi_func().name() }},
+ {{- prefix }},
+ {%- call _arg_list_ffi_call(func) -%}
+)
+{%- endmacro -%}
+
+{%- macro _arg_list_ffi_call(func) %}
+ {%- for arg in func.arguments() %}
+ {{- arg.name()|lower_rb(arg.type_().borrow()) }}
+ {%- if !loop.last %},{% endif %}
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in Ruby declarations of methods, functions and constructors.
+// Note the var_name_rb and type_rb filters.
+-#}
+
+{% macro arg_list_decl(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name_rb }}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|literal_rb }}
+ {%- else %}
+ {%- endmatch %}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+{#-
+// Arglist as used in the UniFFILib function declarations.
+// Note unfiltered name but type_ffi filters.
+-#}
+{%- macro arg_list_ffi_decl(func) %}
+ [{%- for arg in func.arguments() -%}{{ arg.type_().borrow()|type_ffi }}, {% endfor -%} RustCallStatus.by_ref]
+{%- endmacro -%}
+
+{%- macro coerce_args(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg.name() }} = {{ arg.name()|coerce_rb(arg.type_().borrow()) -}}
+ {% endfor -%}
+{%- endmacro -%}
+
+{%- macro coerce_args_extra_indent(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg.name() }} = {{ arg.name()|coerce_rb(arg.type_().borrow()) }}
+ {%- endfor %}
+{%- endmacro -%}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb
new file mode 100644
index 0000000000..72cb60f73f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb
@@ -0,0 +1,47 @@
+# This file was autogenerated by some hot garbage in the `uniffi` crate.
+# Trust me, you don't want to mess with it!
+
+# Common helper code.
+#
+# Ideally this would live in a separate .rb file where it can be unittested etc
+# in isolation, and perhaps even published as a re-useable package.
+#
+# However, it's important that the detils of how this helper code works (e.g. the
+# way that different builtin types are passed across the FFI) exactly match what's
+# expected by the rust code on the other side of the interface. In practice right
+# now that means coming from the exact some version of `uniffi` that was used to
+# compile the rust component. The easiest way to ensure this is to bundle the Ruby
+# helpers directly inline like we're doing here.
+
+require 'ffi'
+
+module {{ ci.namespace()|class_name_rb }}
+ {% include "RustBufferTemplate.rb" %}
+ {% include "RustBufferStream.rb" %}
+ {% include "RustBufferBuilder.rb" %}
+
+ # Error definitions
+ {% include "ErrorTemplate.rb" %}
+
+ {% include "NamespaceLibraryTemplate.rb" %}
+
+ # Public interface members begin here.
+
+ {% for e in ci.enum_definitions() %}
+ {% include "EnumTemplate.rb" %}
+ {%- endfor -%}
+
+ {%- for rec in ci.record_definitions() %}
+ {% include "RecordTemplate.rb" %}
+ {% endfor %}
+
+ {% for func in ci.function_definitions() %}
+ {% include "TopLevelFunctionTemplate.rb" %}
+ {% endfor %}
+
+ {% for obj in ci.object_definitions() %}
+ {% include "ObjectTemplate.rb" %}
+ {% endfor %}
+end
+
+{% import "macros.rb" as rb %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs
new file mode 100644
index 0000000000..5c445bfae5
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs
@@ -0,0 +1,62 @@
+/* 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 anyhow::{bail, Context, Result};
+use camino::Utf8Path;
+use std::env;
+use std::ffi::OsString;
+use std::process::{Command, Stdio};
+use uniffi_testing::UniFFITestHelper;
+
+/// Run Ruby tests for a UniFFI test fixture
+pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
+ let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let test_helper = UniFFITestHelper::new(fixture_name).context("UniFFITestHelper::new")?;
+ let out_dir = test_helper
+ .create_out_dir(tmp_dir, &script_path)
+ .context("create_out_dir")?;
+ test_helper
+ .copy_cdylibs_to_out_dir(&out_dir)
+ .context("copy_cdylibs_to_out_dir")?;
+ generate_sources(&test_helper.cdylib_path()?, &out_dir, &test_helper)
+ .context("generate_sources")?;
+
+ let rubypath = env::var_os("RUBYLIB").unwrap_or_else(|| OsString::from(""));
+ let rubypath = env::join_paths(
+ env::split_paths(&rubypath).chain(vec![out_dir.to_path_buf().into_std_path_buf()]),
+ )?;
+
+ let status = Command::new("ruby")
+ .current_dir(out_dir)
+ .env("RUBYLIB", rubypath)
+ .arg(script_path)
+ .stderr(Stdio::inherit())
+ .stdout(Stdio::inherit())
+ .spawn()
+ .context("Failed to spawn `ruby` when running script")?
+ .wait()
+ .context("Failed to wait for `ruby` when running script")?;
+ if !status.success() {
+ bail!("running `ruby` failed");
+ }
+ Ok(())
+}
+
+fn generate_sources(
+ library_path: &Utf8Path,
+ out_dir: &Utf8Path,
+ test_helper: &UniFFITestHelper,
+) -> Result<()> {
+ for source in test_helper.get_compile_sources()? {
+ crate::generate_bindings(
+ &source.udl_path,
+ source.config_path.as_deref(),
+ vec!["ruby"],
+ Some(out_dir),
+ Some(library_path),
+ false,
+ )?;
+ }
+ Ok(())
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs
new file mode 100644
index 0000000000..828a8823ee
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.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 CallbackInterfaceCodeType {
+ id: String,
+}
+
+impl CallbackInterfaceCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for CallbackInterfaceCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!("CallbackInterface{}", self.type_label(oracle))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs
new file mode 100644
index 0000000000..f91be9ce54
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs
@@ -0,0 +1,98 @@
+/* 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, Literal, TypeIdentifier};
+
+pub struct OptionalCodeType {
+ inner: TypeIdentifier,
+}
+
+impl OptionalCodeType {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+}
+
+impl CodeType for OptionalCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ format!("{}?", oracle.find(&self.inner).type_label(oracle))
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!("Option{}", oracle.find(&self.inner).canonical_name(oracle))
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::Null => "nil".into(),
+ _ => oracle.find(&self.inner).literal(oracle, literal),
+ }
+ }
+}
+
+pub struct SequenceCodeType {
+ inner: TypeIdentifier,
+}
+
+impl SequenceCodeType {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+}
+
+impl CodeType for SequenceCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ format!("[{}]", oracle.find(&self.inner).type_label(oracle))
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Sequence{}",
+ oracle.find(&self.inner).canonical_name(oracle)
+ )
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::EmptySequence => "[]".into(),
+ _ => unreachable!(),
+ }
+ }
+}
+
+pub struct MapCodeType {
+ key: TypeIdentifier,
+ value: TypeIdentifier,
+}
+
+impl MapCodeType {
+ pub fn new(key: TypeIdentifier, value: TypeIdentifier) -> Self {
+ Self { key, value }
+ }
+}
+
+impl CodeType for MapCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "[{}: {}]",
+ oracle.find(&self.key).type_label(oracle),
+ oracle.find(&self.value).type_label(oracle)
+ )
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Dictionary{}{}",
+ oracle.find(&self.key).canonical_name(oracle),
+ oracle.find(&self.value).canonical_name(oracle)
+ )
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::EmptyMap => "[:]".into(),
+ _ => unreachable!(),
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs
new file mode 100644
index 0000000000..bf1806e5e2
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.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 CustomCodeType {
+ name: String,
+}
+
+impl CustomCodeType {
+ pub fn new(name: String) -> Self {
+ CustomCodeType { name }
+ }
+}
+
+impl CodeType for CustomCodeType {
+ 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/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs
new file mode 100644
index 0000000000..04d8422f6a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs
@@ -0,0 +1,33 @@
+/* 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, Literal};
+
+pub struct EnumCodeType {
+ id: String,
+}
+
+impl EnumCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for EnumCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ if let Literal::Enum(v, _) = literal {
+ format!(".{}", oracle.enum_variant_name(v))
+ } else {
+ unreachable!();
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.rs
new file mode 100644
index 0000000000..4219f24c39
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.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 ErrorCodeType {
+ id: String,
+}
+
+impl ErrorCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ErrorCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs
new file mode 100644
index 0000000000..c9a925cdaa
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs
@@ -0,0 +1,35 @@
+/* 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)
+ }
+
+ // lower and lift need to call public function which were generated for
+ // the original types.
+ fn lower(&self, oracle: &dyn CodeOracle) -> String {
+ format!("{}_lower", self.ffi_converter_name(oracle))
+ }
+
+ fn lift(&self, oracle: &dyn CodeOracle) -> String {
+ format!("{}_lift", self.ffi_converter_name(oracle))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs
new file mode 100644
index 0000000000..4f57734934
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs
@@ -0,0 +1,29 @@
+/* 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 TimestampCodeType;
+
+impl CodeType for TimestampCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ "Date".into()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ "Timestamp".into()
+ }
+}
+
+pub struct DurationCodeType;
+
+impl CodeType for DurationCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ "TimeInterval".into()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ "Duration".into()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
new file mode 100644
index 0000000000..abbe0b3539
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
@@ -0,0 +1,476 @@
+/* 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 std::borrow::Borrow;
+use std::cell::RefCell;
+use std::collections::{BTreeSet, HashMap, HashSet};
+
+use anyhow::{Context, Result};
+use askama::Template;
+use heck::{ToLowerCamelCase, ToUpperCamelCase};
+use serde::{Deserialize, Serialize};
+
+use super::Bindings;
+use crate::backend::{CodeOracle, CodeType, TemplateExpression, TypeIdentifier};
+use crate::interface::*;
+use crate::MergeWith;
+
+mod callback_interface;
+mod compounds;
+mod custom;
+mod enum_;
+mod error;
+mod external;
+mod miscellany;
+mod object;
+mod primitives;
+mod record;
+
+/// Config options for the caller to customize the generated Swift.
+///
+/// Note that this can only be used to control details of the Swift *that do not affect the underlying component*,
+/// since the details of the underlying component are entirely determined by the `ComponentInterface`.
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Config {
+ cdylib_name: Option<String>,
+ module_name: Option<String>,
+ ffi_module_name: Option<String>,
+ ffi_module_filename: Option<String>,
+ generate_module_map: Option<bool>,
+ omit_argument_labels: Option<bool>,
+ #[serde(default)]
+ custom_types: HashMap<String, CustomTypeConfig>,
+}
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+pub struct CustomTypeConfig {
+ imports: Option<Vec<String>>,
+ type_name: Option<String>,
+ into_custom: TemplateExpression,
+ from_custom: TemplateExpression,
+}
+
+impl Config {
+ /// The name of the Swift module containing the high-level foreign-language bindings.
+ pub fn module_name(&self) -> String {
+ match self.module_name.as_ref() {
+ Some(name) => name.clone(),
+ None => "uniffi".into(),
+ }
+ }
+
+ /// The name of the lower-level C module containing the FFI declarations.
+ pub fn ffi_module_name(&self) -> String {
+ match self.ffi_module_name.as_ref() {
+ Some(name) => name.clone(),
+ None => format!("{}FFI", self.module_name()),
+ }
+ }
+
+ /// The filename stem for the lower-level C module containing the FFI declarations.
+ pub fn ffi_module_filename(&self) -> String {
+ match self.ffi_module_filename.as_ref() {
+ Some(name) => name.clone(),
+ None => self.ffi_module_name(),
+ }
+ }
+
+ /// The name of the `.modulemap` file for the lower-level C module with FFI declarations.
+ pub fn modulemap_filename(&self) -> String {
+ format!("{}.modulemap", self.ffi_module_filename())
+ }
+
+ /// The name of the `.h` file for the lower-level C module with FFI declarations.
+ pub fn header_filename(&self) -> String {
+ format!("{}.h", self.ffi_module_filename())
+ }
+
+ /// The name of the compiled Rust library containing the FFI implementation.
+ pub fn cdylib_name(&self) -> String {
+ if let Some(cdylib_name) = &self.cdylib_name {
+ cdylib_name.clone()
+ } else {
+ "uniffi".into()
+ }
+ }
+
+ /// Whether to generate a `.modulemap` file for the lower-level C module with FFI declarations.
+ pub fn generate_module_map(&self) -> bool {
+ self.generate_module_map.unwrap_or(true)
+ }
+
+ /// Whether to omit argument labels in Swift function definitions.
+ pub fn omit_argument_labels(&self) -> bool {
+ self.omit_argument_labels.unwrap_or(false)
+ }
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ module_name: Some(ci.namespace().into()),
+ cdylib_name: Some(format!("uniffi_{}", ci.namespace())),
+ ..Default::default()
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ module_name: self.module_name.merge_with(&other.module_name),
+ ffi_module_name: self.ffi_module_name.merge_with(&other.ffi_module_name),
+ cdylib_name: self.cdylib_name.merge_with(&other.cdylib_name),
+ ffi_module_filename: self
+ .ffi_module_filename
+ .merge_with(&other.ffi_module_filename),
+ generate_module_map: self
+ .generate_module_map
+ .merge_with(&other.generate_module_map),
+ omit_argument_labels: self
+ .omit_argument_labels
+ .merge_with(&other.omit_argument_labels),
+ custom_types: self.custom_types.merge_with(&other.custom_types),
+ }
+ }
+}
+
+/// Generate UniFFI component bindings for Swift, as strings in memory.
+///
+pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<Bindings> {
+ let header = BridgingHeader::new(config, ci)
+ .render()
+ .context("failed to render Swift bridging header")?;
+ let library = SwiftWrapper::new(config.clone(), ci)
+ .render()
+ .context("failed to render Swift library")?;
+ let modulemap = if config.generate_module_map() {
+ Some(
+ ModuleMap::new(config, ci)
+ .render()
+ .context("failed to render Swift modulemap")?,
+ )
+ } else {
+ None
+ };
+ Ok(Bindings {
+ library,
+ header,
+ modulemap,
+ })
+}
+
+/// Renders Swift helper code for all types
+///
+/// This template is a bit different than others in that it stores internal state from the render
+/// process. Make sure to only call `render()` once.
+#[derive(Template)]
+#[template(syntax = "kt", escape = "none", path = "Types.swift")]
+pub struct TypeRenderer<'a> {
+ config: &'a Config,
+ ci: &'a ComponentInterface,
+ // Track included modules for the `include_once()` macro
+ include_once_names: RefCell<HashSet<String>>,
+ // Track imports added with the `add_import()` macro
+ imports: RefCell<BTreeSet<String>>,
+}
+
+impl<'a> TypeRenderer<'a> {
+ fn new(config: &'a Config, ci: &'a ComponentInterface) -> Self {
+ Self {
+ config,
+ ci,
+ include_once_names: RefCell::new(HashSet::new()),
+ imports: RefCell::new(BTreeSet::new()),
+ }
+ }
+
+ // The following methods are used by the `Types.kt` macros.
+
+ // Helper for the including a template, but only once.
+ //
+ // The first time this is called with a name it will return true, indicating that we should
+ // include the template. Subsequent calls will return false.
+ fn include_once_check(&self, name: &str) -> bool {
+ self.include_once_names
+ .borrow_mut()
+ .insert(name.to_string())
+ }
+
+ // Helper to add an import statement
+ //
+ // Call this inside your template to cause an import statement to be added at the top of the
+ // file. Imports will be sorted and de-deuped.
+ //
+ // Returns an empty string so that it can be used inside an askama `{{ }}` block.
+ fn add_import(&self, name: &str) -> &str {
+ self.imports.borrow_mut().insert(name.to_owned());
+ ""
+ }
+}
+
+/// Template for generating the `.h` file that defines the low-level C FFI.
+///
+/// This file defines only the low-level structs and functions that are exposed
+/// by the compiled Rust code. It gets wrapped into a higher-level API by the
+/// code from [`SwiftWrapper`].
+#[derive(Template)]
+#[template(syntax = "c", escape = "none", path = "BridgingHeaderTemplate.h")]
+pub struct BridgingHeader<'config, 'ci> {
+ _config: &'config Config,
+ ci: &'ci ComponentInterface,
+}
+
+impl<'config, 'ci> BridgingHeader<'config, 'ci> {
+ pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self {
+ Self {
+ _config: config,
+ ci,
+ }
+ }
+}
+
+/// Template for generating the `.modulemap` file that exposes the low-level C FFI.
+///
+/// This file defines how the low-level C FFI from [`BridgingHeader`] gets exposed
+/// as a Swift module that can be called by other Swift code. In our case, its only
+/// job is to define the *name* of the Swift module that will contain the FFI functions
+/// so that it can be imported by the higher-level code in from [`SwiftWrapper`].
+#[derive(Template)]
+#[template(syntax = "c", escape = "none", path = "ModuleMapTemplate.modulemap")]
+pub struct ModuleMap<'config, 'ci> {
+ config: &'config Config,
+ _ci: &'ci ComponentInterface,
+}
+
+impl<'config, 'ci> ModuleMap<'config, 'ci> {
+ pub fn new(config: &'config Config, _ci: &'ci ComponentInterface) -> Self {
+ Self { config, _ci }
+ }
+}
+
+#[derive(Template)]
+#[template(syntax = "swift", escape = "none", path = "wrapper.swift")]
+pub struct SwiftWrapper<'a> {
+ config: Config,
+ ci: &'a ComponentInterface,
+ type_helper_code: String,
+ type_imports: BTreeSet<String>,
+}
+impl<'a> SwiftWrapper<'a> {
+ pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
+ let type_renderer = TypeRenderer::new(&config, ci);
+ let type_helper_code = type_renderer.render().unwrap();
+ let type_imports = type_renderer.imports.into_inner();
+ Self {
+ config,
+ ci,
+ type_helper_code,
+ type_imports,
+ }
+ }
+
+ pub fn imports(&self) -> Vec<String> {
+ self.type_imports.iter().cloned().collect()
+ }
+
+ pub fn initialization_fns(&self) -> Vec<String> {
+ self.ci
+ .iter_types()
+ .into_iter()
+ .filter_map(|t| t.initialization_fn(&SwiftCodeOracle))
+ .collect()
+ }
+}
+
+#[derive(Clone)]
+pub struct SwiftCodeOracle;
+
+impl SwiftCodeOracle {
+ // Map `Type` instances to a `Box<dyn CodeType>` for that type.
+ //
+ // There is a companion match in `templates/Types.swift` which performs a similar function for the
+ // template code.
+ //
+ // - When adding additional types here, make sure to also add a match arm to the `Types.swift` template.
+ // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> {
+ match type_ {
+ Type::UInt8 => Box::new(primitives::UInt8CodeType),
+ Type::Int8 => Box::new(primitives::Int8CodeType),
+ Type::UInt16 => Box::new(primitives::UInt16CodeType),
+ Type::Int16 => Box::new(primitives::Int16CodeType),
+ Type::UInt32 => Box::new(primitives::UInt32CodeType),
+ Type::Int32 => Box::new(primitives::Int32CodeType),
+ Type::UInt64 => Box::new(primitives::UInt64CodeType),
+ Type::Int64 => Box::new(primitives::Int64CodeType),
+ Type::Float32 => Box::new(primitives::Float32CodeType),
+ Type::Float64 => Box::new(primitives::Float64CodeType),
+ Type::Boolean => Box::new(primitives::BooleanCodeType),
+ Type::String => Box::new(primitives::StringCodeType),
+
+ Type::Timestamp => Box::new(miscellany::TimestampCodeType),
+ Type::Duration => Box::new(miscellany::DurationCodeType),
+
+ Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
+ Type::Object(id) => Box::new(object::ObjectCodeType::new(id)),
+ Type::Record(id) => Box::new(record::RecordCodeType::new(id)),
+ Type::Error(id) => Box::new(error::ErrorCodeType::new(id)),
+ Type::CallbackInterface(id) => {
+ Box::new(callback_interface::CallbackInterfaceCodeType::new(id))
+ }
+
+ 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 { 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")
+ }
+ }
+ }
+}
+
+impl CodeOracle for SwiftCodeOracle {
+ fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+ self.create_code_type(type_.clone())
+ }
+
+ /// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc).
+ fn class_name(&self, nm: &str) -> String {
+ nm.to_string().to_upper_camel_case()
+ }
+
+ /// Get the idiomatic Swift rendering of a function name.
+ fn fn_name(&self, nm: &str) -> String {
+ format!("`{}`", nm.to_string().to_lower_camel_case())
+ }
+
+ /// Get the idiomatic Swift rendering of a variable name.
+ fn var_name(&self, nm: &str) -> String {
+ format!("`{}`", nm.to_string().to_lower_camel_case())
+ }
+
+ /// Get the idiomatic Swift rendering of an individual enum variant.
+ fn enum_variant_name(&self, nm: &str) -> String {
+ format!("`{}`", nm.to_string().to_lower_camel_case())
+ }
+
+ /// Get the idiomatic Swift rendering of an exception name.
+ fn error_name(&self, nm: &str) -> String {
+ format!("`{}`", self.class_name(nm))
+ }
+
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::Int8 => "int8_t".into(),
+ FfiType::UInt8 => "uint8_t".into(),
+ FfiType::Int16 => "int16_t".into(),
+ FfiType::UInt16 => "uint16_t".into(),
+ FfiType::Int32 => "int32_t".into(),
+ FfiType::UInt32 => "uint32_t".into(),
+ FfiType::Int64 => "int64_t".into(),
+ FfiType::UInt64 => "uint64_t".into(),
+ FfiType::Float32 => "float".into(),
+ FfiType::Float64 => "double".into(),
+ FfiType::RustArcPtr(_) => "void*_Nonnull".into(),
+ FfiType::RustBuffer(_) => "RustBuffer".into(),
+ FfiType::ForeignBytes => "ForeignBytes".into(),
+ FfiType::ForeignCallback => "ForeignCallback _Nonnull".to_string(),
+ }
+ }
+}
+
+pub mod filters {
+ use super::*;
+
+ fn oracle() -> &'static SwiftCodeOracle {
+ &SwiftCodeOracle
+ }
+
+ pub fn type_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.type_label(oracle))
+ }
+
+ pub fn canonical_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.canonical_name(oracle))
+ }
+
+ pub fn ffi_converter_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.ffi_converter_name(oracle()))
+ }
+
+ pub fn lower_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.lower(oracle()))
+ }
+
+ pub fn write_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.write(oracle()))
+ }
+
+ pub fn lift_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.lift(oracle()))
+ }
+
+ pub fn read_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.read(oracle()))
+ }
+
+ pub fn literal_swift(
+ literal: &Literal,
+ codetype: &impl CodeType,
+ ) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.literal(oracle, literal))
+ }
+
+ /// Get the Swift syntax for representing a given low-level `FfiType`.
+ pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_type_label(type_))
+ }
+
+ /// Get the type that a type is lowered into. This is subtly different than `type_ffi`, see
+ /// #1106 for details
+ pub fn type_ffi_lowered(ffi_type: &FfiType) -> Result<String, askama::Error> {
+ Ok(match ffi_type {
+ FfiType::Int8 => "Int8".into(),
+ FfiType::UInt8 => "UInt8".into(),
+ FfiType::Int16 => "Int16".into(),
+ FfiType::UInt16 => "UInt16".into(),
+ FfiType::Int32 => "Int32".into(),
+ FfiType::UInt32 => "UInt32".into(),
+ FfiType::Int64 => "Int64".into(),
+ FfiType::UInt64 => "UInt64".into(),
+ FfiType::Float32 => "float".into(),
+ FfiType::Float64 => "double".into(),
+ FfiType::RustArcPtr(_) => "void*_Nonnull".into(),
+ FfiType::RustBuffer(_) => "RustBuffer".into(),
+ FfiType::ForeignBytes => "ForeignBytes".into(),
+ FfiType::ForeignCallback => "ForeignCallback _Nonnull".to_string(),
+ })
+ }
+
+ /// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc).
+ pub fn class_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().class_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of a function name.
+ pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().fn_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of a variable name.
+ pub fn var_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().var_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of an individual enum variant.
+ pub fn enum_variant_swift(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().enum_variant_name(nm))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs
new file mode 100644
index 0000000000..d227f8e7cd
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.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 ObjectCodeType {
+ id: String,
+}
+
+impl ObjectCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ObjectCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs
new file mode 100644
index 0000000000..9989e0a421
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs
@@ -0,0 +1,87 @@
+/* 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, Literal};
+use crate::interface::{types::Type, Radix};
+use paste::paste;
+
+fn render_literal(oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ fn typed_number(oracle: &dyn CodeOracle, type_: &Type, num_str: String) -> String {
+ match type_ {
+ // special case Int32.
+ Type::Int32 => num_str,
+ // otherwise use constructor e.g. UInt8(x)
+ Type::Int8
+ | Type::UInt8
+ | Type::Int16
+ | Type::UInt16
+ | Type::UInt32
+ | Type::Int64
+ | Type::UInt64
+ | Type::Float32
+ | Type::Float64 =>
+ // XXX we should pass in the codetype itself.
+ {
+ format!("{}({num_str})", oracle.find(type_).type_label(oracle))
+ }
+ _ => panic!("Unexpected literal: {num_str} is not a number"),
+ }
+ }
+
+ match literal {
+ Literal::Boolean(v) => format!("{v}"),
+ Literal::String(s) => format!("\"{s}\""),
+ Literal::Int(i, radix, type_) => typed_number(
+ oracle,
+ type_,
+ match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ ),
+ Literal::UInt(i, radix, type_) => typed_number(
+ oracle,
+ type_,
+ match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ ),
+ Literal::Float(string, type_) => typed_number(oracle, type_, string.clone()),
+ _ => unreachable!("Literal"),
+ }
+}
+
+macro_rules! impl_code_type_for_primitive {
+ ($T:ty, $class_name:literal) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ $class_name.into()
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, &literal)
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_primitive!(BooleanCodeType, "Bool");
+impl_code_type_for_primitive!(StringCodeType, "String");
+impl_code_type_for_primitive!(Int8CodeType, "Int8");
+impl_code_type_for_primitive!(Int16CodeType, "Int16");
+impl_code_type_for_primitive!(Int32CodeType, "Int32");
+impl_code_type_for_primitive!(Int64CodeType, "Int64");
+impl_code_type_for_primitive!(UInt8CodeType, "UInt8");
+impl_code_type_for_primitive!(UInt16CodeType, "UInt16");
+impl_code_type_for_primitive!(UInt32CodeType, "UInt32");
+impl_code_type_for_primitive!(UInt64CodeType, "UInt64");
+impl_code_type_for_primitive!(Float32CodeType, "Float");
+impl_code_type_for_primitive!(Float64CodeType, "Double");
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs
new file mode 100644
index 0000000000..0943bcaada
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.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 RecordCodeType {
+ id: String,
+}
+
+impl RecordCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for RecordCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs
new file mode 100644
index 0000000000..5fd1c404cb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs
@@ -0,0 +1,99 @@
+/* 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/. */
+
+//! # Swift bindings backend for UniFFI
+//!
+//! This module generates Swift bindings from a [`ComponentInterface`] definition,
+//! using Swift's builtin support for loading C header files.
+//!
+//! Conceptually, the generated bindings are split into two Swift modules, one for the low-level
+//! C FFI layer and one for the higher-level Swift bindings. For a UniFFI component named "example"
+//! we generate:
+//!
+//! * A C header file `exampleFFI.h` declaring the low-level structs and functions for calling
+//! into Rust, along with a corresponding `exampleFFI.modulemap` to expose them to Swift.
+//!
+//! * A Swift source file `example.swift` that imports the `exampleFFI` module and wraps it
+//! to provide the higher-level Swift API.
+//!
+//! Most of the concepts in a [`ComponentInterface`] have an obvious counterpart in Swift,
+//! with the details documented in inline comments where appropriate.
+//!
+//! To handle lifting/lowering/serializing types across the FFI boundary, the Swift code
+//! defines a `protocol ViaFfi` that is analogous to the `uniffi::ViaFfi` Rust trait.
+//! Each type that can traverse the FFI conforms to the `ViaFfi` protocol, which specifies:
+//!
+//! * The corresponding low-level type.
+//! * How to lift from and lower into into that type.
+//! * How to read from and write into a byte buffer.
+//!
+
+use std::{io::Write, process::Command};
+
+use anyhow::Result;
+use camino::Utf8Path;
+use fs_err::File;
+
+pub mod gen_swift;
+pub use gen_swift::{generate_bindings, Config};
+mod test;
+
+use super::super::interface::ComponentInterface;
+pub use test::run_test;
+
+/// The Swift bindings generated from a [`ComponentInterface`].
+///
+pub struct Bindings {
+ /// The contents of the generated `.swift` file, as a string.
+ library: String,
+ /// The contents of the generated `.h` file, as a string.
+ header: String,
+ /// The contents of the generated `.modulemap` file, as a string.
+ modulemap: Option<String>,
+}
+
+/// Write UniFFI component bindings for Swift as files on disk.
+///
+/// Unlike other target languages, binding to Rust code from Swift involves more than just
+/// generating a `.swift` file. We also need to produce a `.h` file with the C-level API
+/// declarations, and a `.modulemap` file to tell Swift how to use it.
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+) -> Result<()> {
+ let Bindings {
+ header,
+ library,
+ modulemap,
+ } = generate_bindings(config, ci)?;
+
+ let source_file = out_dir.join(format!("{}.swift", config.module_name()));
+ let mut l = File::create(&source_file)?;
+ write!(l, "{library}")?;
+
+ let mut h = File::create(out_dir.join(config.header_filename()))?;
+ write!(h, "{header}")?;
+
+ if let Some(modulemap) = modulemap {
+ let mut m = File::create(out_dir.join(config.modulemap_filename()))?;
+ write!(m, "{modulemap}")?;
+ }
+
+ if try_format_code {
+ if let Err(e) = Command::new("swiftformat")
+ .arg(source_file.as_str())
+ .output()
+ {
+ println!(
+ "Warning: Unable to auto-format {} using swiftformat: {:?}",
+ source_file.file_name().unwrap(),
+ e
+ )
+ }
+ }
+
+ Ok(())
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift
new file mode 100644
index 0000000000..465e519628
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift
@@ -0,0 +1,20 @@
+fileprivate struct FfiConverterBool : FfiConverter {
+ typealias FfiType = Int8
+ typealias SwiftType = Bool
+
+ public static func lift(_ value: Int8) throws -> Bool {
+ return value != 0
+ }
+
+ public static func lower(_ value: Bool) -> Int8 {
+ return value ? 1 : 0
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Bool, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h
new file mode 100644
index 0000000000..50b95d96e8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h
@@ -0,0 +1,55 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+// The following structs are used to implement the lowest level
+// of the FFI, and thus useful to multiple uniffied crates.
+// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
+#ifdef UNIFFI_SHARED_H
+ // We also try to prevent mixing versions of shared uniffi header structs.
+ // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4
+ #ifndef UNIFFI_SHARED_HEADER_V4
+ #error Combining helper code from multiple versions of uniffi is not supported
+ #endif // ndef UNIFFI_SHARED_HEADER_V4
+#else
+#define UNIFFI_SHARED_H
+#define UNIFFI_SHARED_HEADER_V4
+// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
+// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
+
+typedef struct RustBuffer
+{
+ int32_t capacity;
+ int32_t len;
+ uint8_t *_Nullable data;
+} RustBuffer;
+
+typedef int32_t (*ForeignCallback)(uint64_t, int32_t, RustBuffer, RustBuffer *_Nonnull);
+
+typedef struct ForeignBytes
+{
+ int32_t len;
+ const uint8_t *_Nullable data;
+} ForeignBytes;
+
+// Error definitions
+typedef struct RustCallStatus {
+ int8_t code;
+ RustBuffer errorBuf;
+} RustCallStatus;
+
+// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
+// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
+#endif // def UNIFFI_SHARED_H
+
+{% for func in ci.iter_ffi_function_definitions() -%}
+ {%- match func.return_type() -%}{%- when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}(
+ {% call swift::arg_list_ffi_decl(func) %}
+ );
+{% endfor -%}
+
+{% import "macros.swift" as swift %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift
new file mode 100644
index 0000000000..9ab9bdbe48
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift
@@ -0,0 +1,60 @@
+fileprivate extension NSLock {
+ func withLock<T>(f: () throws -> T) rethrows -> T {
+ self.lock()
+ defer { self.unlock() }
+ return try f()
+ }
+}
+
+fileprivate typealias UniFFICallbackHandle = UInt64
+fileprivate class UniFFICallbackHandleMap<T> {
+ private var leftMap: [UniFFICallbackHandle: T] = [:]
+ private var counter: [UniFFICallbackHandle: UInt64] = [:]
+ private var rightMap: [ObjectIdentifier: UniFFICallbackHandle] = [:]
+
+ private let lock = NSLock()
+ private var currentHandle: UniFFICallbackHandle = 0
+ private let stride: UniFFICallbackHandle = 1
+
+ func insert(obj: T) -> UniFFICallbackHandle {
+ lock.withLock {
+ let id = ObjectIdentifier(obj as AnyObject)
+ let handle = rightMap[id] ?? {
+ currentHandle += stride
+ let handle = currentHandle
+ leftMap[handle] = obj
+ rightMap[id] = handle
+ return handle
+ }()
+ counter[handle] = (counter[handle] ?? 0) + 1
+ return handle
+ }
+ }
+
+ func get(handle: UniFFICallbackHandle) -> T? {
+ lock.withLock {
+ leftMap[handle]
+ }
+ }
+
+ func delete(handle: UniFFICallbackHandle) {
+ remove(handle: handle)
+ }
+
+ @discardableResult
+ func remove(handle: UniFFICallbackHandle) -> T? {
+ lock.withLock {
+ defer { counter[handle] = (counter[handle] ?? 1) - 1 }
+ guard counter[handle] == 1 else { return leftMap[handle] }
+ let obj = leftMap.removeValue(forKey: handle)
+ if let obj = obj {
+ rightMap.removeValue(forKey: ObjectIdentifier(obj as AnyObject))
+ }
+ return obj
+ }
+ }
+}
+
+// Magic number for the Rust proxy to call using the same mechanism as every other method,
+// to free the callback once it's dropped by Rust.
+private let IDX_CALLBACK_FREE: Int32 = 0
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift
new file mode 100644
index 0000000000..7842511a83
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift
@@ -0,0 +1,154 @@
+{%- let cbi = ci.get_callback_interface_definition(name).unwrap() %}
+{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %}
+{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %}
+
+// Declaration and FfiConverters for {{ type_name }} Callback Interface
+
+public protocol {{ type_name }} : AnyObject {
+ {% for meth in cbi.methods() -%}
+ func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::throws(meth) -%}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %} -> {{ return_type|type_name -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+}
+
+// The ForeignCallback that is passed to Rust.
+fileprivate let {{ foreign_callback }} : ForeignCallback =
+ { (handle: UniFFICallbackHandle, method: Int32, args: RustBuffer, out_buf: UnsafeMutablePointer<RustBuffer>) -> Int32 in
+ {% for meth in cbi.methods() -%}
+ {%- let method_name = format!("invoke_{}", meth.name())|fn_name -%}
+
+ func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ args: RustBuffer) throws -> RustBuffer {
+ defer { args.deallocate() }
+ {#- Unpacking args from the RustBuffer #}
+ {%- if meth.arguments().len() != 0 -%}
+ {#- Calling the concrete callback object #}
+
+ 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 !loop.last %}, {% endif %}
+ {% endfor -%}
+ )
+ {% else %}
+ {% if meth.return_type().is_some() %}let result = {% endif -%}
+ {% if meth.throws() %}try {% endif -%}
+ swiftCallbackInterface.{{ meth.name()|fn_name }}()
+ {% endif -%}
+
+ {#- Packing up the return value into a RustBuffer #}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) -%}
+ var writer = [UInt8]()
+ {{ return_type|write_fn }}(result, into: &writer)
+ return RustBuffer(bytes: writer)
+ {%- else -%}
+ return RustBuffer()
+ {% endmatch -%}
+ // TODO catch errors and report them back to Rust.
+ // https://github.com/mozilla/uniffi-rs/issues/351
+
+ }
+ {% endfor %}
+
+ let cb: {{ cbi|type_name }}
+ do {
+ cb = try {{ ffi_converter_name }}.lift(handle)
+ } catch {
+ out_buf.pointee = {{ Type::String.borrow()|lower_fn }}("{{ cbi.name() }}: Invalid handle")
+ return -1
+ }
+
+ switch method {
+ case IDX_CALLBACK_FREE:
+ {{ ffi_converter_name }}.drop(handle: handle)
+ // No return value.
+ // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return 0
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
+ case {{ loop.index }}:
+ do {
+ out_buf.pointee = try {{ method_name }}(cb, args)
+ // Value written to out buffer.
+ // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return 1
+ {%- match meth.throws_type() %}
+ {%- when Some(error_type) %}
+ } catch let error as {{ error_type|type_name }} {
+ out_buf.pointee = {{ error_type|lower_fn }}(error)
+ return -2
+ {%- else %}
+ {%- endmatch %}
+ } catch let error {
+ out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error))
+ return -1
+ }
+ {% endfor %}
+ // This should never happen, because an out of bounds method index won't
+ // ever be used. Once we can catch errors, we should return an InternalError.
+ // https://github.com/mozilla/uniffi-rs/issues/351
+ default:
+ // An unexpected error happened.
+ // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return -1
+ }
+ }
+
+// FfiConverter protocol for callback interfaces
+fileprivate struct {{ ffi_converter_name }} {
+ // Initialize our callback method with the scaffolding code
+ private static var callbackInitialized = false
+ private static func initCallback() {
+ try! rustCall { (err: UnsafeMutablePointer<RustCallStatus>) in
+ {{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err)
+ }
+ }
+ private static func ensureCallbackinitialized() {
+ if !callbackInitialized {
+ initCallback()
+ callbackInitialized = true
+ }
+ }
+
+ static func drop(handle: UniFFICallbackHandle) {
+ handleMap.remove(handle: handle)
+ }
+
+ 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 = UniFFICallbackHandle
+
+ public static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType {
+ ensureCallbackinitialized();
+ guard let callback = handleMap.get(handle: handle) else {
+ throw UniffiInternalError.unexpectedStaleHandle
+ }
+ return callback
+ }
+
+ 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)
+ }
+
+ public static func lower(_ v: SwiftType) -> UniFFICallbackHandle {
+ ensureCallbackinitialized();
+ return handleMap.insert(obj: v)
+ }
+
+ public static func write(_ v: SwiftType, into buf: inout [UInt8]) {
+ ensureCallbackinitialized();
+ writeInt(&buf, lower(v))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift
new file mode 100644
index 0000000000..cb63f0b97b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift
@@ -0,0 +1,86 @@
+{%- 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 #}
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ */
+public typealias {{ name }} = {{ builtin|type_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) %}
+
+{# When the config specifies a different type name, create a typealias for it #}
+{%- match config.type_name %}
+{%- when Some with (concrete_type_name) %}
+/**
+ * Typealias from the type name used in the UDL file to the custom type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ */
+public typealias {{ name }} = {{ concrete_type_name }}
+{%- else %}
+{%- endmatch %}
+
+{%- match config.imports %}
+{%- when Some(imports) %}
+{%- for import_name in imports %}
+{{ self.add_import(import_name) }}
+{%- endfor %}
+{%- else %}
+{%- endmatch %}
+
+public struct FfiConverterType{{ name }}: FfiConverter {
+ {#- Custom type config supplied, use it to convert the builtin type #}
+
+ 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") }}
+ }
+
+ public static func write(_ value: {{ name }}, into buf: inout [UInt8]) {
+ let builtinValue = {{ config.from_custom.render("value") }}
+ return {{ builtin|write_fn }}(builtinValue, into: &buf)
+ }
+
+ public static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} {
+ let builtinValue = try {{ builtin|lift_fn }}(value)
+ return {{ config.into_custom.render("builtinValue") }}
+ }
+
+ public static func lower(_ value: {{ name }}) -> {{ ffi_type_name }} {
+ let builtinValue = {{ config.from_custom.render("value") }}
+ return {{ builtin|lower_fn }}(builtinValue)
+ }
+}
+
+{#
+We always write these public functions just incase the type is used as
+an external type by another crate.
+#}
+public func FfiConverterType{{ name }}_lift(_ buf: RustBuffer) throws -> {{ name }} {
+ return try FfiConverterType{{ name }}.lift(buf)
+}
+
+public func FfiConverterType{{ name }}_lower(_ value: {{ name }}) -> RustBuffer {
+ return FfiConverterType{{ name }}.lower(value)
+}
+
+{%- endmatch %}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift
new file mode 100644
index 0000000000..c2aa49e9d1
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift
@@ -0,0 +1,24 @@
+fileprivate struct FfiConverterDuration: FfiConverterRustBuffer {
+ typealias SwiftType = 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)
+ }
+
+ 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")
+ }
+
+ if value < 0 {
+ fatalError("Invalid duration, must be non-negative")
+ }
+
+ let seconds = UInt64(value)
+ let nanoseconds = UInt32((value - Double(seconds)) * 1.0e9)
+ writeInt(&buf, seconds)
+ writeInt(&buf, nanoseconds)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift
new file mode 100644
index 0000000000..74f0a3effe
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift
@@ -0,0 +1,60 @@
+// Note that we don't yet support `indirect` for enums.
+// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion.
+{%- let e = ci.get_enum_definition(name).unwrap() %}
+public enum {{ type_name }} {
+ {% for variant in e.variants() %}
+ case {{ variant.name()|enum_variant_swift }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%}
+ {% endfor %}
+}
+
+public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
+ typealias SwiftType = {{ 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() %}
+ 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)
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}
+ ){%- endif %}
+ {% endfor %}
+ default: throw UniffiInternalError.unexpectedEnumCase
+ }
+ }
+
+ public 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 %}):
+ writeInt(&buf, Int32({{ loop.index }}))
+ {% for field in variant.fields() -%}
+ {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf)
+ {% endfor -%}
+ {% else %}
+ case .{{ variant.name()|enum_variant_swift }}:
+ writeInt(&buf, Int32({{ loop.index }}))
+ {% endif %}
+ {%- endfor %}
+ }
+ }
+}
+
+{#
+We always write these public functions just in case the enum is used as
+an external type by another crate.
+#}
+public func {{ ffi_converter_name }}_lift(_ buf: RustBuffer) throws -> {{ type_name }} {
+ return try {{ ffi_converter_name }}.lift(buf)
+}
+
+public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuffer {
+ return {{ ffi_converter_name }}.lower(value)
+}
+
+{% if !contains_object_references %}
+extension {{ type_name }}: Equatable, Hashable {}
+{% endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift
new file mode 100644
index 0000000000..b2c4bfe492
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift
@@ -0,0 +1,83 @@
+{%- let e = ci.get_error_definition(name).unwrap() %}
+public enum {{ type_name }} {
+
+ {% if e.is_flat() %}
+ {% for variant in e.variants() %}
+ // Simple error enums only carry a message
+ case {{ variant.name()|class_name }}(message: String)
+ {% endfor %}
+
+ {%- else %}
+ {% for variant in e.variants() %}
+ case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%}
+ {% endfor %}
+
+ {%- endif %}
+}
+
+public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
+ typealias SwiftType = {{ 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 {
+
+ {% 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)
+ )
+ {% endfor %}
+
+ {% else %}
+
+ {% 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)
+ {%- if !loop.last %}, {% endif %}
+ {% endfor -%}
+ ){% endif -%}
+ {% endfor %}
+
+ {% endif -%}
+ default: throw UniffiInternalError.unexpectedEnumCase
+ }
+ }
+
+ public 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):
+ writeInt(&buf, Int32({{ loop.index }}))
+ {{ Type::String.borrow()|write_fn }}(message, into: &buf)
+ {%- endfor %}
+
+ {% else %}
+
+ {% 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 %}):
+ writeInt(&buf, Int32({{ loop.index }}))
+ {% for field in variant.fields() -%}
+ {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf)
+ {% endfor -%}
+ {% else %}
+ case .{{ variant.name()|class_name }}:
+ writeInt(&buf, Int32({{ loop.index }}))
+ {% endif %}
+ {%- endfor %}
+
+ {%- endif %}
+ }
+ }
+}
+
+{% if !contains_object_references %}
+extension {{ type_name }}: Equatable, Hashable {}
+{% endif %}
+extension {{ type_name }}: Error { }
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift
new file mode 100644
index 0000000000..fb986beab6
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterFloat: FfiConverterPrimitive {
+ typealias FfiType = Float
+ typealias SwiftType = Float
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Float {
+ return try lift(readFloat(&buf))
+ }
+
+ public static func write(_ value: Float, into buf: inout [UInt8]) {
+ writeFloat(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift
new file mode 100644
index 0000000000..74421c045c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterDouble: FfiConverterPrimitive {
+ typealias FfiType = Double
+ typealias SwiftType = Double
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Double {
+ return try lift(readDouble(&buf))
+ }
+
+ public static func write(_ value: Double, into buf: inout [UInt8]) {
+ writeDouble(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift
new file mode 100644
index 0000000000..18e2397733
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift
@@ -0,0 +1,84 @@
+// An error type for FFI errors. These errors occur at the UniFFI level, not
+// the library level.
+fileprivate enum UniffiInternalError: LocalizedError {
+ case bufferOverflow
+ case incompleteData
+ case unexpectedOptionalTag
+ case unexpectedEnumCase
+ case unexpectedNullPointer
+ case unexpectedRustCallStatusCode
+ case unexpectedRustCallError
+ case unexpectedStaleHandle
+ case rustPanic(_ message: String)
+
+ public var errorDescription: String? {
+ switch self {
+ case .bufferOverflow: return "Reading the requested value would read past the end of the buffer"
+ case .incompleteData: return "The buffer still has data after lifting its containing value"
+ case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1"
+ case .unexpectedEnumCase: return "Raw enum value doesn't match any cases"
+ case .unexpectedNullPointer: return "Raw pointer value was null"
+ case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code"
+ case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified"
+ case .unexpectedStaleHandle: return "The object in the handle map has been dropped already"
+ case let .rustPanic(message): return message
+ }
+ }
+}
+
+fileprivate let CALL_SUCCESS: Int8 = 0
+fileprivate let CALL_ERROR: Int8 = 1
+fileprivate let CALL_PANIC: Int8 = 2
+
+fileprivate extension RustCallStatus {
+ init() {
+ self.init(
+ code: CALL_SUCCESS,
+ errorBuf: RustBuffer.init(
+ capacity: 0,
+ len: 0,
+ data: nil
+ )
+ )
+ }
+}
+
+private func rustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T {
+ try makeRustCall(callback, errorHandler: {
+ $0.deallocate()
+ return UniffiInternalError.unexpectedRustCallError
+ })
+}
+
+private func rustCallWithError<T, F: FfiConverter>
+ (_ errorFfiConverter: F.Type, _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T
+ where F.SwiftType: Error, F.FfiType == RustBuffer
+ {
+ try makeRustCall(callback, errorHandler: { return try errorFfiConverter.lift($0) })
+}
+
+private func makeRustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T, errorHandler: (RustBuffer) throws -> Error) throws -> T {
+ var callStatus = RustCallStatus.init()
+ let returnedVal = callback(&callStatus)
+ switch callStatus.code {
+ case CALL_SUCCESS:
+ return returnedVal
+
+ case CALL_ERROR:
+ throw try errorHandler(callStatus.errorBuf)
+
+ case CALL_PANIC:
+ // When the rust code sees a panic, it tries to construct a RustBuffer
+ // with the message. But if that code panics, then it just sends back
+ // an empty buffer.
+ if callStatus.errorBuf.len > 0 {
+ throw UniffiInternalError.rustPanic(try {{ Type::String.borrow()|lift_fn }}(callStatus.errorBuf))
+ } else {
+ callStatus.errorBuf.deallocate()
+ throw UniffiInternalError.rustPanic("Rust panic")
+ }
+
+ default:
+ throw UniffiInternalError.unexpectedRustCallStatusCode
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift
new file mode 100644
index 0000000000..ac57fc5e58
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterInt16: FfiConverterPrimitive {
+ typealias FfiType = Int16
+ typealias SwiftType = Int16
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int16 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Int16, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift
new file mode 100644
index 0000000000..0ccfc13e4e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterInt32: FfiConverterPrimitive {
+ typealias FfiType = Int32
+ typealias SwiftType = Int32
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int32 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Int32, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift
new file mode 100644
index 0000000000..d7d4082933
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterInt64: FfiConverterPrimitive {
+ typealias FfiType = Int64
+ typealias SwiftType = Int64
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int64 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Int64, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift
new file mode 100644
index 0000000000..f2387e4340
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterInt8: FfiConverterPrimitive {
+ typealias FfiType = Int8
+ typealias SwiftType = Int8
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int8 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Int8, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift
new file mode 100644
index 0000000000..05713aca26
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift
@@ -0,0 +1,22 @@
+fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
+ public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
+ let len = Int32(value.count)
+ writeInt(&buf, len)
+ for (key, value) in value {
+ {{ key_type|write_fn }}(key, into: &buf)
+ {{ value_type|write_fn }}(value, into: &buf)
+ }
+ }
+
+ 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))
+ for _ in 0..<len {
+ let key = try {{ key_type|read_fn }}(from: &buf)
+ let value = try {{ value_type|read_fn }}(from: &buf)
+ dict[key] = value
+ }
+ return dict
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap
new file mode 100644
index 0000000000..f5f73ceb1b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap
@@ -0,0 +1,6 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+module {{ config.ffi_module_name() }} {
+ header "{{ config.header_filename() }}"
+ export *
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift
new file mode 100644
index 0000000000..ab0b22b1d8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift
@@ -0,0 +1,88 @@
+{%- let obj = ci.get_object_definition(name).unwrap() %}
+public protocol {{ obj.name() }}Protocol {
+ {% for meth in obj.methods() -%}
+ func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::throws(meth) -%}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %} -> {{ return_type|type_name -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+}
+
+public class {{ type_name }}: {{ obj.name() }}Protocol {
+ fileprivate let pointer: UnsafeMutableRawPointer
+
+ // TODO: We'd like this to be `private` but for Swifty reasons,
+ // we can't implement `FfiConverter` without making this `required` and we can't
+ // make it `required` without making it `public`.
+ required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
+ self.pointer = pointer
+ }
+
+ {%- match obj.primary_constructor() %}
+ {%- when Some with (cons) %}
+ public convenience init({% call swift::arg_list_decl(cons) -%}) {% call swift::throws(cons) %} {
+ self.init(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %})
+ }
+ {%- when None %}
+ {%- endmatch %}
+
+ deinit {
+ try! rustCall { {{ obj.ffi_object_free().name() }}(pointer, $0) }
+ }
+
+ {% for cons in obj.alternate_constructors() %}
+ public static func {{ cons.name()|fn_name }}({% call swift::arg_list_decl(cons) %}) {% call swift::throws(cons) %} -> {{ type_name }} {
+ return {{ type_name }}(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %})
+ }
+ {% endfor %}
+
+ {# // TODO: Maybe merge the two templates (i.e the one with a return type and the one without) #}
+ {% for meth in obj.methods() -%}
+ {%- match meth.return_type() -%}
+
+ {%- when Some with (return_type) -%}
+ public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} -> {{ return_type|type_name }} {
+ return {% call swift::try(meth) %} {{ return_type|lift_fn }}(
+ {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %}
+ )
+ }
+
+ {%- when None -%}
+ public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} {
+ {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %}
+ }
+ {%- endmatch %}
+ {% endfor %}
+}
+
+
+public struct {{ ffi_converter_name }}: FfiConverter {
+ typealias FfiType = UnsafeMutableRawPointer
+ typealias SwiftType = {{ 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.
+ let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v))
+ if (ptr == nil) {
+ throw UniffiInternalError.unexpectedNullPointer
+ }
+ return try lift(ptr!)
+ }
+
+ 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)))))
+ }
+
+ public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} {
+ return {{ type_name}}(unsafeFromRawPointer: pointer)
+ }
+
+ public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer {
+ return value.pointer
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift
new file mode 100644
index 0000000000..1dac65be63
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift
@@ -0,0 +1,20 @@
+fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
+ typealias SwiftType = {{ type_name }}
+
+ public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
+ guard let value = value else {
+ writeInt(&buf, Int8(0))
+ return
+ }
+ writeInt(&buf, Int8(1))
+ {{ inner_type|write_fn }}(value, into: &buf)
+ }
+
+ 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)
+ default: throw UniffiInternalError.unexpectedOptionalTag
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift
new file mode 100644
index 0000000000..7f2de44052
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift
@@ -0,0 +1,62 @@
+{%- let rec = ci.get_record_definition(name).unwrap() %}
+public struct {{ type_name }} {
+ {%- for field in rec.fields() %}
+ public var {{ field.name()|var_name }}: {{ field|type_name }}
+ {%- endfor %}
+
+ // Default memberwise initializers are never public by default, so we
+ // declare one manually.
+ public init({% call swift::field_list_decl(rec) %}) {
+ {%- for field in rec.fields() %}
+ self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
+ {%- endfor %}
+ }
+}
+
+{% if !contains_object_references %}
+extension {{ type_name }}: Equatable, Hashable {
+ public static func ==(lhs: {{ type_name }}, rhs: {{ type_name }}) -> Bool {
+ {%- for field in rec.fields() %}
+ if lhs.{{ field.name()|var_name }} != rhs.{{ field.name()|var_name }} {
+ return false
+ }
+ {%- endfor %}
+ return true
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ {%- for field in rec.fields() %}
+ hasher.combine({{ field.name()|var_name }})
+ {%- endfor %}
+ }
+}
+{% endif %}
+
+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)
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}
+ )
+ }
+
+ 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 %}
+ }
+}
+
+{#
+We always write these public functions just in case the struct is used as
+an external type by another crate.
+#}
+public func {{ ffi_converter_name }}_lift(_ buf: RustBuffer) throws -> {{ type_name }} {
+ return try {{ ffi_converter_name }}.lift(buf)
+}
+
+public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuffer {
+ return {{ ffi_converter_name }}.lower(value)
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift
new file mode 100644
index 0000000000..2f737b6635
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift
@@ -0,0 +1,183 @@
+fileprivate extension RustBuffer {
+ // Allocate a new buffer, copying the contents of a `UInt8` array.
+ init(bytes: [UInt8]) {
+ let rbuf = bytes.withUnsafeBufferPointer { ptr in
+ RustBuffer.from(ptr)
+ }
+ self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data)
+ }
+
+ static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer {
+ try! rustCall { {{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignBytes(bufferPointer: ptr), $0) }
+ }
+
+ // Frees the buffer in place.
+ // The buffer must not be used after this is called.
+ func deallocate() {
+ try! rustCall { {{ ci.ffi_rustbuffer_free().name() }}(self, $0) }
+ }
+}
+
+fileprivate extension ForeignBytes {
+ init(bufferPointer: UnsafeBufferPointer<UInt8>) {
+ self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress)
+ }
+}
+
+// For every type used in the interface, we provide helper methods for conveniently
+// lifting and lowering that type from C-compatible data, and for reading and writing
+// values of that type in a buffer.
+
+// Helper classes/extensions that don't change.
+// Someday, this will be in a library of its own.
+
+fileprivate extension Data {
+ init(rustBuffer: RustBuffer) {
+ // TODO: This copies the buffer. Can we read directly from a
+ // Rust buffer?
+ self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len))
+ }
+}
+
+// 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.
+fileprivate func readInt<T: FixedWidthInteger>(_ reader: inout (data: Data, offset: Data.Index)) throws -> T {
+ let range = reader.offset..<reader.offset + MemoryLayout<T>.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
+fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array<UInt8> {
+ 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.
+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.
+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.
+fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool {
+ return reader.offset < reader.data.count
+}
+
+// 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.
+
+fileprivate func createWriter() -> [UInt8] {
+ return []
+}
+
+fileprivate func writeBytes<S>(_ 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!
+fileprivate func writeInt<T: FixedWidthInteger>(_ writer: inout [UInt8], _ value: T) {
+ var value = value.bigEndian
+ withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) }
+}
+
+fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) {
+ writeInt(&writer, 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
+// analogous go the Rust trait of the same name.
+fileprivate protocol FfiConverter {
+ associatedtype FfiType
+ associatedtype SwiftType
+
+ static func lift(_ value: FfiType) throws -> SwiftType
+ static func lower(_ value: SwiftType) -> FfiType
+ 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.
+fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { }
+
+extension FfiConverterPrimitive {
+ public static func lift(_ value: FfiType) throws -> SwiftType {
+ return value
+ }
+
+ public static func lower(_ value: SwiftType) -> FfiType {
+ return value
+ }
+}
+
+// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`.
+// Used for complex types where it's hard to write a custom lift/lower.
+fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {}
+
+extension FfiConverterRustBuffer {
+ public static func lift(_ buf: RustBuffer) throws -> SwiftType {
+ var reader = createReader(data: Data(rustBuffer: buf))
+ let value = try read(from: &reader)
+ if hasRemaining(reader) {
+ throw UniffiInternalError.incompleteData
+ }
+ buf.deallocate()
+ return value
+ }
+
+ public static func lower(_ value: SwiftType) -> RustBuffer {
+ var writer = createWriter()
+ write(value, into: &writer)
+ return RustBuffer(bytes: writer)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift
new file mode 100644
index 0000000000..bf664f6411
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift
@@ -0,0 +1,21 @@
+fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
+ typealias SwiftType = {{ type_name }}
+
+ public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
+ let len = Int32(value.count)
+ writeInt(&buf, len)
+ for item in value {
+ {{ inner_type|write_fn }}(item, into: &buf)
+ }
+ }
+
+ 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))
+ for _ in 0 ..< len {
+ seq.append(try {{ inner_type|read_fn }}(from: &buf))
+ }
+ return seq
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift
new file mode 100644
index 0000000000..b7d3466bdd
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift
@@ -0,0 +1,37 @@
+fileprivate struct FfiConverterString: FfiConverter {
+ typealias SwiftType = String
+ typealias FfiType = RustBuffer
+
+ public static func lift(_ value: RustBuffer) throws -> String {
+ defer {
+ value.deallocate()
+ }
+ if value.data == nil {
+ return String()
+ }
+ let bytes = UnsafeBufferPointer<UInt8>(start: value.data!, count: Int(value.len))
+ return String(bytes: bytes, encoding: String.Encoding.utf8)!
+ }
+
+ 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
+ // The swift string gives us a trailing null byte, we don't want it.
+ let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1))
+ return RustBuffer.from(buf)
+ }
+ }
+ }
+
+ 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)!
+ }
+
+ 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/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift
new file mode 100644
index 0000000000..3cd472fa0e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift
@@ -0,0 +1,34 @@
+fileprivate struct FfiConverterTimestamp: FfiConverterRustBuffer {
+ typealias SwiftType = 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 {
+ let delta = Double(seconds) + (Double(nanoseconds) / 1.0e9)
+ return Date.init(timeIntervalSince1970: delta)
+ } else {
+ let delta = Double(seconds) - (Double(nanoseconds) / 1.0e9)
+ return Date.init(timeIntervalSince1970: delta)
+ }
+ }
+
+ public static func write(_ value: Date, into buf: inout [UInt8]) {
+ var delta = value.timeIntervalSince1970
+ var sign: Int64 = 1
+ if delta < 0 {
+ // The nanoseconds portion of the epoch offset must always be
+ // positive, to simplify the calculation we will use the absolute
+ // value of the offset.
+ sign = -1
+ delta = -delta
+ }
+ if delta.rounded(.down) > Double(Int64.max) {
+ fatalError("Timestamp overflow, exceeds max bounds supported by Uniffi")
+ }
+ let seconds = Int64(delta)
+ let nanoseconds = UInt32((delta - Double(seconds)) * 1.0e9)
+ writeInt(&buf, sign * seconds)
+ writeInt(&buf, nanoseconds)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift
new file mode 100644
index 0000000000..5ce762d2a0
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift
@@ -0,0 +1,15 @@
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+
+public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) {% call swift::throws(func) %} -> {{ return_type|type_name }} {
+ return {% call swift::try(func) %} {{ return_type|lift_fn }}(
+ {% call swift::to_ffi_call(func) %}
+ )
+}
+
+{% when None %}
+
+public func {{ func.name()|fn_name }}({% call swift::arg_list_decl(func) %}) {% call swift::throws(func) %} {
+ {% call swift::to_ffi_call(func) %}
+}
+{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift
new file mode 100644
index 0000000000..6e37e1b7f3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift
@@ -0,0 +1,90 @@
+{%- import "macros.swift" as swift %}
+{%- for type_ in ci.iter_types() %}
+{%- let type_name = type_|type_name %}
+{%- let ffi_converter_name = type_|ffi_converter_name %}
+{%- let canonical_type_name = type_|canonical_name %}
+{%- let contains_object_references = ci.item_contains_object_references(type_) %}
+
+{#
+ # Map `Type` instances to an include statement for that type.
+ #
+ # There is a companion match in `KotlinCodeOracle::create_code_type()` which performs a similar function for the
+ # Rust code.
+ #
+ # - When adding additional types here, make sure to also add a match arm to that function.
+ # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ #}
+{%- match type_ %}
+
+{%- when Type::Boolean %}
+{%- include "BooleanHelper.swift" %}
+
+{%- when Type::String %}
+{%- include "StringHelper.swift" %}
+
+{%- when Type::Int8 %}
+{%- include "Int8Helper.swift" %}
+
+{%- when Type::Int16 %}
+{%- include "Int16Helper.swift" %}
+
+{%- when Type::Int32 %}
+{%- include "Int32Helper.swift" %}
+
+{%- when Type::Int64 %}
+{%- include "Int64Helper.swift" %}
+
+{%- when Type::UInt8 %}
+{%- include "UInt8Helper.swift" %}
+
+{%- when Type::UInt16 %}
+{%- include "UInt16Helper.swift" %}
+
+{%- when Type::UInt32 %}
+{%- include "UInt32Helper.swift" %}
+
+{%- when Type::UInt64 %}
+{%- include "UInt64Helper.swift" %}
+
+{%- when Type::Float32 %}
+{%- include "Float32Helper.swift" %}
+
+{%- when Type::Float64 %}
+{%- include "Float64Helper.swift" %}
+
+{%- when Type::Timestamp %}
+{%- include "TimestampHelper.swift" %}
+
+{%- when Type::Duration %}
+{%- include "DurationHelper.swift" %}
+
+{%- when Type::CallbackInterface(name) %}
+{%- include "CallbackInterfaceTemplate.swift" %}
+
+{%- when Type::Custom { name, builtin } %}
+{%- include "CustomType.swift" %}
+
+{%- when Type::Enum(name) %}
+{%- include "EnumTemplate.swift" %}
+
+{%- when Type::Error(name) %}
+{%- include "ErrorTemplate.swift" %}
+
+{%- when Type::Object(name) %}
+{%- include "ObjectTemplate.swift" %}
+
+{%- when Type::Record(name) %}
+{%- include "RecordTemplate.swift" %}
+
+{%- when Type::Optional(inner_type) %}
+{%- include "OptionalTemplate.swift" %}
+
+{%- when Type::Sequence(inner_type) %}
+{%- include "SequenceTemplate.swift" %}
+
+{%- when Type::Map(key_type, value_type) %}
+{%- include "MapTemplate.swift" %}
+
+{%- else %}
+{%- endmatch %}
+{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift
new file mode 100644
index 0000000000..b7fc0942a5
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterUInt16: FfiConverterPrimitive {
+ typealias FfiType = UInt16
+ typealias SwiftType = UInt16
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt16 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift
new file mode 100644
index 0000000000..e7a64aab93
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterUInt32: FfiConverterPrimitive {
+ typealias FfiType = UInt32
+ typealias SwiftType = UInt32
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift
new file mode 100644
index 0000000000..eb674a2c53
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterUInt64: FfiConverterPrimitive {
+ typealias FfiType = UInt64
+ typealias SwiftType = UInt64
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt64 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift
new file mode 100644
index 0000000000..4baf613494
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterUInt8: FfiConverterPrimitive {
+ typealias FfiType = UInt8
+ typealias SwiftType = UInt8
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: UInt8, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift
new file mode 100644
index 0000000000..dcc0a2df74
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift
@@ -0,0 +1,97 @@
+{#
+// Template to call into rust. Used in several places.
+// Variable names in `arg_list_decl` should match up with arg lists
+// passed to rust via `_arg_list_ffi_call`
+#}
+
+{%- macro to_ffi_call(func) -%}
+{% call try(func) %}
+ {% match func.throws_type() %}
+ {% when Some with (e) %}
+ rustCallWithError({{ e|ffi_converter_name }}.self) {
+ {% else %}
+ rustCall() {
+ {% endmatch %}
+ {{ func.ffi_func().name() }}({% call _arg_list_ffi_call(func) -%}{% if func.arguments().len() > 0 %}, {% endif %}$0)
+}
+{%- endmacro -%}
+
+{%- macro to_ffi_call_with_prefix(prefix, func) -%}
+{% call try(func) %}
+ {%- match func.throws_type() %}
+ {%- when Some with (e) %}
+ rustCallWithError({{ e|ffi_converter_name }}.self) {
+ {%- else %}
+ rustCall() {
+ {% endmatch %}
+ {{ func.ffi_func().name() }}(
+ {{- prefix }}, {% call _arg_list_ffi_call(func) -%}{% if func.arguments().len() > 0 %}, {% endif %}$0
+ )
+}
+{%- endmacro %}
+
+{%- macro _arg_list_ffi_call(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg|lower_fn }}({{ arg.name()|var_name }})
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in Swift declarations of methods, functions and constructors.
+// Note the var_name and type_name filters.
+-#}
+
+{% macro arg_list_decl(func) %}
+ {%- for arg in func.arguments() -%}
+ {% if config.omit_argument_labels() %}_ {% endif %}{{ arg.name()|var_name }}: {{ arg|type_name -}}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|literal_swift(arg) }}
+ {%- else %}
+ {%- endmatch %}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+{#-
+// Field lists as used in Swift declarations of Records and Enums.
+// Note the var_name and type_name filters.
+-#}
+{% macro field_list_decl(item) %}
+ {%- for field in item.fields() -%}
+ {{ field.name()|var_name }}: {{ field|type_name -}}
+ {%- match field.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|literal_swift(field) }}
+ {%- else %}
+ {%- endmatch -%}
+ {% if !loop.last %}, {% endif %}
+ {%- endfor %}
+{%- endmacro %}
+
+
+{% macro arg_list_protocol(func) %}
+ {%- for arg in func.arguments() -%}
+ {% if config.omit_argument_labels() %}_ {% endif %}{{ arg.name()|var_name }}: {{ arg|type_name -}}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+
+{#-
+// Arglist as used in the _UniFFILib function declarations.
+// Note unfiltered name but ffi_type_name filters.
+-#}
+{%- macro arg_list_ffi_decl(func) %}
+ {%- for arg in func.arguments() %}
+ {{- arg.type_().borrow()|ffi_type_name }} {{ arg.name() -}},
+ {%- endfor %}
+ RustCallStatus *_Nonnull out_status
+{%- endmacro -%}
+
+{%- macro throws(func) %}
+{%- if func.throws() %}throws{% endif %}
+{%- endmacro -%}
+
+{%- macro try(func) %}
+{%- if func.throws() %}try{% else %}try!{% endif %}
+{%- endmacro -%}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift
new file mode 100644
index 0000000000..ac0557912e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift
@@ -0,0 +1,40 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+{%- import "macros.swift" as swift %}
+import Foundation
+{%- for imported_class in self.imports() %}
+import {{ imported_class }}
+{%- endfor %}
+
+// Depending on the consumer's build setup, the low-level FFI code
+// might be in a separate module, or it might be compiled inline into
+// this module. This is a bit of light hackery to work with both.
+#if canImport({{ config.ffi_module_name() }})
+import {{ config.ffi_module_name() }}
+#endif
+
+{% include "RustBufferTemplate.swift" %}
+{% include "Helpers.swift" %}
+
+// Public interface members begin here.
+{{ type_helper_code }}
+
+{%- for func in ci.function_definitions() %}
+{%- include "TopLevelFunctionTemplate.swift" %}
+{%- endfor %}
+
+/**
+ * Top level initializers and tear down methods.
+ *
+ * This is generated by uniffi.
+ */
+public enum {{ config.module_name().borrow()|class_name }}Lifecycle {
+ /**
+ * Initialize the FFI and Rust library. This should be only called once per application.
+ */
+ func initialize() {
+ {%- for initialization_fn in self.initialization_fns() %}
+ {{ initialization_fn }}()
+ {%- endfor %}
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs
new file mode 100644
index 0000000000..e51c4a9c05
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs
@@ -0,0 +1,225 @@
+/* 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 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::{CompileSource, UniFFITestHelper};
+
+/// Run Swift tests for a UniFFI test fixture
+pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
+ let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let test_helper = UniFFITestHelper::new(fixture_name).context("UniFFITestHelper::new")?;
+ let out_dir = test_helper
+ .create_out_dir(tmp_dir, &script_path)
+ .context("create_out_dir()")?;
+ test_helper
+ .copy_cdylibs_to_out_dir(&out_dir)
+ .context("copy_fixture_library_to_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 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
+
+ let mut command = Command::new("swift");
+ command
+ .current_dir(&out_dir)
+ .arg("-I")
+ .arg(&out_dir)
+ .arg("-L")
+ .arg(&out_dir)
+ .args(calc_library_args(&out_dir)?)
+ .arg("-Xcc")
+ .arg(format!(
+ "-fmodule-map-file={}",
+ generated_sources.module_map
+ ))
+ .arg(&script_path);
+ let status = command
+ .spawn()
+ .context("Failed to spawn `swiftc` when running test script")?
+ .wait()
+ .context("Failed to wait for `swiftc` when running test script")?;
+ if !status.success() {
+ bail!("running `swift` to run test script failed ({:?})", command)
+ }
+ Ok(())
+}
+
+fn calc_module_name(filename: &str) -> String {
+ filename.strip_suffix(".swift").unwrap().to_snake_case()
+}
+
+fn compile_swift_module<T: AsRef<OsStr>>(
+ out_dir: &Utf8Path,
+ module_name: &str,
+ sources: impl IntoIterator<Item = T>,
+ 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 {
+ generated_swift_files: Vec<Utf8PathBuf>,
+ module_map: Utf8PathBuf,
+ main_source_filename: String,
+}
+
+impl GeneratedSources {
+ fn new(
+ library_path: &Utf8Path,
+ out_dir: &Utf8Path,
+ test_helper: &UniFFITestHelper,
+ ) -> Result<Self> {
+ // 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(),
+ vec!["swift"],
+ Some(out_dir),
+ Some(library_path),
+ false,
+ )?;
+ }
+
+ let generated_module_maps = glob(&out_dir.join("*.modulemap"))?;
+
+ Ok(GeneratedSources {
+ 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::<Result<Vec<String>>>()?
+ .join("\n")
+ )?;
+ path
+ }
+ },
+ })
+ }
+
+ 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,
+ )
+ }
+}
+
+// Wraps glob to use Utf8Paths and flattens errors
+fn glob(globspec: &Utf8Path) -> Result<Vec<Utf8PathBuf>> {
+ glob::glob(globspec.as_str())?
+ .map(|globresult| Ok(Utf8PathBuf::try_from(globresult?)?))
+ .collect()
+}
+
+fn calc_library_args(out_dir: &Utf8Path) -> Result<Vec<String>> {
+ 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/third_party/rust/uniffi_bindgen/src/interface/attributes.rs b/third_party/rust/uniffi_bindgen/src/interface/attributes.rs
new file mode 100644
index 0000000000..4df6042564
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/attributes.rs
@@ -0,0 +1,759 @@
+/* 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/. */
+
+//! # Attribute definitions for a `ComponentInterface`.
+//!
+//! This module provides some conveniences for working with attribute definitions
+//! from WebIDL. When encountering a weedle `ExtendedAttribute` node, use `TryFrom`
+//! to convert it into an [`Attribute`] representing one of the attributes that we
+//! support. You can also use the [`parse_attributes`] function to parse an
+//! `ExtendedAttributeList` into a vec of same.
+//!
+//! We only support a small number of attributes, so it's manageable to have them
+//! all handled by a single abstraction. This might need to be refactored in future
+//! if we grow significantly more complicated attribute handling.
+
+use anyhow::{bail, Result};
+use uniffi_meta::Checksum;
+
+/// Represents an attribute parsed from UDL, like `[ByRef]` or `[Throws]`.
+///
+/// This is a convenience enum for parsing UDL attributes and erroring out if we encounter
+/// any unsupported ones. These don't convert directly into parts of a `ComponentInterface`, but
+/// may influence the properties of things like functions and arguments.
+#[derive(Debug, Clone, Checksum)]
+pub(super) enum Attribute {
+ ByRef,
+ Enum,
+ Error,
+ Name(String),
+ SelfType(SelfType),
+ Threadsafe, // N.B. the `[Threadsafe]` attribute is deprecated and will be removed
+ Throws(String),
+ // `[External="crate_name"]` - We can `use crate_name::...` for the type.
+ External(String),
+ // Custom type on the scaffolding side
+ Custom,
+}
+
+impl Attribute {
+ pub fn is_error(&self) -> bool {
+ matches!(self, Attribute::Error)
+ }
+ pub fn is_enum(&self) -> bool {
+ matches!(self, Attribute::Enum)
+ }
+}
+
+/// Convert a weedle `ExtendedAttribute` into an `Attribute` for a `ComponentInterface` member,
+/// or error out if the attribute is not supported.
+impl TryFrom<&weedle::attribute::ExtendedAttribute<'_>> for Attribute {
+ type Error = anyhow::Error;
+ fn try_from(
+ weedle_attribute: &weedle::attribute::ExtendedAttribute<'_>,
+ ) -> Result<Self, anyhow::Error> {
+ match weedle_attribute {
+ // Matches plain named attributes like "[ByRef"].
+ weedle::attribute::ExtendedAttribute::NoArgs(attr) => match (attr.0).0 {
+ "ByRef" => Ok(Attribute::ByRef),
+ "Enum" => Ok(Attribute::Enum),
+ "Error" => Ok(Attribute::Error),
+ "Threadsafe" => Ok(Attribute::Threadsafe),
+ "Custom" => Ok(Attribute::Custom),
+ _ => anyhow::bail!("ExtendedAttributeNoArgs not supported: {:?}", (attr.0).0),
+ },
+ // Matches assignment-style attributes like ["Throws=Error"]
+ weedle::attribute::ExtendedAttribute::Ident(identity) => {
+ match identity.lhs_identifier.0 {
+ "Name" => Ok(Attribute::Name(name_from_id_or_string(&identity.rhs))),
+ "Throws" => Ok(Attribute::Throws(name_from_id_or_string(&identity.rhs))),
+ "Self" => Ok(Attribute::SelfType(SelfType::try_from(&identity.rhs)?)),
+ "External" => Ok(Attribute::External(name_from_id_or_string(&identity.rhs))),
+ _ => anyhow::bail!(
+ "Attribute identity Identifier not supported: {:?}",
+ identity.lhs_identifier.0
+ ),
+ }
+ }
+ _ => anyhow::bail!("Attribute not supported: {:?}", weedle_attribute),
+ }
+ }
+}
+
+fn name_from_id_or_string(nm: &weedle::attribute::IdentifierOrString<'_>) -> String {
+ match nm {
+ weedle::attribute::IdentifierOrString::Identifier(identifier) => identifier.0.to_string(),
+ weedle::attribute::IdentifierOrString::String(str_lit) => str_lit.0.to_string(),
+ }
+}
+
+/// Parse a weedle `ExtendedAttributeList` into a list of `Attribute`s,
+/// erroring out on duplicates.
+fn parse_attributes<F>(
+ weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
+ validator: F,
+) -> Result<Vec<Attribute>>
+where
+ F: Fn(&Attribute) -> Result<()>,
+{
+ let attrs = &weedle_attributes.body.list;
+
+ let mut hash_set = std::collections::HashSet::new();
+ for attr in attrs {
+ if !hash_set.insert(attr) {
+ anyhow::bail!("Duplicated ExtendedAttribute: {:?}", attr);
+ }
+ }
+
+ let attrs = attrs
+ .iter()
+ .map(Attribute::try_from)
+ .collect::<Result<Vec<_>, _>>()?;
+
+ for attr in &attrs {
+ validator(attr)?;
+ }
+
+ Ok(attrs)
+}
+
+/// Attributes that can be attached to an `enum` definition in the UDL.
+/// There's only one case here: using `[Error]` to mark an enum as an error class.
+#[derive(Debug, Clone, Checksum, Default)]
+pub(super) struct EnumAttributes(Vec<Attribute>);
+
+impl EnumAttributes {
+ pub fn contains_error_attr(&self) -> bool {
+ self.0.iter().any(|attr| attr.is_error())
+ }
+}
+
+impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for EnumAttributes {
+ type Error = anyhow::Error;
+ fn try_from(
+ weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
+ ) -> Result<Self, Self::Error> {
+ let attrs = parse_attributes(weedle_attributes, |attr| match attr {
+ Attribute::Error => Ok(()),
+ _ => bail!(format!("{attr:?} not supported for enums")),
+ })?;
+ Ok(Self(attrs))
+ }
+}
+
+impl<T: TryInto<EnumAttributes, Error = anyhow::Error>> TryFrom<Option<T>> for EnumAttributes {
+ type Error = anyhow::Error;
+ fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
+ match value {
+ None => Ok(Default::default()),
+ Some(v) => v.try_into(),
+ }
+ }
+}
+
+/// Represents UDL attributes that might appear on a function.
+///
+/// This supports the `[Throws=ErrorName]` attribute for functions that
+/// can produce an error.
+#[derive(Debug, Clone, Checksum, Default)]
+pub(super) struct FunctionAttributes(Vec<Attribute>);
+
+impl FunctionAttributes {
+ pub(super) fn get_throws_err(&self) -> Option<&str> {
+ self.0.iter().find_map(|attr| match attr {
+ // This will hopefully return a helpful compilation error
+ // if the error is not defined.
+ Attribute::Throws(inner) => Some(inner.as_ref()),
+ _ => None,
+ })
+ }
+}
+
+impl FromIterator<Attribute> for FunctionAttributes {
+ fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
+ Self(Vec::from_iter(iter))
+ }
+}
+
+impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for FunctionAttributes {
+ type Error = anyhow::Error;
+ fn try_from(
+ weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
+ ) -> Result<Self, Self::Error> {
+ let attrs = parse_attributes(weedle_attributes, |attr| match attr {
+ Attribute::Throws(_) => Ok(()),
+ _ => bail!(format!("{attr:?} not supported for functions")),
+ })?;
+ Ok(Self(attrs))
+ }
+}
+
+impl<T: TryInto<FunctionAttributes, Error = anyhow::Error>> TryFrom<Option<T>>
+ for FunctionAttributes
+{
+ type Error = anyhow::Error;
+ fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
+ match value {
+ None => Ok(Default::default()),
+ Some(v) => v.try_into(),
+ }
+ }
+}
+
+/// Represents UDL attributes that might appear on a function argument.
+///
+/// This supports the `[ByRef]` attribute for arguments that should be passed
+/// by reference in the generated Rust scaffolding.
+#[derive(Debug, Clone, Checksum, Default)]
+pub(super) struct ArgumentAttributes(Vec<Attribute>);
+
+impl ArgumentAttributes {
+ pub fn by_ref(&self) -> bool {
+ self.0.iter().any(|attr| matches!(attr, Attribute::ByRef))
+ }
+}
+
+impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for ArgumentAttributes {
+ type Error = anyhow::Error;
+ fn try_from(
+ weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
+ ) -> Result<Self, Self::Error> {
+ let attrs = parse_attributes(weedle_attributes, |attr| match attr {
+ Attribute::ByRef => Ok(()),
+ _ => bail!(format!("{attr:?} not supported for arguments")),
+ })?;
+ Ok(Self(attrs))
+ }
+}
+
+impl<T: TryInto<ArgumentAttributes, Error = anyhow::Error>> TryFrom<Option<T>>
+ for ArgumentAttributes
+{
+ type Error = anyhow::Error;
+ fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
+ match value {
+ None => Ok(Default::default()),
+ Some(v) => v.try_into(),
+ }
+ }
+}
+
+/// Represents UDL attributes that might appear on an `interface` definition.
+#[derive(Debug, Clone, Checksum, Default)]
+pub(super) struct InterfaceAttributes(Vec<Attribute>);
+
+impl InterfaceAttributes {
+ pub fn contains_enum_attr(&self) -> bool {
+ self.0.iter().any(|attr| attr.is_enum())
+ }
+
+ pub fn contains_error_attr(&self) -> bool {
+ self.0.iter().any(|attr| attr.is_error())
+ }
+
+ pub fn threadsafe(&self) -> bool {
+ self.0
+ .iter()
+ .any(|attr| matches!(attr, Attribute::Threadsafe))
+ }
+}
+
+impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for InterfaceAttributes {
+ type Error = anyhow::Error;
+ fn try_from(
+ weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
+ ) -> Result<Self, Self::Error> {
+ let attrs = parse_attributes(weedle_attributes, |attr| match attr {
+ Attribute::Enum => Ok(()),
+ Attribute::Error => Ok(()),
+ Attribute::Threadsafe => Ok(()),
+ _ => bail!(format!("{attr:?} not supported for interface definition")),
+ })?;
+ // Can't be both `[Threadsafe]` and an `[Enum]`.
+ if attrs.len() > 1 {
+ bail!("conflicting attributes on interface definition");
+ }
+ Ok(Self(attrs))
+ }
+}
+
+impl<T: TryInto<InterfaceAttributes, Error = anyhow::Error>> TryFrom<Option<T>>
+ for InterfaceAttributes
+{
+ type Error = anyhow::Error;
+ fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
+ match value {
+ None => Ok(Default::default()),
+ Some(v) => v.try_into(),
+ }
+ }
+}
+
+/// Represents UDL attributes that might appear on a constructor.
+///
+/// This supports the `[Throws=ErrorName]` attribute for constructors that can produce
+/// an error, and the `[Name=MethodName]` for non-default constructors.
+#[derive(Debug, Clone, Checksum, Default)]
+pub(super) struct ConstructorAttributes(Vec<Attribute>);
+
+impl ConstructorAttributes {
+ pub(super) fn get_throws_err(&self) -> Option<&str> {
+ self.0.iter().find_map(|attr| match attr {
+ // This will hopefully return a helpful compilation error
+ // if the error is not defined.
+ Attribute::Throws(inner) => Some(inner.as_ref()),
+ _ => None,
+ })
+ }
+
+ pub(super) fn get_name(&self) -> Option<&str> {
+ self.0.iter().find_map(|attr| match attr {
+ Attribute::Name(inner) => Some(inner.as_ref()),
+ _ => None,
+ })
+ }
+}
+
+impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for ConstructorAttributes {
+ type Error = anyhow::Error;
+ fn try_from(
+ weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
+ ) -> Result<Self, Self::Error> {
+ let attrs = parse_attributes(weedle_attributes, |attr| match attr {
+ Attribute::Throws(_) => Ok(()),
+ Attribute::Name(_) => Ok(()),
+ _ => bail!(format!("{attr:?} not supported for constructors")),
+ })?;
+ Ok(Self(attrs))
+ }
+}
+
+/// Represents UDL attributes that might appear on a method.
+///
+/// This supports the `[Throws=ErrorName]` attribute for methods that can produce
+/// an error, and the `[Self=ByArc]` attribute for methods that take `Arc<Self>` as receiver.
+#[derive(Debug, Clone, Checksum, Default)]
+pub(super) struct MethodAttributes(Vec<Attribute>);
+
+impl MethodAttributes {
+ pub(super) fn get_throws_err(&self) -> Option<&str> {
+ self.0.iter().find_map(|attr| match attr {
+ // This will hopefully return a helpful compilation error
+ // if the error is not defined.
+ Attribute::Throws(inner) => Some(inner.as_ref()),
+ _ => None,
+ })
+ }
+
+ pub(super) fn get_self_by_arc(&self) -> bool {
+ self.0
+ .iter()
+ .any(|attr| matches!(attr, Attribute::SelfType(SelfType::ByArc)))
+ }
+}
+
+impl FromIterator<Attribute> for MethodAttributes {
+ fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
+ Self(Vec::from_iter(iter))
+ }
+}
+
+impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for MethodAttributes {
+ type Error = anyhow::Error;
+ fn try_from(
+ weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
+ ) -> Result<Self, Self::Error> {
+ let attrs = parse_attributes(weedle_attributes, |attr| match attr {
+ Attribute::SelfType(_) => Ok(()),
+ Attribute::Throws(_) => Ok(()),
+ _ => bail!(format!("{attr:?} not supported for methods")),
+ })?;
+ Ok(Self(attrs))
+ }
+}
+
+impl<T: TryInto<MethodAttributes, Error = anyhow::Error>> TryFrom<Option<T>> for MethodAttributes {
+ type Error = anyhow::Error;
+ fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
+ match value {
+ None => Ok(Default::default()),
+ Some(v) => v.try_into(),
+ }
+ }
+}
+
+/// Represents the different possible types of method call receiver.
+///
+/// Actually we only support one of these right now, `[Self=ByArc]`.
+/// We might add more in future, e.g. a `[Self=ByRef]` if there are cases
+/// where we need to force the receiver to be taken by reference.
+#[derive(Debug, Clone, Checksum)]
+pub(super) enum SelfType {
+ ByArc, // Method receiver is `Arc<Self>`.
+}
+
+impl TryFrom<&weedle::attribute::IdentifierOrString<'_>> for SelfType {
+ type Error = anyhow::Error;
+ fn try_from(nm: &weedle::attribute::IdentifierOrString<'_>) -> Result<Self, Self::Error> {
+ Ok(match nm {
+ weedle::attribute::IdentifierOrString::Identifier(identifier) => match identifier.0 {
+ "ByArc" => SelfType::ByArc,
+ _ => bail!("Unsupported Self Type: {:?}", identifier.0),
+ },
+ weedle::attribute::IdentifierOrString::String(_) => {
+ bail!("Unsupported Self Type: {:?}", nm)
+ }
+ })
+ }
+}
+
+/// Represents UDL attributes that might appear on a typedef
+///
+/// This supports the `[External="crate_name"]` and `[Custom]` attributes for types.
+#[derive(Debug, Clone, Checksum, Default)]
+pub(super) struct TypedefAttributes(Vec<Attribute>);
+
+impl TypedefAttributes {
+ pub(super) fn get_crate_name(&self) -> String {
+ self.0
+ .iter()
+ .find_map(|attr| match attr {
+ Attribute::External(crate_name) => Some(crate_name.clone()),
+ _ => None,
+ })
+ .expect("must have a crate name")
+ }
+
+ pub(super) fn is_custom(&self) -> bool {
+ self.0
+ .iter()
+ .any(|attr| matches!(attr, Attribute::Custom { .. }))
+ }
+}
+
+impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for TypedefAttributes {
+ type Error = anyhow::Error;
+ fn try_from(
+ weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
+ ) -> Result<Self, Self::Error> {
+ let attrs = parse_attributes(weedle_attributes, |attr| match attr {
+ Attribute::External { .. } | Attribute::Custom => Ok(()),
+ _ => bail!(format!("{attr:?} not supported for typedefs")),
+ })?;
+ Ok(Self(attrs))
+ }
+}
+
+impl<T: TryInto<TypedefAttributes, Error = anyhow::Error>> TryFrom<Option<T>>
+ for TypedefAttributes
+{
+ type Error = anyhow::Error;
+ fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
+ match value {
+ None => Ok(Default::default()),
+ Some(v) => v.try_into(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use weedle::Parse;
+
+ #[test]
+ fn test_byref() -> Result<()> {
+ let (_, node) = weedle::attribute::ExtendedAttribute::parse("ByRef").unwrap();
+ let attr = Attribute::try_from(&node)?;
+ assert!(matches!(attr, Attribute::ByRef));
+ Ok(())
+ }
+
+ #[test]
+ fn test_enum() -> Result<()> {
+ let (_, node) = weedle::attribute::ExtendedAttribute::parse("Enum").unwrap();
+ let attr = Attribute::try_from(&node)?;
+ assert!(matches!(attr, Attribute::Enum));
+ assert!(attr.is_enum());
+ Ok(())
+ }
+
+ #[test]
+ fn test_error() -> Result<()> {
+ let (_, node) = weedle::attribute::ExtendedAttribute::parse("Error").unwrap();
+ let attr = Attribute::try_from(&node)?;
+ assert!(matches!(attr, Attribute::Error));
+ assert!(attr.is_error());
+ Ok(())
+ }
+
+ #[test]
+ fn test_name() -> Result<()> {
+ let (_, node) = weedle::attribute::ExtendedAttribute::parse("Name=Value").unwrap();
+ let attr = Attribute::try_from(&node)?;
+ assert!(matches!(attr, Attribute::Name(nm) if nm == "Value"));
+
+ let (_, node) = weedle::attribute::ExtendedAttribute::parse("Name").unwrap();
+ let err = Attribute::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "ExtendedAttributeNoArgs not supported: \"Name\""
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_selftype() -> Result<()> {
+ let (_, node) = weedle::attribute::ExtendedAttribute::parse("Self=ByArc").unwrap();
+ let attr = Attribute::try_from(&node)?;
+ assert!(matches!(attr, Attribute::SelfType(SelfType::ByArc)));
+ let (_, node) = weedle::attribute::ExtendedAttribute::parse("Self=ByMistake").unwrap();
+ let err = Attribute::try_from(&node).unwrap_err();
+ assert_eq!(err.to_string(), "Unsupported Self Type: \"ByMistake\"");
+ Ok(())
+ }
+
+ #[test]
+ fn test_threadsafe() -> Result<()> {
+ let (_, node) = weedle::attribute::ExtendedAttribute::parse("Threadsafe").unwrap();
+ let attr = Attribute::try_from(&node)?;
+ assert!(matches!(attr, Attribute::Threadsafe));
+ Ok(())
+ }
+
+ #[test]
+ fn test_throws() -> Result<()> {
+ let (_, node) = weedle::attribute::ExtendedAttribute::parse("Throws=Name").unwrap();
+ let attr = Attribute::try_from(&node)?;
+ assert!(matches!(attr, Attribute::Throws(nm) if nm == "Name"));
+
+ let (_, node) = weedle::attribute::ExtendedAttribute::parse("Throws").unwrap();
+ let err = Attribute::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "ExtendedAttributeNoArgs not supported: \"Throws\""
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_unsupported() {
+ let (_, node) =
+ weedle::attribute::ExtendedAttribute::parse("UnsupportedAttribute").unwrap();
+ let err = Attribute::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "ExtendedAttributeNoArgs not supported: \"UnsupportedAttribute\""
+ );
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttribute::parse("Unsupported=Attribute").unwrap();
+ let err = Attribute::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "Attribute identity Identifier not supported: \"Unsupported\""
+ );
+ }
+
+ #[test]
+ fn test_other_attributes_not_supported_for_enums() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Error, ByRef]").unwrap();
+ let err = EnumAttributes::try_from(&node).unwrap_err();
+ assert_eq!(err.to_string(), "ByRef not supported for enums");
+ }
+
+ #[test]
+ fn test_throws_attribute() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Throws=Error]").unwrap();
+ let attrs = FunctionAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.get_throws_err(), Some("Error")));
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
+ let attrs = FunctionAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.get_throws_err(), None));
+ }
+
+ #[test]
+ fn test_other_attributes_not_supported_for_functions() {
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, ByRef]").unwrap();
+ let err = FunctionAttributes::try_from(&node).unwrap_err();
+ assert_eq!(err.to_string(), "ByRef not supported for functions");
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, Self=ByArc]").unwrap();
+ let err = FunctionAttributes::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "SelfType(ByArc) not supported for functions"
+ );
+ }
+
+ #[test]
+ fn test_method_attributes() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Throws=Error]").unwrap();
+ let attrs = MethodAttributes::try_from(&node).unwrap();
+ assert!(!attrs.get_self_by_arc());
+ assert!(matches!(attrs.get_throws_err(), Some("Error")));
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
+ let attrs = MethodAttributes::try_from(&node).unwrap();
+ assert!(!attrs.get_self_by_arc());
+ assert!(attrs.get_throws_err().is_none());
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc, Throws=Error]").unwrap();
+ let attrs = MethodAttributes::try_from(&node).unwrap();
+ assert!(attrs.get_self_by_arc());
+ assert!(attrs.get_throws_err().is_some());
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc]").unwrap();
+ let attrs = MethodAttributes::try_from(&node).unwrap();
+ assert!(attrs.get_self_by_arc());
+ assert!(attrs.get_throws_err().is_none());
+ }
+
+ #[test]
+ fn test_constructor_attributes() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Throws=Error]").unwrap();
+ let attrs = ConstructorAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.get_throws_err(), Some("Error")));
+ assert!(matches!(attrs.get_name(), None));
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Name=MyFactory]").unwrap();
+ let attrs = ConstructorAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.get_throws_err(), None));
+ assert!(matches!(attrs.get_name(), Some("MyFactory")));
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, Name=MyFactory]")
+ .unwrap();
+ let attrs = ConstructorAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.get_throws_err(), Some("Error")));
+ assert!(matches!(attrs.get_name(), Some("MyFactory")));
+ }
+
+ #[test]
+ fn test_other_attributes_not_supported_for_constructors() {
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, ByRef]").unwrap();
+ let err = ConstructorAttributes::try_from(&node).unwrap_err();
+ assert_eq!(err.to_string(), "ByRef not supported for constructors");
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, Self=ByArc]").unwrap();
+ let err = ConstructorAttributes::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "SelfType(ByArc) not supported for constructors"
+ );
+ }
+
+ #[test]
+ fn test_byref_attribute() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[ByRef]").unwrap();
+ let attrs = ArgumentAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.by_ref(), true));
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
+ let attrs = ArgumentAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.by_ref(), false));
+ }
+
+ #[test]
+ fn test_other_attributes_not_supported_for_arguments() {
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, ByRef]").unwrap();
+ let err = ArgumentAttributes::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "Throws(\"Error\") not supported for arguments"
+ );
+ }
+
+ #[test]
+ fn test_threadsafe_attribute() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Threadsafe]").unwrap();
+ let attrs = InterfaceAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.threadsafe(), true));
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
+ let attrs = InterfaceAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.threadsafe(), false));
+ }
+
+ #[test]
+ fn test_enum_attribute() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Enum]").unwrap();
+ let attrs = InterfaceAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.contains_enum_attr(), true));
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
+ let attrs = InterfaceAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.contains_enum_attr(), false));
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Threadsafe]").unwrap();
+ let attrs = InterfaceAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.contains_enum_attr(), false));
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Threadsafe, Enum]").unwrap();
+ let err = InterfaceAttributes::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "conflicting attributes on interface definition"
+ );
+ }
+
+ #[test]
+ fn test_other_attributes_not_supported_for_interfaces() {
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Threadsafe, ByRef]").unwrap();
+ let err = InterfaceAttributes::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "ByRef not supported for interface definition"
+ );
+ }
+
+ #[test]
+ fn test_typedef_attribute() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Custom]").unwrap();
+ let attrs = TypedefAttributes::try_from(&node).unwrap();
+ assert!(attrs.is_custom());
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[External=crate_name]").unwrap();
+ let attrs = TypedefAttributes::try_from(&node).unwrap();
+ assert!(!attrs.is_custom());
+ assert_eq!(attrs.get_crate_name(), "crate_name");
+ }
+
+ #[test]
+ fn test_typedef_attributes_malformed() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Custom=foo]").unwrap();
+ let err = TypedefAttributes::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "Attribute identity Identifier not supported: \"Custom\""
+ );
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[External]").unwrap();
+ let err = TypedefAttributes::try_from(&node).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "ExtendedAttributeNoArgs not supported: \"External\""
+ );
+ }
+
+ #[test]
+ fn test_other_attributes_not_supported_for_typedef() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[ByRef]").unwrap();
+ let err = TypedefAttributes::try_from(&node).unwrap_err();
+ assert_eq!(err.to_string(), "ByRef not supported for typedefs");
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs
new file mode 100644
index 0000000000..a7164cec5b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs
@@ -0,0 +1,169 @@
+/* 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/. */
+
+//! # Callback Interface definitions for a `ComponentInterface`.
+//!
+//! This module converts callback interface definitions from UDL into structures that
+//! can be added to a `ComponentInterface`. A declaration in the UDL like this:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! callback interface Example {
+//! string hello();
+//! };
+//! # "##)?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Will result in a [`CallbackInterface`] member being added to the resulting
+//! [`ComponentInterface`]:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! # callback interface Example {
+//! # string hello();
+//! # };
+//! # "##)?;
+//! let callback = ci.get_callback_interface_definition("Example").unwrap();
+//! assert_eq!(callback.name(), "Example");
+//! assert_eq!(callback.methods()[0].name(), "hello");
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+
+use anyhow::{bail, Result};
+use uniffi_meta::Checksum;
+
+use super::ffi::{FfiArgument, FfiFunction, FfiType};
+use super::object::Method;
+use super::types::{Type, TypeIterator};
+use super::{APIConverter, ComponentInterface};
+
+#[derive(Debug, Clone, Checksum)]
+pub struct CallbackInterface {
+ pub(super) name: String,
+ pub(super) methods: Vec<Method>,
+ // We don't include the FFIFunc in the hash calculation, because:
+ // - it is entirely determined by the other fields,
+ // so excluding it is safe.
+ // - its `name` property includes a checksum derived from the very
+ // hash value we're trying to calculate here, so excluding it
+ // avoids a weird circular dependency in the calculation.
+ #[checksum_ignore]
+ pub(super) ffi_init_callback: FfiFunction,
+}
+
+impl CallbackInterface {
+ fn new(name: String) -> CallbackInterface {
+ CallbackInterface {
+ name,
+ methods: Default::default(),
+ ffi_init_callback: Default::default(),
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn type_(&self) -> Type {
+ Type::CallbackInterface(self.name.clone())
+ }
+
+ pub fn methods(&self) -> Vec<&Method> {
+ self.methods.iter().collect()
+ }
+
+ pub fn ffi_init_callback(&self) -> &FfiFunction {
+ &self.ffi_init_callback
+ }
+
+ pub(super) fn derive_ffi_funcs(&mut self, ci_prefix: &str) {
+ self.ffi_init_callback.name = format!("ffi_{ci_prefix}_{}_init_callback", self.name);
+ self.ffi_init_callback.arguments = vec![FfiArgument {
+ name: "callback_stub".to_string(),
+ type_: FfiType::ForeignCallback,
+ }];
+ self.ffi_init_callback.return_type = None;
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ Box::new(self.methods.iter().flat_map(Method::iter_types))
+ }
+}
+
+impl APIConverter<CallbackInterface> for weedle::CallbackInterfaceDefinition<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<CallbackInterface> {
+ if self.attributes.is_some() {
+ bail!("callback interface attributes are not supported yet");
+ }
+ if self.inheritance.is_some() {
+ bail!("callback interface inheritance is not supported");
+ }
+ let mut object = CallbackInterface::new(self.identifier.0.to_string());
+ for member in &self.members.body {
+ match member {
+ weedle::interface::InterfaceMember::Operation(t) => {
+ let mut method: Method = t.convert(ci)?;
+ method.object_name = object.name.clone();
+ object.methods.push(method);
+ }
+ _ => bail!(
+ "no support for callback interface member type {:?} yet",
+ member
+ ),
+ }
+ }
+ Ok(object)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_empty_interface() {
+ const UDL: &str = r#"
+ namespace test{};
+ // Weird, but allowed.
+ callback interface Testing {};
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.callback_interface_definitions().len(), 1);
+ assert_eq!(
+ ci.get_callback_interface_definition("Testing")
+ .unwrap()
+ .methods()
+ .len(),
+ 0
+ );
+ }
+
+ #[test]
+ fn test_multiple_interfaces() {
+ const UDL: &str = r#"
+ namespace test{};
+ callback interface One {
+ void one();
+ };
+ callback interface Two {
+ u32 two();
+ u64 too();
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.callback_interface_definitions().len(), 2);
+
+ let callbacks_one = ci.get_callback_interface_definition("One").unwrap();
+ assert_eq!(callbacks_one.methods().len(), 1);
+ assert_eq!(callbacks_one.methods()[0].name(), "one");
+
+ let callbacks_two = ci.get_callback_interface_definition("Two").unwrap();
+ assert_eq!(callbacks_two.methods().len(), 2);
+ assert_eq!(callbacks_two.methods()[0].name(), "two");
+ assert_eq!(callbacks_two.methods()[1].name(), "too");
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/enum_.rs b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs
new file mode 100644
index 0000000000..c85e6ecc46
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs
@@ -0,0 +1,441 @@
+/* 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/. */
+
+//! # Enum definitions for a `ComponentInterface`.
+//!
+//! This module converts enum definition from UDL into structures that can be
+//! added to a `ComponentInterface`. A declaration in the UDL like this:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! enum Example {
+//! "one",
+//! "two"
+//! };
+//! # "##)?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Will result in a [`Enum`] member being added to the resulting [`ComponentInterface`]:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! # enum Example {
+//! # "one",
+//! # "two"
+//! # };
+//! # "##)?;
+//! let e = ci.get_enum_definition("Example").unwrap();
+//! assert_eq!(e.name(), "Example");
+//! assert_eq!(e.variants().len(), 2);
+//! assert_eq!(e.variants()[0].name(), "one");
+//! assert_eq!(e.variants()[1].name(), "two");
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Like in Rust, UniFFI enums can contain associated data, but this needs to be
+//! declared with a different syntax in order to work within the restrictions of
+//! WebIDL. A declaration like this:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! [Enum]
+//! interface Example {
+//! Zero();
+//! One(u32 first);
+//! Two(u32 first, string second);
+//! };
+//! # "##)?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Will result in an [`Enum`] member whose variants have associated fields:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! # [Enum]
+//! # interface ExampleWithData {
+//! # Zero();
+//! # One(u32 first);
+//! # Two(u32 first, string second);
+//! # };
+//! # "##)?;
+//! let e = ci.get_enum_definition("ExampleWithData").unwrap();
+//! assert_eq!(e.name(), "ExampleWithData");
+//! assert_eq!(e.variants().len(), 3);
+//! assert_eq!(e.variants()[0].name(), "Zero");
+//! assert_eq!(e.variants()[0].fields().len(), 0);
+//! assert_eq!(e.variants()[1].name(), "One");
+//! assert_eq!(e.variants()[1].fields().len(), 1);
+//! assert_eq!(e.variants()[1].fields()[0].name(), "first");
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+
+use anyhow::{bail, Result};
+use uniffi_meta::Checksum;
+
+use super::record::Field;
+use super::types::{Type, TypeIterator};
+use super::{APIConverter, ComponentInterface};
+
+/// Represents an enum with named variants, each of which may have named
+/// and typed fields.
+///
+/// Enums are passed across the FFI by serializing to a bytebuffer, with a
+/// i32 indicating the variant followed by the serialization of each field.
+#[derive(Debug, Clone, PartialEq, Eq, Checksum)]
+pub struct Enum {
+ pub(super) name: String,
+ pub(super) variants: Vec<Variant>,
+ // "Flat" enums do not have variants with associated data.
+ pub(super) flat: bool,
+}
+
+impl Enum {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn type_(&self) -> Type {
+ // *sigh* at the clone here, the relationship between a ComponentInterface
+ // and its contained types could use a bit of a cleanup.
+ Type::Enum(self.name.clone())
+ }
+
+ pub fn variants(&self) -> &[Variant] {
+ &self.variants
+ }
+
+ pub fn is_flat(&self) -> bool {
+ self.flat
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ Box::new(self.variants.iter().flat_map(Variant::iter_types))
+ }
+}
+
+impl From<uniffi_meta::EnumMetadata> for Enum {
+ fn from(meta: uniffi_meta::EnumMetadata) -> Self {
+ let flat = meta.variants.iter().all(|v| v.fields.is_empty());
+ Self {
+ name: meta.name,
+ variants: meta.variants.into_iter().map(Into::into).collect(),
+ flat,
+ }
+ }
+}
+
+// Note that we have two `APIConverter` impls here - one for the `enum` case
+// and one for the `[Enum] interface` case.
+
+impl APIConverter<Enum> for weedle::EnumDefinition<'_> {
+ fn convert(&self, _ci: &mut ComponentInterface) -> Result<Enum> {
+ Ok(Enum {
+ name: self.identifier.0.to_string(),
+ variants: self
+ .values
+ .body
+ .list
+ .iter()
+ .map::<Result<_>, _>(|v| {
+ Ok(Variant {
+ name: v.0.to_string(),
+ ..Default::default()
+ })
+ })
+ .collect::<Result<Vec<_>>>()?,
+ // Enums declared using the `enum` syntax can never have variants with fields.
+ flat: true,
+ })
+ }
+}
+
+impl APIConverter<Enum> for weedle::InterfaceDefinition<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Enum> {
+ if self.inheritance.is_some() {
+ bail!("interface inheritance is not supported for enum interfaces");
+ }
+ // We don't need to check `self.attributes` here; if calling code has dispatched
+ // to this impl then we already know there was an `[Enum]` attribute.
+ Ok(Enum {
+ name: self.identifier.0.to_string(),
+ variants: self
+ .members
+ .body
+ .iter()
+ .map::<Result<Variant>, _>(|member| match member {
+ weedle::interface::InterfaceMember::Operation(t) => Ok(t.convert(ci)?),
+ _ => bail!(
+ "interface member type {:?} not supported in enum interface",
+ member
+ ),
+ })
+ .collect::<Result<Vec<_>>>()?,
+ // Enums declared using the `[Enum] interface` syntax might have variants with fields.
+ flat: false,
+ })
+ }
+}
+
+/// Represents an individual variant in an Enum.
+///
+/// Each variant has a name and zero or more fields.
+#[derive(Debug, Clone, Default, PartialEq, Eq, Checksum)]
+pub struct Variant {
+ pub(super) name: String,
+ pub(super) fields: Vec<Field>,
+}
+
+impl Variant {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn fields(&self) -> &[Field] {
+ &self.fields
+ }
+
+ pub fn has_fields(&self) -> bool {
+ !self.fields.is_empty()
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ Box::new(self.fields.iter().flat_map(Field::iter_types))
+ }
+}
+
+impl From<uniffi_meta::VariantMetadata> for Variant {
+ fn from(meta: uniffi_meta::VariantMetadata) -> Self {
+ Self {
+ name: meta.name,
+ fields: meta.fields.into_iter().map(Into::into).collect(),
+ }
+ }
+}
+
+impl APIConverter<Variant> for weedle::interface::OperationInterfaceMember<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Variant> {
+ if self.special.is_some() {
+ bail!("special operations not supported");
+ }
+ if let Some(weedle::interface::StringifierOrStatic::Stringifier(_)) = self.modifier {
+ bail!("stringifiers are not supported");
+ }
+ // OK, so this is a little weird.
+ // The syntax we use for enum interface members is `Name(type arg, ...);`, which parses
+ // as an anonymous operation where `Name` is the return type. We re-interpret it to
+ // use `Name` as the name of the variant.
+ if self.identifier.is_some() {
+ bail!("enum interface members must not have a method name");
+ }
+ let name: String = {
+ use weedle::types::{
+ NonAnyType::Identifier, ReturnType, SingleType::NonAny, Type::Single,
+ };
+ match &self.return_type {
+ ReturnType::Type(Single(NonAny(Identifier(id)))) => id.type_.0.to_owned(),
+ _ => bail!("enum interface members must have plain identifiers as names"),
+ }
+ };
+ Ok(Variant {
+ name,
+ fields: self
+ .args
+ .body
+ .list
+ .iter()
+ .map(|arg| arg.convert(ci))
+ .collect::<Result<Vec<_>>>()?,
+ })
+ }
+}
+
+impl APIConverter<Field> for weedle::argument::Argument<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Field> {
+ match self {
+ weedle::argument::Argument::Single(t) => t.convert(ci),
+ weedle::argument::Argument::Variadic(_) => bail!("variadic arguments not supported"),
+ }
+ }
+}
+
+impl APIConverter<Field> for weedle::argument::SingleArgument<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Field> {
+ let type_ = ci.resolve_type_expression(&self.type_)?;
+ if let Type::Object(_) = type_ {
+ bail!("Objects cannot currently be used in enum variant data");
+ }
+ if self.default.is_some() {
+ bail!("enum interface variant fields must not have default values");
+ }
+ if self.attributes.is_some() {
+ bail!("enum interface variant fields must not have attributes");
+ }
+ // TODO: maybe we should use our own `Field` type here with just name and type,
+ // rather than appropriating record::Field..?
+ Ok(Field {
+ name: self.identifier.0.to_string(),
+ type_,
+ default: None,
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::super::ffi::FfiType;
+ use super::*;
+
+ #[test]
+ fn test_duplicate_variants() {
+ const UDL: &str = r#"
+ namespace test{};
+ // Weird, but currently allowed!
+ // We should probably disallow this...
+ enum Testing { "one", "two", "one" };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.enum_definitions().count(), 1);
+ assert_eq!(
+ ci.get_enum_definition("Testing").unwrap().variants().len(),
+ 3
+ );
+ }
+
+ #[test]
+ fn test_associated_data() {
+ const UDL: &str = r##"
+ namespace test {
+ void takes_an_enum(TestEnum e);
+ void takes_an_enum_with_data(TestEnumWithData ed);
+ TestEnum returns_an_enum();
+ TestEnumWithData returns_an_enum_with_data();
+ };
+
+ enum TestEnum { "one", "two" };
+
+ [Enum]
+ interface TestEnumWithData {
+ Zero();
+ One(u32 first);
+ Two(u32 first, string second);
+ };
+
+ [Enum]
+ interface TestEnumWithoutData {
+ One();
+ Two();
+ };
+ "##;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.enum_definitions().count(), 3);
+ assert_eq!(ci.function_definitions().len(), 4);
+
+ // The "flat" enum with no associated data.
+ let e = ci.get_enum_definition("TestEnum").unwrap();
+ assert!(e.is_flat());
+ assert_eq!(e.variants().len(), 2);
+ assert_eq!(
+ e.variants().iter().map(|v| v.name()).collect::<Vec<_>>(),
+ vec!["one", "two"]
+ );
+ assert_eq!(e.variants()[0].fields().len(), 0);
+ assert_eq!(e.variants()[1].fields().len(), 0);
+
+ // The enum with associated data.
+ let ed = ci.get_enum_definition("TestEnumWithData").unwrap();
+ assert!(!ed.is_flat());
+ assert_eq!(ed.variants().len(), 3);
+ assert_eq!(
+ ed.variants().iter().map(|v| v.name()).collect::<Vec<_>>(),
+ vec!["Zero", "One", "Two"]
+ );
+ assert_eq!(ed.variants()[0].fields().len(), 0);
+ assert_eq!(
+ ed.variants()[1]
+ .fields()
+ .iter()
+ .map(|f| f.name())
+ .collect::<Vec<_>>(),
+ vec!["first"]
+ );
+ assert_eq!(
+ ed.variants()[1]
+ .fields()
+ .iter()
+ .map(|f| f.type_())
+ .collect::<Vec<_>>(),
+ vec![&Type::UInt32]
+ );
+ assert_eq!(
+ ed.variants()[2]
+ .fields()
+ .iter()
+ .map(|f| f.name())
+ .collect::<Vec<_>>(),
+ vec!["first", "second"]
+ );
+ assert_eq!(
+ ed.variants()[2]
+ .fields()
+ .iter()
+ .map(|f| f.type_())
+ .collect::<Vec<_>>(),
+ vec![&Type::UInt32, &Type::String]
+ );
+
+ // The enum declared via interface, but with no associated data.
+ let ewd = ci.get_enum_definition("TestEnumWithoutData").unwrap();
+ assert!(!ewd.is_flat());
+ assert_eq!(ewd.variants().len(), 2);
+ assert_eq!(
+ ewd.variants().iter().map(|v| v.name()).collect::<Vec<_>>(),
+ vec!["One", "Two"]
+ );
+ assert_eq!(ewd.variants()[0].fields().len(), 0);
+ assert_eq!(ewd.variants()[1].fields().len(), 0);
+
+ // Flat enums pass over the FFI as bytebuffers.
+ // (It might be nice to optimize these to pass as plain integers, but that's
+ // difficult atop the current factoring of `ComponentInterface` and friends).
+ let farg = ci.get_function_definition("takes_an_enum").unwrap();
+ assert_eq!(*farg.arguments()[0].type_(), Type::Enum("TestEnum".into()));
+ assert_eq!(
+ farg.ffi_func().arguments()[0].type_(),
+ FfiType::RustBuffer(None)
+ );
+ let fret = ci.get_function_definition("returns_an_enum").unwrap();
+ assert!(matches!(fret.return_type(), Some(Type::Enum(nm)) if nm == "TestEnum"));
+ assert!(matches!(
+ fret.ffi_func().return_type(),
+ Some(FfiType::RustBuffer(None))
+ ));
+
+ // Enums with associated data pass over the FFI as bytebuffers.
+ let farg = ci
+ .get_function_definition("takes_an_enum_with_data")
+ .unwrap();
+ assert_eq!(
+ *farg.arguments()[0].type_(),
+ Type::Enum("TestEnumWithData".into())
+ );
+ assert_eq!(
+ farg.ffi_func().arguments()[0].type_(),
+ FfiType::RustBuffer(None)
+ );
+ let fret = ci
+ .get_function_definition("returns_an_enum_with_data")
+ .unwrap();
+ assert!(matches!(fret.return_type(), Some(Type::Enum(nm)) if nm == "TestEnumWithData"));
+ assert!(matches!(
+ fret.ffi_func().return_type(),
+ Some(FfiType::RustBuffer(None))
+ ));
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/error.rs b/third_party/rust/uniffi_bindgen/src/interface/error.rs
new file mode 100644
index 0000000000..9aa57255e8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/error.rs
@@ -0,0 +1,230 @@
+/* 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/. */
+
+//! # Error definitions for a `ComponentInterface`.
+//!
+//! This module converts error definition from UDL into structures that can be
+//! added to a `ComponentInterface`. A declaration in the UDL like this:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! [Error]
+//! enum Example {
+//! "one",
+//! "two"
+//! };
+//! # "##)?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Will result in an [`Error`] member with fieldless variants being added to the resulting [`ComponentInterface`]:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! # [Error]
+//! # enum Example {
+//! # "one",
+//! # "two"
+//! # };
+//! # "##)?;
+//! let err = ci.get_error_definition("Example").unwrap();
+//! assert_eq!(err.name(), "Example");
+//! assert_eq!(err.variants().len(), 2);
+//! assert_eq!(err.variants()[0].name(), "one");
+//! assert_eq!(err.variants()[1].name(), "two");
+//! assert_eq!(err.is_flat(), true);
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! A declaration in the UDL like this:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! [Error]
+//! interface Example {
+//! one(i16 code);
+//! two(string reason);
+//! three(i32 x, i32 y);
+//! };
+//! # "##)?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Will result in an [`Error`] member with variants that have fields being added to the resulting [`ComponentInterface`]:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! # [Error]
+//! # interface Example {
+//! # one();
+//! # two(string reason);
+//! # three(i32 x, i32 y);
+//! # };
+//! # "##)?;
+//! let err = ci.get_error_definition("Example").unwrap();
+//! assert_eq!(err.name(), "Example");
+//! assert_eq!(err.variants().len(), 3);
+//! assert_eq!(err.variants()[0].name(), "one");
+//! assert_eq!(err.variants()[1].name(), "two");
+//! assert_eq!(err.variants()[2].name(), "three");
+//! assert_eq!(err.variants()[0].fields().len(), 0);
+//! assert_eq!(err.variants()[1].fields().len(), 1);
+//! assert_eq!(err.variants()[1].fields()[0].name(), "reason");
+//! assert_eq!(err.variants()[2].fields().len(), 2);
+//! assert_eq!(err.variants()[2].fields()[0].name(), "x");
+//! assert_eq!(err.variants()[2].fields()[1].name(), "y");
+//! assert_eq!(err.is_flat(), false);
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+
+use anyhow::Result;
+use uniffi_meta::Checksum;
+
+use super::enum_::{Enum, Variant};
+use super::types::{Type, TypeIterator};
+use super::{APIConverter, ComponentInterface};
+
+/// Represents an Error that might be thrown by functions/methods in the component interface.
+///
+/// Errors are represented in the UDL as enums with the special `[Error]` attribute, but
+/// they're handled in the FFI very differently. We create them in `uniffi::call_with_result()` if
+/// the wrapped function returns an `Err` value
+/// struct and assign an integer error code to each variant.
+#[derive(Debug, Clone, PartialEq, Eq, Checksum)]
+pub struct Error {
+ pub name: String,
+ enum_: Enum,
+}
+
+impl Error {
+ pub fn from_enum(enum_: Enum) -> Self {
+ Self {
+ name: enum_.name.clone(),
+ enum_,
+ }
+ }
+
+ pub fn type_(&self) -> Type {
+ // *sigh* at the clone here, the relationship between a ComponentInterface
+ // and its contained types could use a bit of a cleanup.
+ Type::Error(self.name.clone())
+ }
+
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn wrapped_enum(&self) -> &Enum {
+ &self.enum_
+ }
+
+ pub fn variants(&self) -> &[Variant] {
+ self.enum_.variants()
+ }
+
+ pub fn is_flat(&self) -> bool {
+ self.enum_.is_flat()
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ self.wrapped_enum().iter_types()
+ }
+}
+
+impl From<uniffi_meta::ErrorMetadata> for Error {
+ fn from(meta: uniffi_meta::ErrorMetadata) -> Self {
+ Self {
+ name: meta.name.clone(),
+ enum_: Enum {
+ name: meta.name,
+ variants: meta.variants.into_iter().map(Into::into).collect(),
+ flat: meta.flat,
+ },
+ }
+ }
+}
+
+impl APIConverter<Error> for weedle::EnumDefinition<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Error> {
+ Ok(Error::from_enum(APIConverter::<Enum>::convert(self, ci)?))
+ }
+}
+
+impl APIConverter<Error> for weedle::InterfaceDefinition<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Error> {
+ Ok(Error::from_enum(APIConverter::<Enum>::convert(self, ci)?))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_variants() {
+ const UDL: &str = r#"
+ namespace test{};
+ [Error]
+ enum Testing { "one", "two", "three" };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.error_definitions().count(), 1);
+ let error = ci.get_error_definition("Testing").unwrap();
+ assert_eq!(
+ error
+ .variants()
+ .iter()
+ .map(|v| v.name())
+ .collect::<Vec<&str>>(),
+ vec!("one", "two", "three")
+ );
+ assert!(error.is_flat());
+ }
+
+ #[test]
+ fn test_duplicate_variants() {
+ const UDL: &str = r#"
+ namespace test{};
+ // Weird, but currently allowed!
+ // We should probably disallow this...
+ [Error]
+ enum Testing { "one", "two", "one" };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.error_definitions().count(), 1);
+ assert_eq!(
+ ci.get_error_definition("Testing").unwrap().variants().len(),
+ 3
+ );
+ }
+
+ #[test]
+ fn test_variant_data() {
+ const UDL: &str = r#"
+ namespace test{};
+
+ [Error]
+ interface Testing {
+ One(string reason);
+ Two(u8 code);
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.error_definitions().count(), 1);
+ let error: &Error = ci.get_error_definition("Testing").unwrap();
+ assert_eq!(
+ error
+ .variants()
+ .iter()
+ .map(|v| v.name())
+ .collect::<Vec<&str>>(),
+ vec!("One", "Two")
+ );
+ assert!(!error.is_flat());
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/ffi.rs b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs
new file mode 100644
index 0000000000..e1f9fe9737
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs
@@ -0,0 +1,102 @@
+/* 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/. */
+
+//! # Low-level typesystem for the FFI layer of a component interface.
+//!
+//! This module provides the "FFI-level" typesystem of a UniFFI Rust Component, that is,
+//! the C-style functions and structs and primitive datatypes that are used to interface
+//! between the Rust component code and the foreign-language bindings.
+//!
+//! These types are purely an implementation detail of UniFFI, so consumers shouldn't
+//! need to know about them. But as a developer working on UniFFI itself, you're likely
+//! to spend a lot of time thinking about how these low-level types are used to represent
+//! the higher-level "interface types" from the [`super::types::Type`] enum.
+/// Represents the restricted set of low-level types that can be used to construct
+/// the C-style FFI layer between a rust component and its foreign language bindings.
+///
+/// For the types that involve memory allocation, we make a distinction between
+/// "owned" types (the recipient must free it, or pass it to someone else) and
+/// "borrowed" types (the sender must keep it alive for the duration of the call).
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub enum FfiType {
+ // N.B. there are no booleans at this layer, since they cause problems for JNA.
+ UInt8,
+ Int8,
+ UInt16,
+ Int16,
+ UInt32,
+ Int32,
+ UInt64,
+ Int64,
+ Float32,
+ Float64,
+ /// A `*const c_void` pointer to a rust-owned `Arc<T>`.
+ /// If you've got one of these, you must call the appropriate rust function to free it.
+ /// The templates will generate a unique `free` function for each T.
+ /// The inner string references the name of the `T` type.
+ RustArcPtr(String),
+ /// A byte buffer allocated by rust, and owned by whoever currently holds it.
+ /// If you've got one of these, you must either call the appropriate rust function to free it
+ /// or pass it to someone that will.
+ /// If the inner option is Some, it is the name of the external type. The bindings may need
+ /// to use this name to import the correct RustBuffer for that type.
+ RustBuffer(Option<String>),
+ /// A borrowed reference to some raw bytes owned by foreign language code.
+ /// The provider of this reference must keep it alive for the duration of the receiving call.
+ ForeignBytes,
+ /// A pointer to a single function in to the foreign language.
+ /// This function contains all the machinery to make callbacks work on the foreign language side.
+ ForeignCallback,
+ // TODO: you can imagine a richer structural typesystem here, e.g. `Ref<String>` or something.
+ // We don't need that yet and it's possible we never will, so it isn't here for now.
+}
+
+/// Represents an "extern C"-style function that will be part of the FFI.
+///
+/// These can't be declared explicitly in the UDL, but rather, are derived automatically
+/// from the high-level interface. Each callable thing in the component API will have a
+/// corresponding `FfiFunction` through which it can be invoked, and UniFFI also provides
+/// some built-in `FfiFunction` helpers for use in the foreign language bindings.
+#[derive(Debug, Default, Clone)]
+pub struct FfiFunction {
+ pub(super) name: String,
+ pub(super) arguments: Vec<FfiArgument>,
+ pub(super) return_type: Option<FfiType>,
+}
+
+impl FfiFunction {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+ pub fn arguments(&self) -> Vec<&FfiArgument> {
+ self.arguments.iter().collect()
+ }
+ pub fn return_type(&self) -> Option<&FfiType> {
+ self.return_type.as_ref()
+ }
+}
+
+/// Represents an argument to an FFI function.
+///
+/// Each argument has a name and a type.
+#[derive(Debug, Clone)]
+pub struct FfiArgument {
+ pub(super) name: String,
+ pub(super) type_: FfiType,
+}
+
+impl FfiArgument {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+ pub fn type_(&self) -> FfiType {
+ self.type_.clone()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ // There's not really much to test here to be honest,
+ // it's mostly type declarations.
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/function.rs b/third_party/rust/uniffi_bindgen/src/interface/function.rs
new file mode 100644
index 0000000000..61724816cb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/function.rs
@@ -0,0 +1,288 @@
+/* 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/. */
+
+//! # Function definitions for a `ComponentInterface`.
+//!
+//! This module converts function definitions from UDL into structures that
+//! can be added to a `ComponentInterface`. A declaration in the UDL like this:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! namespace example {
+//! string hello();
+//! };
+//! # "##)?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Will result in a [`Function`] member being added to the resulting [`ComponentInterface`]:
+//!
+//! ```
+//! # use uniffi_bindgen::interface::Type;
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {
+//! # string hello();
+//! # };
+//! # "##)?;
+//! let func = ci.get_function_definition("hello").unwrap();
+//! assert_eq!(func.name(), "hello");
+//! assert!(matches!(func.return_type(), Some(Type::String)));
+//! assert_eq!(func.arguments().len(), 0);
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+use std::convert::TryFrom;
+
+use anyhow::{bail, Result};
+use uniffi_meta::Checksum;
+
+use super::attributes::{ArgumentAttributes, Attribute, FunctionAttributes};
+use super::ffi::{FfiArgument, FfiFunction};
+use super::literal::{convert_default_value, Literal};
+use super::types::{Type, TypeIterator};
+use super::{convert_type, APIConverter, ComponentInterface};
+
+/// Represents a standalone function.
+///
+/// Each `Function` corresponds to a standalone function in the rust module,
+/// and has a corresponding standalone function in the foreign language bindings.
+///
+/// In the FFI, this will be a standalone function with appropriately lowered types.
+#[derive(Debug, Clone, Checksum)]
+pub struct Function {
+ pub(super) name: String,
+ pub(super) arguments: Vec<Argument>,
+ pub(super) return_type: Option<Type>,
+ // We don't include the FFIFunc in the hash calculation, because:
+ // - it is entirely determined by the other fields,
+ // so excluding it is safe.
+ // - its `name` property includes a checksum derived from the very
+ // hash value we're trying to calculate here, so excluding it
+ // avoids a weird circular dependency in the calculation.
+ #[checksum_ignore]
+ pub(super) ffi_func: FfiFunction,
+ pub(super) attributes: FunctionAttributes,
+}
+
+impl Function {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn arguments(&self) -> Vec<&Argument> {
+ self.arguments.iter().collect()
+ }
+
+ pub fn full_arguments(&self) -> Vec<Argument> {
+ self.arguments.to_vec()
+ }
+
+ pub fn return_type(&self) -> Option<&Type> {
+ self.return_type.as_ref()
+ }
+
+ pub fn ffi_func(&self) -> &FfiFunction {
+ &self.ffi_func
+ }
+
+ pub fn throws(&self) -> bool {
+ self.attributes.get_throws_err().is_some()
+ }
+
+ pub fn throws_name(&self) -> Option<&str> {
+ self.attributes.get_throws_err()
+ }
+
+ pub fn throws_type(&self) -> Option<Type> {
+ self.attributes
+ .get_throws_err()
+ .map(|name| Type::Error(name.to_owned()))
+ }
+
+ pub fn derive_ffi_func(&mut self, ci_prefix: &str) -> Result<()> {
+ // The name is already set if the function is defined through a proc-macro invocation
+ // rather than in UDL. Don't overwrite it in that case.
+ if self.ffi_func.name.is_empty() {
+ self.ffi_func.name = format!("{ci_prefix}_{}", self.name);
+ }
+
+ self.ffi_func.arguments = self.arguments.iter().map(|arg| arg.into()).collect();
+ self.ffi_func.return_type = self.return_type.as_ref().map(|rt| rt.into());
+ Ok(())
+ }
+}
+
+impl From<uniffi_meta::FnParamMetadata> for Argument {
+ fn from(meta: uniffi_meta::FnParamMetadata) -> Self {
+ Argument {
+ name: meta.name,
+ type_: convert_type(&meta.ty),
+ by_ref: false,
+ optional: false,
+ default: None,
+ }
+ }
+}
+
+impl From<uniffi_meta::FnMetadata> for Function {
+ fn from(meta: uniffi_meta::FnMetadata) -> Self {
+ let ffi_name = meta.ffi_symbol_name();
+
+ let return_type = meta.return_type.map(|out| convert_type(&out));
+ let arguments = meta.inputs.into_iter().map(Into::into).collect();
+
+ let ffi_func = FfiFunction {
+ name: ffi_name,
+ ..FfiFunction::default()
+ };
+
+ Self {
+ name: meta.name,
+ arguments,
+ return_type,
+ ffi_func,
+ attributes: meta.throws.map(Attribute::Throws).into_iter().collect(),
+ }
+ }
+}
+
+impl APIConverter<Function> for weedle::namespace::NamespaceMember<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Function> {
+ match self {
+ weedle::namespace::NamespaceMember::Operation(f) => f.convert(ci),
+ _ => bail!("no support for namespace member type {:?} yet", self),
+ }
+ }
+}
+
+impl APIConverter<Function> for weedle::namespace::OperationNamespaceMember<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Function> {
+ let return_type = ci.resolve_return_type_expression(&self.return_type)?;
+ Ok(Function {
+ name: match self.identifier {
+ None => bail!("anonymous functions are not supported {:?}", self),
+ Some(id) => id.0.to_string(),
+ },
+ return_type,
+ arguments: self.args.body.list.convert(ci)?,
+ ffi_func: Default::default(),
+ attributes: FunctionAttributes::try_from(self.attributes.as_ref())?,
+ })
+ }
+}
+
+/// Represents an argument to a function/constructor/method call.
+///
+/// Each argument has a name and a type, along with some optional metadata.
+#[derive(Debug, Clone, Checksum)]
+pub struct Argument {
+ pub(super) name: String,
+ pub(super) type_: Type,
+ pub(super) by_ref: bool,
+ pub(super) optional: bool,
+ pub(super) default: Option<Literal>,
+}
+
+impl Argument {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn type_(&self) -> &Type {
+ &self.type_
+ }
+
+ pub fn by_ref(&self) -> bool {
+ self.by_ref
+ }
+
+ pub fn default_value(&self) -> Option<&Literal> {
+ self.default.as_ref()
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ self.type_.iter_types()
+ }
+}
+
+impl From<&Argument> for FfiArgument {
+ fn from(a: &Argument) -> FfiArgument {
+ FfiArgument {
+ name: a.name.clone(),
+ type_: (&a.type_).into(),
+ }
+ }
+}
+
+impl APIConverter<Argument> for weedle::argument::Argument<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Argument> {
+ match self {
+ weedle::argument::Argument::Single(t) => t.convert(ci),
+ weedle::argument::Argument::Variadic(_) => bail!("variadic arguments not supported"),
+ }
+ }
+}
+
+impl APIConverter<Argument> for weedle::argument::SingleArgument<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Argument> {
+ let type_ = ci.resolve_type_expression(&self.type_)?;
+ let default = match self.default {
+ None => None,
+ Some(v) => Some(convert_default_value(&v.value, &type_)?),
+ };
+ let by_ref = ArgumentAttributes::try_from(self.attributes.as_ref())?.by_ref();
+ Ok(Argument {
+ name: self.identifier.0.to_string(),
+ type_,
+ by_ref,
+ optional: self.optional.is_some(),
+ default,
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_minimal_and_rich_function() -> Result<()> {
+ let ci = ComponentInterface::from_webidl(
+ r##"
+ namespace test {
+ void minimal();
+ [Throws=TestError]
+ sequence<string?> rich(u32 arg1, TestDict arg2);
+ };
+ [Error]
+ enum TestError { "err" };
+ dictionary TestDict {
+ u32 field;
+ };
+ "##,
+ )?;
+
+ let func1 = ci.get_function_definition("minimal").unwrap();
+ assert_eq!(func1.name(), "minimal");
+ assert!(func1.return_type().is_none());
+ assert!(func1.throws_type().is_none());
+ assert_eq!(func1.arguments().len(), 0);
+
+ let func2 = ci.get_function_definition("rich").unwrap();
+ assert_eq!(func2.name(), "rich");
+ assert_eq!(
+ func2.return_type().unwrap().canonical_name(),
+ "SequenceOptionalstring"
+ );
+ assert!(matches!(func2.throws_type(), Some(Type::Error(s)) if s == "TestError"));
+ assert_eq!(func2.arguments().len(), 2);
+ assert_eq!(func2.arguments()[0].name(), "arg1");
+ assert_eq!(func2.arguments()[0].type_().canonical_name(), "u32");
+ assert_eq!(func2.arguments()[1].name(), "arg2");
+ assert_eq!(
+ func2.arguments()[1].type_().canonical_name(),
+ "TypeTestDict"
+ );
+ Ok(())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/literal.rs b/third_party/rust/uniffi_bindgen/src/interface/literal.rs
new file mode 100644
index 0000000000..da02613684
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/literal.rs
@@ -0,0 +1,186 @@
+/* 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/. */
+
+//! # Support for literal values.
+//!
+//! This module provides support for interpreting literal values from the UDL,
+//! which appear in places such as default arguments.
+
+use anyhow::{bail, Result};
+use uniffi_meta::Checksum;
+
+use super::types::Type;
+
+// Represents a literal value.
+// Used for e.g. default argument values.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Checksum)]
+pub enum Literal {
+ Boolean(bool),
+ String(String),
+ // Integers are represented as the widest representation we can.
+ // Number formatting vary with language and radix, so we avoid a lot of parsing and
+ // formatting duplication by using only signed and unsigned variants.
+ UInt(u64, Radix, Type),
+ Int(i64, Radix, Type),
+ // Pass the string representation through as typed in the UDL.
+ // This avoids a lot of uncertainty around precision and accuracy,
+ // though bindings for languages less sophisticated number parsing than WebIDL
+ // will have to do extra work.
+ Float(String, Type),
+ Enum(String, Type),
+ EmptySequence,
+ EmptyMap,
+ Null,
+}
+
+// Represent the radix of integer literal values.
+// We preserve the radix into the generated bindings for readability reasons.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Checksum)]
+pub enum Radix {
+ Decimal = 10,
+ Octal = 8,
+ Hexadecimal = 16,
+}
+
+pub(super) fn convert_default_value(
+ default_value: &weedle::literal::DefaultValue<'_>,
+ type_: &Type,
+) -> Result<Literal> {
+ fn convert_integer(literal: &weedle::literal::IntegerLit<'_>, type_: &Type) -> Result<Literal> {
+ let (string, radix) = match literal {
+ weedle::literal::IntegerLit::Dec(v) => (v.0, Radix::Decimal),
+ weedle::literal::IntegerLit::Hex(v) => (v.0, Radix::Hexadecimal),
+ weedle::literal::IntegerLit::Oct(v) => (v.0, Radix::Octal),
+ };
+ // This is the radix of the parsed number, passed to `from_str_radix`.
+ let src_radix = radix as u32;
+ // This radix tells the backends how to represent the number in the output languages.
+ let dest_radix = if string == "0" || string.starts_with('-') {
+ // 1. weedle parses "0" as an octal literal, but we most likely want to treat this as a decimal.
+ // 2. Explicitly negatively signed hex numbers won't convert via i64 very well if they're not 64 bit.
+ // For ease of implementation, output will use decimal.
+ Radix::Decimal
+ } else {
+ radix
+ };
+
+ // Clippy seems to think we should be using `strip_prefix` here, but
+ // it seems confused as to what this is actually doing.
+ #[allow(clippy::manual_strip)]
+ let string = if string.starts_with('-') {
+ ("-".to_string() + string[1..].trim_start_matches("0x")).to_lowercase()
+ } else {
+ string.trim_start_matches("0x").to_lowercase()
+ };
+
+ Ok(match type_ {
+ Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 => Literal::Int(
+ i64::from_str_radix(&string, src_radix)?,
+ dest_radix,
+ type_.clone(),
+ ),
+ Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 => Literal::UInt(
+ u64::from_str_radix(&string, src_radix)?,
+ dest_radix,
+ type_.clone(),
+ ),
+
+ _ => bail!("Cannot coerce literal {} into a non-integer type", string),
+ })
+ }
+
+ fn convert_float(literal: &weedle::literal::FloatLit<'_>, type_: &Type) -> Result<Literal> {
+ let string = match literal {
+ weedle::literal::FloatLit::Value(v) => v.0,
+
+ _ => bail!("Infinity and NaN is not currently supported"),
+ };
+
+ Ok(match type_ {
+ Type::Float32 | Type::Float64 => Literal::Float(string.to_string(), type_.clone()),
+ _ => bail!("Cannot coerce literal {} into a non-float type", string),
+ })
+ }
+
+ Ok(match (default_value, type_) {
+ (weedle::literal::DefaultValue::Boolean(b), Type::Boolean) => Literal::Boolean(b.0),
+ (weedle::literal::DefaultValue::String(s), Type::String) => {
+ // Note that weedle doesn't parse escaped double quotes.
+ // Keeping backends using double quotes (possible for all to date)
+ // means we don't need to escape single quotes. But we haven't spent a lot of time
+ // trying to break default values with weird escapes and quotes.
+ Literal::String(s.0.to_string())
+ }
+ (weedle::literal::DefaultValue::EmptyArray(_), Type::Sequence(_)) => Literal::EmptySequence,
+ (weedle::literal::DefaultValue::String(s), Type::Enum(_)) => {
+ Literal::Enum(s.0.to_string(), type_.clone())
+ }
+ (weedle::literal::DefaultValue::Null(_), Type::Optional(_)) => Literal::Null,
+ (_, Type::Optional(inner_type)) => convert_default_value(default_value, inner_type)?,
+
+ // We'll ensure the type safety in the convert_* number methods.
+ (weedle::literal::DefaultValue::Integer(i), _) => convert_integer(i, type_)?,
+ (weedle::literal::DefaultValue::Float(i), _) => convert_float(i, type_)?,
+
+ _ => bail!("No support for {:?} literal yet", default_value),
+ })
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use weedle::Parse;
+
+ fn parse_and_convert(expr: &str, t: Type) -> Result<Literal> {
+ let (_, node) = weedle::literal::DefaultValue::parse(expr).unwrap();
+ convert_default_value(&node, &t)
+ }
+
+ #[test]
+ fn test_default_value_conversion() -> Result<()> {
+ assert!(matches!(
+ parse_and_convert("0", Type::UInt8)?,
+ Literal::UInt(0, Radix::Decimal, Type::UInt8)
+ ));
+ assert!(matches!(
+ parse_and_convert("-12", Type::Int32)?,
+ Literal::Int(-12, Radix::Decimal, Type::Int32)
+ ));
+ assert!(
+ matches!(parse_and_convert("3.14", Type::Float32)?, Literal::Float(v, Type::Float32) if v == "3.14")
+ );
+ assert!(matches!(
+ parse_and_convert("false", Type::Boolean)?,
+ Literal::Boolean(false)
+ ));
+ assert!(
+ matches!(parse_and_convert("\"TEST\"", Type::String)?, Literal::String(v) if v == "TEST")
+ );
+ assert!(
+ matches!(parse_and_convert("\"one\"", Type::Enum("E".into()))?, Literal::Enum(v, Type::Enum(e)) if v == "one" && e == "E")
+ );
+ assert!(matches!(
+ parse_and_convert("[]", Type::Sequence(Box::new(Type::String)))?,
+ Literal::EmptySequence
+ ));
+ assert!(matches!(
+ parse_and_convert("null", Type::Optional(Box::new(Type::String)))?,
+ Literal::Null
+ ));
+ Ok(())
+ }
+ #[test]
+ fn test_error_on_type_mismatch() {
+ assert_eq!(
+ parse_and_convert("0", Type::Boolean)
+ .unwrap_err()
+ .to_string(),
+ "Cannot coerce literal 0 into a non-integer type"
+ );
+ assert!(parse_and_convert("{}", Type::Boolean)
+ .unwrap_err()
+ .to_string()
+ .starts_with("No support for"));
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/mod.rs b/third_party/rust/uniffi_bindgen/src/interface/mod.rs
new file mode 100644
index 0000000000..3daf50ef4a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/mod.rs
@@ -0,0 +1,1218 @@
+/* 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/. */
+
+//! # Component Interface Definition.
+//!
+//! This module provides an abstract representation of the interface provided by a UniFFI Rust Component,
+//! in high-level terms suitable for translation into target consumer languages such as Kotlin
+//! and Swift. It also provides facilities for parsing a WebIDL interface definition file into such a
+//! representation.
+//!
+//! The entrypoint to this crate is the `ComponentInterface` struct, which holds a complete definition
+//! of the interface provided by a component, in two parts:
+//!
+//! * The high-level consumer API, in terms of objects and records and methods and so-on
+//! * The low-level FFI contract through which the foreign language code can call into Rust.
+//!
+//! That's really the key concept of this crate so it's worth repeating: a `ComponentInterface` completely
+//! defines the shape and semantics of an interface between the Rust-based implementation of a component
+//! and its foreign language consumers, including details like:
+//!
+//! * The names of all symbols in the compiled object file
+//! * The type and arity of all exported functions
+//! * The layout and conventions used for all arguments and return types
+//!
+//! If you have a dynamic library compiled from a Rust Component using this crate, and a foreign
+//! language binding generated from the same `ComponentInterface` using the same version of this
+//! module, then there should be no opportunities for them to disagree on how the two sides should
+//! interact.
+//!
+//! General and incomplete TODO list for this thing:
+//!
+//! * It should prevent user error and the possibility of generating bad code by doing (at least)
+//! the following checks:
+//! * No duplicate names (types, methods, args, etc)
+//! * No shadowing of builtin names, or names we use in code generation
+//! We expect that if the user actually does one of these things, then they *should* get a compile
+//! error when trying to build the component, because the codegen will be invalid. But we can't
+//! guarantee that there's not some edge-case where it produces valid-but-incorrect code.
+//!
+//! * There is a *lot* of cloning going on, in the spirit of "first make it work". There's probably
+//! a good opportunity here for e.g. interned strings, but we're nowhere near the point were we need
+//! that kind of optimization just yet.
+//!
+//! * Error messages and general developer experience leave a lot to be desired.
+
+use std::{
+ collections::{btree_map::Entry, BTreeMap, HashSet},
+ convert::TryFrom,
+ iter,
+};
+
+use anyhow::{bail, ensure, Result};
+
+pub mod types;
+pub use types::Type;
+use types::{TypeIterator, TypeUniverse};
+
+mod attributes;
+mod callbacks;
+pub use callbacks::CallbackInterface;
+mod enum_;
+pub use enum_::Enum;
+mod error;
+pub use error::Error;
+mod function;
+pub use function::{Argument, Function};
+mod literal;
+pub use literal::{Literal, Radix};
+mod namespace;
+pub use namespace::Namespace;
+mod object;
+pub use object::{Constructor, Method, Object};
+mod record;
+pub use record::{Field, Record};
+
+pub mod ffi;
+pub use ffi::{FfiArgument, FfiFunction, FfiType};
+use uniffi_meta::{Checksum, FnMetadata, MethodMetadata, ObjectMetadata};
+
+/// This needs to match the major/minor version of the `uniffi` crate. See
+/// `docs/uniffi-versioning.md` for details.
+///
+/// Once we get to 1.0, then we should reformat this to only include the major version number.
+const UNIFFI_CONTRACT_VERSION: &str = "0.22";
+
+/// The main public interface for this module, representing the complete details of an interface exposed
+/// by a rust component and the details of consuming it via an extern-C FFI layer.
+///
+#[derive(Debug, Default, Checksum)]
+pub struct ComponentInterface {
+ /// This always points to `UNIFFI_CONTRACT_VERSION`. By including it in the checksum, we
+ /// prevent consumers from combining scaffolding and bindings that were created with different
+ /// `uniffi` versions.
+ uniffi_version: &'static str,
+ /// All of the types used in the interface.
+ // We can't checksum `self.types`, but its contents are implied by the other fields
+ // anyway, so it's safe to ignore it.
+ #[checksum_ignore]
+ pub(super) types: TypeUniverse,
+ /// The unique prefix that we'll use for namespacing when exposing this component's API.
+ namespace: String,
+ /// The internal unique prefix used to namespace FFI symbols
+ #[checksum_ignore]
+ ffi_namespace: String,
+ /// The high-level API provided by the component.
+ enums: BTreeMap<String, Enum>,
+ records: BTreeMap<String, Record>,
+ functions: Vec<Function>,
+ objects: Vec<Object>,
+ callback_interfaces: Vec<CallbackInterface>,
+ errors: BTreeMap<String, Error>,
+}
+
+impl ComponentInterface {
+ /// Parse a `ComponentInterface` from a string containing a WebIDL definition.
+ pub fn from_webidl(idl: &str) -> Result<Self> {
+ let mut ci = Self {
+ uniffi_version: UNIFFI_CONTRACT_VERSION,
+ ..Default::default()
+ };
+ // There's some lifetime thing with the errors returned from weedle::Definitions::parse
+ // that my own lifetime is too short to worry about figuring out; unwrap and move on.
+
+ // Note we use `weedle::Definitions::parse` instead of `weedle::parse` so
+ // on parse errors we can see how far weedle got, which helps locate the problem.
+ use weedle::Parse; // this trait must be in scope for parse to work.
+ let (remaining, defns) = weedle::Definitions::parse(idl.trim()).unwrap();
+ if !remaining.is_empty() {
+ println!("Error parsing the IDL. Text remaining to be parsed is:");
+ println!("{remaining}");
+ bail!("parse error");
+ }
+ // Unconditionally add the String type, which is used by the panic handling
+ ci.types.add_known_type(&Type::String)?;
+ // We process the WebIDL definitions in two passes.
+ // First, go through and look for all the named types.
+ ci.types.add_type_definitions_from(defns.as_slice())?;
+ // With those names resolved, we can build a complete representation of the API.
+ APIBuilder::process(&defns, &mut ci)?;
+
+ // The FFI namespace must not be computed on the fly because it could otherwise be
+ // influenced by things added later from proc-macro metadata. Those have their own
+ // namespacing mechanism.
+ assert!(!ci.namespace.is_empty());
+ ci.ffi_namespace = format!("{}_{:x}", ci.namespace, ci.checksum());
+
+ // The following two methods will be called later anyways, but we call them here because
+ // it's convenient for UDL-only tests.
+ ci.check_consistency()?;
+ // Now that the high-level API is settled, we can derive the low-level FFI.
+ ci.derive_ffi_funcs()?;
+
+ Ok(ci)
+ }
+
+ /// The string namespace within which this API should be presented to the caller.
+ ///
+ /// This string would typically be used to prefix function names in the FFI, to build
+ /// a package or module name for the foreign language, etc.
+ pub fn namespace(&self) -> &str {
+ self.namespace.as_str()
+ }
+
+ /// Get the definitions for every Enum type in the interface.
+ pub fn enum_definitions(&self) -> impl Iterator<Item = &Enum> {
+ self.enums.values()
+ }
+
+ /// Get an Enum definition by name, or None if no such Enum is defined.
+ pub fn get_enum_definition(&self, name: &str) -> Option<&Enum> {
+ self.enums.get(name)
+ }
+
+ /// Get the definitions for every Record type in the interface.
+ pub fn record_definitions(&self) -> impl Iterator<Item = &Record> {
+ self.records.values()
+ }
+
+ /// Get a Record definition by name, or None if no such Record is defined.
+ pub fn get_record_definition(&self, name: &str) -> Option<&Record> {
+ self.records.get(name)
+ }
+
+ /// Get the definitions for every Function in the interface.
+ pub fn function_definitions(&self) -> &[Function] {
+ &self.functions
+ }
+
+ /// Get a Function definition by name, or None if no such Function is defined.
+ pub fn get_function_definition(&self, name: &str) -> Option<&Function> {
+ // TODO: probably we could store these internally in a HashMap to make this easier?
+ self.functions.iter().find(|f| f.name == name)
+ }
+
+ /// Get the definitions for every Object type in the interface.
+ pub fn object_definitions(&self) -> &[Object] {
+ &self.objects
+ }
+
+ /// Get an Object definition by name, or None if no such Object is defined.
+ pub fn get_object_definition(&self, name: &str) -> Option<&Object> {
+ // TODO: probably we could store these internally in a HashMap to make this easier?
+ self.objects.iter().find(|o| o.name == name)
+ }
+
+ /// Get the definitions for every Callback Interface type in the interface.
+ pub fn callback_interface_definitions(&self) -> &[CallbackInterface] {
+ &self.callback_interfaces
+ }
+
+ /// Get a Callback interface definition by name, or None if no such interface is defined.
+ pub fn get_callback_interface_definition(&self, name: &str) -> Option<&CallbackInterface> {
+ // TODO: probably we could store these internally in a HashMap to make this easier?
+ self.callback_interfaces.iter().find(|o| o.name == name)
+ }
+
+ /// Get the definitions for every Error type in the interface.
+ pub fn error_definitions(&self) -> impl Iterator<Item = &Error> {
+ self.errors.values()
+ }
+
+ /// Get an Error definition by name, or None if no such Error is defined.
+ pub fn get_error_definition(&self, name: &str) -> Option<&Error> {
+ self.errors.get(name)
+ }
+
+ /// Should we generate read (and lift) functions for errors?
+ ///
+ /// This is a workaround for the fact that lower/write can't be generated for some errors,
+ /// specifically errors that are defined as flat in the UDL, but actually have fields in the
+ /// Rust source.
+ pub fn should_generate_error_read(&self, error: &Error) -> bool {
+ // We can and should always generate read() methods for fielded errors
+ let fielded = !error.is_flat();
+ // For flat errors, we should only generate read() methods if we need them to support
+ // callback interface errors
+ let used_in_callback_interface = self
+ .callback_interface_definitions()
+ .iter()
+ .flat_map(|cb| cb.methods())
+ .any(|m| m.throws_type() == Some(error.type_()));
+
+ fielded || used_in_callback_interface
+ }
+
+ /// Get details about all `Type::External` types
+ pub fn iter_external_types(&self) -> impl Iterator<Item = (&String, &String)> {
+ self.types.iter_known_types().filter_map(|t| match t {
+ Type::External { name, crate_name } => Some((name, crate_name)),
+ _ => None,
+ })
+ }
+
+ /// Get details about all `Type::Custom` types
+ pub fn iter_custom_types(&self) -> impl Iterator<Item = (&String, &Type)> {
+ self.types.iter_known_types().filter_map(|t| match t {
+ Type::Custom { name, builtin } => Some((name, &**builtin)),
+ _ => None,
+ })
+ }
+
+ /// Iterate over all known types in the interface.
+ pub fn iter_types(&self) -> impl Iterator<Item = &Type> {
+ self.types.iter_known_types()
+ }
+
+ /// Get a specific type
+ pub fn get_type(&self, name: &str) -> Option<Type> {
+ self.types.get_type_definition(name)
+ }
+
+ /// Iterate over all types contained in the given item.
+ ///
+ /// This method uses `iter_types` to iterate over the types contained within the given type,
+ /// but additionally recurses into the definition of user-defined types like records and enums
+ /// to yield the types that *they* contain.
+ fn iter_types_in_item<'a>(&'a self, item: &'a Type) -> impl Iterator<Item = &'a Type> + 'a {
+ RecursiveTypeIterator::new(self, item)
+ }
+
+ /// Check whether the given item contains any (possibly nested) Type::Object references.
+ ///
+ /// This is important to know in language bindings that cannot integrate object types
+ /// tightly with the host GC, and hence need to perform manual destruction of objects.
+ pub fn item_contains_object_references(&self, item: &Type) -> bool {
+ self.iter_types_in_item(item)
+ .any(|t| matches!(t, Type::Object(_)))
+ }
+
+ /// Check whether the given item contains any (possibly nested) unsigned types
+ pub fn item_contains_unsigned_types(&self, item: &Type) -> bool {
+ self.iter_types_in_item(item)
+ .any(|t| matches!(t, Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64))
+ }
+
+ /// Check whether the interface contains any optional types
+ pub fn contains_optional_types(&self) -> bool {
+ self.types
+ .iter_known_types()
+ .any(|t| matches!(t, Type::Optional(_)))
+ }
+
+ /// Check whether the interface contains any sequence types
+ pub fn contains_sequence_types(&self) -> bool {
+ self.types
+ .iter_known_types()
+ .any(|t| matches!(t, Type::Sequence(_)))
+ }
+
+ /// Check whether the interface contains any map types
+ pub fn contains_map_types(&self) -> bool {
+ self.types
+ .iter_known_types()
+ .any(|t| matches!(t, Type::Map(_, _)))
+ }
+
+ /// Calculate a numeric checksum for this ComponentInterface.
+ ///
+ /// The checksum can be used to guard against accidentally using foreign-language bindings
+ /// generated from one version of an interface with the compiled Rust code from a different
+ /// version of that interface. It offers the following properties:
+ ///
+ /// - Two ComponentIntefaces generated from the same WebIDL file, using the same version of uniffi
+ /// and the same version of Rust, will always have the same checksum value.
+ /// - Two ComponentInterfaces will, with high probability, have different checksum values if:
+ /// - They were generated from two different WebIDL files.
+ /// - They were generated by two different versions of uniffi
+ ///
+ /// Note that this is designed to prevent accidents, not attacks, so there is no need for the
+ /// checksum to be cryptographically secure.
+ pub fn checksum(&self) -> u16 {
+ uniffi_meta::checksum(self)
+ }
+
+ /// The namespace to use in FFI-level function definitions.
+ ///
+ /// The value returned by this method is used as a prefix to namespace all UDL-defined FFI
+ /// functions used in this ComponentInterface.
+ ///
+ /// Since these names are an internal implementation detail that is not typically visible to
+ /// consumers, we take the opportunity to add an additional safety guard by including a 4-hex-char
+ /// checksum in each name. If foreign-language bindings attempt to load and use a version of the
+ /// Rust code compiled from a different UDL definition than the one used for the bindings themselves,
+ /// then there is a high probability of checksum mismatch and they will fail to link against the
+ /// compiled Rust code. The result will be an ugly inscrutable link-time error, but that is a lot
+ /// better than triggering potentially arbitrary memory unsafety!
+ pub fn ffi_namespace(&self) -> &str {
+ assert!(!self.ffi_namespace.is_empty());
+ &self.ffi_namespace
+ }
+
+ /// Builtin FFI function for allocating a new `RustBuffer`.
+ /// This is needed so that the foreign language bindings can create buffers in which to pass
+ /// complex data types across the FFI.
+ pub fn ffi_rustbuffer_alloc(&self) -> FfiFunction {
+ FfiFunction {
+ name: format!("ffi_{}_rustbuffer_alloc", self.ffi_namespace()),
+ arguments: vec![FfiArgument {
+ name: "size".to_string(),
+ type_: FfiType::Int32,
+ }],
+ return_type: Some(FfiType::RustBuffer(None)),
+ }
+ }
+
+ /// Builtin FFI function for copying foreign-owned bytes
+ /// This is needed so that the foreign language bindings can create buffers in which to pass
+ /// complex data types across the FFI.
+ pub fn ffi_rustbuffer_from_bytes(&self) -> FfiFunction {
+ FfiFunction {
+ name: format!("ffi_{}_rustbuffer_from_bytes", self.ffi_namespace()),
+ arguments: vec![FfiArgument {
+ name: "bytes".to_string(),
+ type_: FfiType::ForeignBytes,
+ }],
+ return_type: Some(FfiType::RustBuffer(None)),
+ }
+ }
+
+ /// Builtin FFI function for freeing a `RustBuffer`.
+ /// This is needed so that the foreign language bindings can free buffers in which they received
+ /// complex data types returned across the FFI.
+ pub fn ffi_rustbuffer_free(&self) -> FfiFunction {
+ FfiFunction {
+ name: format!("ffi_{}_rustbuffer_free", self.ffi_namespace()),
+ arguments: vec![FfiArgument {
+ name: "buf".to_string(),
+ type_: FfiType::RustBuffer(None),
+ }],
+ return_type: None,
+ }
+ }
+
+ /// Builtin FFI function for reserving extra space in a `RustBuffer`.
+ /// This is needed so that the foreign language bindings can grow buffers used for passing
+ /// complex data types across the FFI.
+ pub fn ffi_rustbuffer_reserve(&self) -> FfiFunction {
+ FfiFunction {
+ name: format!("ffi_{}_rustbuffer_reserve", self.ffi_namespace()),
+ arguments: vec![
+ FfiArgument {
+ name: "buf".to_string(),
+ type_: FfiType::RustBuffer(None),
+ },
+ FfiArgument {
+ name: "additional".to_string(),
+ type_: FfiType::Int32,
+ },
+ ],
+ return_type: Some(FfiType::RustBuffer(None)),
+ }
+ }
+
+ /// List the definitions of all FFI functions in the interface.
+ ///
+ /// The set of FFI functions is derived automatically from the set of higher-level types
+ /// along with the builtin FFI helper functions.
+ pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> + '_ {
+ self.iter_user_ffi_function_definitions()
+ .cloned()
+ .chain(self.iter_rust_buffer_ffi_function_definitions())
+ }
+
+ /// List all FFI functions definitions for user-defined interfaces
+ ///
+ /// This includes FFI functions for:
+ /// - Top-level functions
+ /// - Object methods
+ /// - Callback interfaces
+ pub fn iter_user_ffi_function_definitions(&self) -> impl Iterator<Item = &FfiFunction> + '_ {
+ iter::empty()
+ .chain(
+ self.objects
+ .iter()
+ .flat_map(|obj| obj.iter_ffi_function_definitions()),
+ )
+ .chain(
+ self.callback_interfaces
+ .iter()
+ .map(|cb| cb.ffi_init_callback()),
+ )
+ .chain(self.functions.iter().map(|f| &f.ffi_func))
+ }
+
+ /// List all FFI functions definitions for RustBuffer functionality
+ pub fn iter_rust_buffer_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> {
+ [
+ self.ffi_rustbuffer_alloc(),
+ self.ffi_rustbuffer_from_bytes(),
+ self.ffi_rustbuffer_free(),
+ self.ffi_rustbuffer_reserve(),
+ ]
+ .into_iter()
+ }
+
+ //
+ // Private methods for building a ComponentInterface.
+ //
+
+ /// Resolve a weedle type expression into a `Type`.
+ ///
+ /// This method uses the current state of our `TypeUniverse` to turn a weedle type expression
+ /// into a concrete `Type` (or error if the type expression is not well defined). It abstracts
+ /// away the complexity of walking weedle's type struct hierarchy by dispatching to the `TypeResolver`
+ /// trait.
+ fn resolve_type_expression<T: types::TypeResolver>(&mut self, expr: T) -> Result<Type> {
+ self.types.resolve_type_expression(expr)
+ }
+
+ /// Resolve a weedle `ReturnType` expression into an optional `Type`.
+ ///
+ /// This method is similar to `resolve_type_expression`, but tailored specifically for return types.
+ /// It can return `None` to represent a non-existent return value.
+ fn resolve_return_type_expression(
+ &mut self,
+ expr: &weedle::types::ReturnType<'_>,
+ ) -> Result<Option<Type>> {
+ Ok(match expr {
+ weedle::types::ReturnType::Undefined(_) => None,
+ weedle::types::ReturnType::Type(t) => {
+ // Older versions of WebIDL used `void` for functions that don't return a value,
+ // while newer versions have replaced it with `undefined`. Special-case this for
+ // backwards compatibility for our consumers.
+ use weedle::types::{NonAnyType::Identifier, SingleType::NonAny, Type::Single};
+ match t {
+ Single(NonAny(Identifier(id))) if id.type_.0 == "void" => None,
+ _ => Some(self.resolve_type_expression(t)?),
+ }
+ }
+ })
+ }
+
+ /// Called by `APIBuilder` impls to add a newly-parsed namespace definition to the `ComponentInterface`.
+ fn add_namespace_definition(&mut self, defn: Namespace) -> Result<()> {
+ if !self.namespace.is_empty() {
+ bail!("duplicate namespace definition");
+ }
+ self.namespace = defn.name;
+ Ok(())
+ }
+
+ /// Called by `APIBuilder` impls to add a newly-parsed enum definition to the `ComponentInterface`.
+ pub(super) fn add_enum_definition(&mut self, defn: Enum) -> Result<()> {
+ match self.enums.entry(defn.name().to_owned()) {
+ Entry::Vacant(v) => {
+ v.insert(defn);
+ }
+ Entry::Occupied(o) => {
+ let existing_def = o.get();
+ if defn != *existing_def {
+ bail!(
+ "Mismatching definition for enum `{}`!\n\
+ existing definition: {existing_def:#?},\n\
+ new definition: {defn:#?}",
+ defn.name(),
+ );
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Called by `APIBuilder` impls to add a newly-parsed record definition to the `ComponentInterface`.
+ pub(super) fn add_record_definition(&mut self, defn: Record) -> Result<()> {
+ match self.records.entry(defn.name().to_owned()) {
+ Entry::Vacant(v) => {
+ v.insert(defn);
+ }
+ Entry::Occupied(o) => {
+ let existing_def = o.get();
+ if defn != *existing_def {
+ bail!(
+ "Mismatching definition for record `{}`!\n\
+ existing definition: {existing_def:#?},\n\
+ new definition: {defn:#?}",
+ defn.name(),
+ );
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ fn add_function_impl(&mut self, defn: Function) -> Result<()> {
+ // Since functions are not a first-class type, we have to check for duplicates here
+ // rather than relying on the type-finding pass to catch them.
+ if self.functions.iter().any(|f| f.name == defn.name) {
+ bail!("duplicate function definition: \"{}\"", defn.name);
+ }
+ if !matches!(self.types.get_type_definition(defn.name()), None) {
+ bail!("Conflicting type definition for \"{}\"", defn.name());
+ }
+ self.functions.push(defn);
+
+ Ok(())
+ }
+
+ /// Called by `APIBuilder` impls to add a newly-parsed function definition to the `ComponentInterface`.
+ fn add_function_definition(&mut self, defn: Function) -> Result<()> {
+ for arg in &defn.arguments {
+ self.types.add_known_type(&arg.type_)?;
+ }
+ if let Some(ty) = &defn.return_type {
+ self.types.add_known_type(ty)?;
+ }
+
+ self.add_function_impl(defn)
+ }
+
+ pub(super) fn add_fn_meta(&mut self, meta: FnMetadata) -> Result<()> {
+ self.add_function_impl(meta.into())
+ }
+
+ pub(super) fn add_method_meta(&mut self, meta: MethodMetadata) {
+ let object = get_or_insert_object(&mut self.objects, &meta.self_name);
+ let defn: Method = meta.into();
+ object.methods.push(defn);
+ }
+
+ pub(super) fn add_object_free_fn(&mut self, meta: ObjectMetadata) {
+ let object = get_or_insert_object(&mut self.objects, &meta.name);
+ object.ffi_func_free.name = meta.free_ffi_symbol_name();
+ }
+
+ /// Called by `APIBuilder` impls to add a newly-parsed object definition to the `ComponentInterface`.
+ fn add_object_definition(&mut self, defn: Object) {
+ // Note that there will be no duplicates thanks to the previous type-finding pass.
+ self.objects.push(defn);
+ }
+
+ /// Called by `APIBuilder` impls to add a newly-parsed callback interface definition to the `ComponentInterface`.
+ fn add_callback_interface_definition(&mut self, defn: CallbackInterface) {
+ // Note that there will be no duplicates thanks to the previous type-finding pass.
+ self.callback_interfaces.push(defn);
+ }
+
+ /// Called by `APIBuilder` impls to add a newly-parsed error definition to the `ComponentInterface`.
+ pub(super) fn add_error_definition(&mut self, defn: Error) -> Result<()> {
+ match self.errors.entry(defn.name().to_owned()) {
+ Entry::Vacant(v) => {
+ v.insert(defn);
+ }
+ Entry::Occupied(o) => {
+ let existing_def = o.get();
+ if defn != *existing_def {
+ bail!(
+ "Mismatching definition for error `{}`!\n\
+ existing definition: {existing_def:#?},\n\
+ new definition: {defn:#?}",
+ defn.name(),
+ );
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Resolve unresolved types within proc-macro function / method signatures.
+ pub fn resolve_types(&mut self) -> Result<()> {
+ fn handle_unresolved_in(
+ ty: &mut Type,
+ f: impl Fn(&str) -> Result<Type> + Clone,
+ ) -> Result<()> {
+ match ty {
+ Type::Unresolved { name } => {
+ *ty = f(name)?;
+ }
+ Type::Optional(inner) => {
+ handle_unresolved_in(inner, f)?;
+ }
+ Type::Sequence(inner) => {
+ handle_unresolved_in(inner, f)?;
+ }
+ Type::Map(k, v) => {
+ handle_unresolved_in(k, f.clone())?;
+ handle_unresolved_in(v, f)?;
+ }
+ _ => {}
+ }
+
+ Ok(())
+ }
+
+ let fn_sig_types = self.functions.iter_mut().flat_map(|fun| {
+ fun.arguments
+ .iter_mut()
+ .map(|arg| &mut arg.type_)
+ .chain(&mut fun.return_type)
+ });
+ let method_sig_types = self.objects.iter_mut().flat_map(|obj| {
+ obj.methods.iter_mut().flat_map(|m| {
+ m.arguments
+ .iter_mut()
+ .map(|arg| &mut arg.type_)
+ .chain(&mut m.return_type)
+ })
+ });
+
+ let record_fields_types = self
+ .records
+ .values_mut()
+ .flat_map(|r| r.fields.iter_mut().map(|f| &mut f.type_));
+ let enum_fields_types = self.enums.values_mut().flat_map(|e| {
+ e.variants
+ .iter_mut()
+ .flat_map(|r| r.fields.iter_mut().map(|f| &mut f.type_))
+ });
+
+ let possibly_unresolved_types = fn_sig_types
+ .chain(method_sig_types)
+ .chain(record_fields_types)
+ .chain(enum_fields_types);
+
+ for ty in possibly_unresolved_types {
+ handle_unresolved_in(ty, |unresolved_ty_name| {
+ match self.types.get_type_definition(unresolved_ty_name) {
+ Some(def) => {
+ assert!(
+ !matches!(&def, Type::Unresolved { .. }),
+ "unresolved types must not be part of TypeUniverse"
+ );
+ Ok(def)
+ }
+ None => bail!("Failed to resolve type `{unresolved_ty_name}`"),
+ }
+ })?;
+
+ // The proc-macro scanning metadata code doesn't add known types
+ // when they could contain unresolved types, so we have to do it
+ // here after replacing unresolveds.
+ self.types.add_known_type(ty)?;
+ }
+
+ Ok(())
+ }
+
+ /// Perform global consistency checks on the declared interface.
+ ///
+ /// This method checks for consistency problems in the declared interface
+ /// as a whole, and which can only be detected after we've finished defining
+ /// the entire interface.
+ pub fn check_consistency(&self) -> Result<()> {
+ if self.namespace.is_empty() {
+ bail!("missing namespace definition");
+ }
+
+ // To keep codegen tractable, enum variant names must not shadow type names.
+ for e in self.enums.values() {
+ for variant in &e.variants {
+ if self.types.get_type_definition(variant.name()).is_some() {
+ bail!(
+ "Enum variant names must not shadow type names: \"{}\"",
+ variant.name()
+ )
+ }
+ }
+ }
+
+ for ty in self.iter_types() {
+ match ty {
+ Type::Object(name) => {
+ ensure!(
+ self.objects.iter().any(|o| o.name == *name),
+ "Object `{name}` has no definition",
+ );
+ }
+ Type::Record(name) => {
+ ensure!(
+ self.records.contains_key(name),
+ "Record `{name}` has no definition",
+ );
+ }
+ Type::Enum(name) => {
+ ensure!(
+ self.enums.contains_key(name),
+ "Enum `{name}` has no definition",
+ );
+ }
+ Type::Unresolved { name } => {
+ bail!("Type `{name}` should be resolved at this point");
+ }
+ _ => {}
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Automatically derive the low-level FFI functions from the high-level types in the interface.
+ ///
+ /// This should only be called after the high-level types have been completed defined, otherwise
+ /// the resulting set will be missing some entries.
+ pub fn derive_ffi_funcs(&mut self) -> Result<()> {
+ let ci_prefix = self.ffi_namespace().to_owned();
+ for func in self.functions.iter_mut() {
+ func.derive_ffi_func(&ci_prefix)?;
+ }
+ for obj in self.objects.iter_mut() {
+ obj.derive_ffi_funcs(&ci_prefix)?;
+ }
+ for callback in self.callback_interfaces.iter_mut() {
+ callback.derive_ffi_funcs(&ci_prefix);
+ }
+ Ok(())
+ }
+}
+
+fn get_or_insert_object<'a>(objects: &'a mut Vec<Object>, name: &str) -> &'a mut Object {
+ // The find-based way of writing this currently runs into a borrow checker
+ // error, so we use position
+ match objects.iter_mut().position(|o| o.name == name) {
+ Some(idx) => &mut objects[idx],
+ None => {
+ objects.push(Object::new(name.to_owned()));
+ objects.last_mut().unwrap()
+ }
+ }
+}
+
+/// Stateful iterator for yielding all types contained in a given type.
+///
+/// This struct is the implementation of [`ComponentInterface::iter_types_in_item`] and should be
+/// considered an opaque implementation detail. It's a separate struct because I couldn't
+/// figure out a way to implement it using iterators and closures that would make the lifetimes
+/// work out correctly.
+///
+/// The idea here is that we want to yield all the types from `iter_types` on a given type, and
+/// additionally we want to recurse into the definition of any user-provided types like records,
+/// enums, etc so we can also yield the types contained therein.
+///
+/// To guard against infinite recursion, we maintain a list of previously-seen user-defined
+/// types, ensuring that we recurse into the definition of those types only once. To simplify
+/// the implementation, we maintain a queue of pending user-defined types that we have seen
+/// but not yet recursed into. (Ironically, the use of an explicit queue means our implementation
+/// is not actually recursive...)
+struct RecursiveTypeIterator<'a> {
+ /// The [`ComponentInterface`] from which this iterator was created.
+ ci: &'a ComponentInterface,
+ /// The currently-active iterator from which we're yielding.
+ current: TypeIterator<'a>,
+ /// A set of names of user-defined types that we have already seen.
+ seen: HashSet<&'a str>,
+ /// A queue of user-defined types that we need to recurse into.
+ pending: Vec<&'a Type>,
+}
+
+impl<'a> RecursiveTypeIterator<'a> {
+ /// Allocate a new `RecursiveTypeIterator` over the given item.
+ fn new(ci: &'a ComponentInterface, item: &'a Type) -> RecursiveTypeIterator<'a> {
+ RecursiveTypeIterator {
+ ci,
+ // We begin by iterating over the types from the item itself.
+ current: item.iter_types(),
+ seen: Default::default(),
+ pending: Default::default(),
+ }
+ }
+
+ /// Add a new type to the queue of pending types, if not previously seen.
+ fn add_pending_type(&mut self, type_: &'a Type) {
+ match type_ {
+ Type::Record(nm)
+ | Type::Enum(nm)
+ | Type::Error(nm)
+ | Type::Object(nm)
+ | Type::CallbackInterface(nm) => {
+ if !self.seen.contains(nm.as_str()) {
+ self.pending.push(type_);
+ self.seen.insert(nm.as_str());
+ }
+ }
+ _ => (),
+ }
+ }
+
+ /// Advance the iterator to recurse into the next pending type, if any.
+ ///
+ /// This method is called when the current iterator is empty, and it will select
+ /// the next pending type from the queue and start iterating over its contained types.
+ /// The return value will be the first item from the new iterator.
+ fn advance_to_next_type(&mut self) -> Option<&'a Type> {
+ if let Some(next_type) = self.pending.pop() {
+ // This is a little awkward because the various definition lookup methods return an `Option<T>`.
+ // In the unlikely event that one of them returns `None` then, rather than trying to advance
+ // to a non-existent type, we just leave the existing iterator in place and allow the recursive
+ // call to `next()` to try again with the next pending type.
+ let next_iter = match next_type {
+ Type::Record(nm) => self.ci.get_record_definition(nm).map(Record::iter_types),
+ Type::Enum(nm) => self.ci.get_enum_definition(nm).map(Enum::iter_types),
+ Type::Error(nm) => self.ci.get_error_definition(nm).map(Error::iter_types),
+ Type::Object(nm) => self.ci.get_object_definition(nm).map(Object::iter_types),
+ Type::CallbackInterface(nm) => self
+ .ci
+ .get_callback_interface_definition(nm)
+ .map(CallbackInterface::iter_types),
+ _ => None,
+ };
+ if let Some(next_iter) = next_iter {
+ self.current = next_iter;
+ }
+ // Advance the new iterator to its first item. If the new iterator happens to be empty,
+ // this will recurse back in to `advance_to_next_type` until we find one that isn't.
+ self.next()
+ } else {
+ // We've completely finished the iteration over all pending types.
+ None
+ }
+ }
+}
+
+impl<'a> Iterator for RecursiveTypeIterator<'a> {
+ type Item = &'a Type;
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(type_) = self.current.next() {
+ self.add_pending_type(type_);
+ Some(type_)
+ } else {
+ self.advance_to_next_type()
+ }
+ }
+}
+
+/// Trait to help build a `ComponentInterface` from WedIDL syntax nodes.
+///
+/// This trait does structural matching on the various weedle AST nodes and
+/// uses them to build up the records, enums, objects etc in the provided
+/// `ComponentInterface`.
+trait APIBuilder {
+ fn process(&self, ci: &mut ComponentInterface) -> Result<()>;
+}
+
+/// Add to a `ComponentInterface` from a list of weedle definitions,
+/// by processing each in turn.
+impl<T: APIBuilder> APIBuilder for Vec<T> {
+ fn process(&self, ci: &mut ComponentInterface) -> Result<()> {
+ for item in self {
+ item.process(ci)?;
+ }
+ Ok(())
+ }
+}
+
+/// Add to a `ComponentInterface` from a weedle definition.
+/// This is conceptually the root of the parser, and dispatches to implementations
+/// for the various specific WebIDL types that we support.
+impl APIBuilder for weedle::Definition<'_> {
+ fn process(&self, ci: &mut ComponentInterface) -> Result<()> {
+ match self {
+ weedle::Definition::Namespace(d) => d.process(ci)?,
+ weedle::Definition::Enum(d) => {
+ // We check if the enum represents an error...
+ let attrs = attributes::EnumAttributes::try_from(d.attributes.as_ref())?;
+ if attrs.contains_error_attr() {
+ let err = d.convert(ci)?;
+ ci.add_error_definition(err)?;
+ } else {
+ let e = d.convert(ci)?;
+ ci.add_enum_definition(e)?;
+ }
+ }
+ weedle::Definition::Dictionary(d) => {
+ let rec = d.convert(ci)?;
+ ci.add_record_definition(rec)?;
+ }
+ weedle::Definition::Interface(d) => {
+ let attrs = attributes::InterfaceAttributes::try_from(d.attributes.as_ref())?;
+ if attrs.contains_enum_attr() {
+ let e = d.convert(ci)?;
+ ci.add_enum_definition(e)?;
+ } else if attrs.contains_error_attr() {
+ let e = d.convert(ci)?;
+ ci.add_error_definition(e)?;
+ } else {
+ let obj = d.convert(ci)?;
+ ci.add_object_definition(obj);
+ }
+ }
+ weedle::Definition::CallbackInterface(d) => {
+ let obj = d.convert(ci)?;
+ ci.add_callback_interface_definition(obj);
+ }
+ // everything needed for typedefs is done in finder.rs.
+ weedle::Definition::Typedef(_) => {}
+ _ => bail!("don't know how to deal with {:?}", self),
+ }
+ Ok(())
+ }
+}
+
+/// Trait to help convert WedIDL syntax nodes into `ComponentInterface` objects.
+///
+/// This trait does structural matching on the various weedle AST nodes and converts
+/// them into appropriate structs that we can use to build up the contents of a
+/// `ComponentInterface`. It is basically the `TryFrom` trait except that the conversion
+/// always happens in the context of a given `ComponentInterface`, which is used for
+/// resolving e.g. type definitions.
+///
+/// The difference between this trait and `APIBuilder` is that `APIConverter` treats the
+/// `ComponentInterface` as a read-only data source for resolving types, while `APIBuilder`
+/// actually mutates the `ComponentInterface` to add new definitions.
+trait APIConverter<T> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<T>;
+}
+
+/// Convert a list of weedle items into a list of `ComponentInterface` items,
+/// by doing a direct item-by-item mapping.
+impl<U, T: APIConverter<U>> APIConverter<Vec<U>> for Vec<T> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Vec<U>> {
+ self.iter().map(|v| v.convert(ci)).collect::<Result<_>>()
+ }
+}
+
+fn convert_type(s: &uniffi_meta::Type) -> Type {
+ use uniffi_meta::Type as Ty;
+
+ match s {
+ Ty::U8 => Type::UInt8,
+ Ty::U16 => Type::UInt16,
+ Ty::U32 => Type::UInt32,
+ Ty::U64 => Type::UInt64,
+ Ty::I8 => Type::Int8,
+ Ty::I16 => Type::Int16,
+ Ty::I32 => Type::Int32,
+ Ty::I64 => Type::Int64,
+ Ty::F32 => Type::Float32,
+ Ty::F64 => Type::Float64,
+ Ty::Bool => Type::Boolean,
+ Ty::String => Type::String,
+ Ty::Option { inner_type } => Type::Optional(convert_type(inner_type).into()),
+ Ty::Vec { inner_type } => Type::Sequence(convert_type(inner_type).into()),
+ Ty::HashMap {
+ key_type,
+ value_type,
+ } => Type::Map(
+ convert_type(key_type).into(),
+ convert_type(value_type).into(),
+ ),
+ Ty::ArcObject { object_name } => Type::Object(object_name.clone()),
+ Ty::Unresolved { name } => Type::Unresolved { name: name.clone() },
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ // Note that much of the functionality of `ComponentInterface` is tested via its interactions
+ // with specific member types, in the sub-modules defining those member types.
+
+ const UDL1: &str = r#"
+ namespace foobar{};
+ enum Test {
+ "test_me",
+ };
+ "#;
+
+ const UDL2: &str = r#"
+ namespace hello {
+ u64 world();
+ };
+ dictionary Test {
+ boolean me;
+ };
+ "#;
+
+ #[test]
+ fn test_checksum_always_matches_for_same_webidl() {
+ for udl in &[UDL1, UDL2] {
+ let ci1 = ComponentInterface::from_webidl(udl).unwrap();
+ let ci2 = ComponentInterface::from_webidl(udl).unwrap();
+ assert_eq!(ci1.checksum(), ci2.checksum());
+ }
+ }
+
+ #[test]
+ fn test_checksum_differs_for_different_webidl() {
+ // There is a small probability of this test spuriously failing due to hash collision.
+ // If it happens often enough to be a problem, probably this whole "checksum" thing
+ // is not working out as intended.
+ let ci1 = ComponentInterface::from_webidl(UDL1).unwrap();
+ let ci2 = ComponentInterface::from_webidl(UDL2).unwrap();
+ assert_ne!(ci1.checksum(), ci2.checksum());
+ }
+
+ #[test]
+ fn test_checksum_differs_for_different_uniffi_version() {
+ // There is a small probability of this test spuriously failing due to hash collision.
+ // If it happens often enough to be a problem, probably this whole "checksum" thing
+ // is not working out as intended.
+ for udl in &[UDL1, UDL2] {
+ let ci1 = ComponentInterface::from_webidl(udl).unwrap();
+ let mut ci2 = ComponentInterface::from_webidl(udl).unwrap();
+ ci2.uniffi_version = "99.99";
+ assert_ne!(ci1.checksum(), ci2.checksum());
+ }
+ }
+
+ #[test]
+ fn test_duplicate_type_names_are_an_error() {
+ const UDL: &str = r#"
+ namespace test{};
+ interface Testing {
+ constructor();
+ };
+ dictionary Testing {
+ u32 field;
+ };
+ "#;
+ let err = ComponentInterface::from_webidl(UDL).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "Conflicting type definition for `Testing`! \
+ existing definition: Object(\"Testing\"), \
+ new definition: Record(\"Testing\")"
+ );
+
+ const UDL2: &str = r#"
+ namespace test{};
+ enum Testing {
+ "one", "two"
+ };
+ [Error]
+ enum Testing { "three", "four" };
+ "#;
+ let err = ComponentInterface::from_webidl(UDL2).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "Conflicting type definition for `Testing`! \
+ existing definition: Enum(\"Testing\"), \
+ new definition: Error(\"Testing\")"
+ );
+
+ const UDL3: &str = r#"
+ namespace test{
+ u32 Testing();
+ };
+ enum Testing {
+ "one", "two"
+ };
+ "#;
+ let err = ComponentInterface::from_webidl(UDL3).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "Conflicting type definition for \"Testing\""
+ );
+ }
+
+ #[test]
+ fn test_enum_variant_names_dont_shadow_types() {
+ // There are some edge-cases during codegen where we don't know how to disambiguate
+ // between an enum variant reference and a top-level type reference, so we
+ // disallow it in order to give a more scrutable error to the consumer.
+ const UDL: &str = r#"
+ namespace test{};
+ interface Testing {
+ constructor();
+ };
+ [Enum]
+ interface HardToCodegenFor {
+ Testing();
+ OtherVariant(u32 field);
+ };
+ "#;
+ let err = ComponentInterface::from_webidl(UDL).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "Enum variant names must not shadow type names: \"Testing\""
+ );
+ }
+
+ #[test]
+ fn test_contains_optional_types() {
+ let mut ci = ComponentInterface {
+ ..Default::default()
+ };
+
+ // check that `contains_optional_types` returns false when there is no Optional type in the interface
+ assert!(!ci.contains_optional_types());
+
+ // check that `contains_optional_types` returns true when there is an Optional type in the interface
+ assert!(ci
+ .types
+ .add_type_definition("TestOptional{}", Type::Optional(Box::new(Type::String)))
+ .is_ok());
+ assert!(ci.contains_optional_types());
+ }
+
+ #[test]
+ fn test_contains_sequence_types() {
+ let mut ci = ComponentInterface {
+ ..Default::default()
+ };
+
+ // check that `contains_sequence_types` returns false when there is no Sequence type in the interface
+ assert!(!ci.contains_sequence_types());
+
+ // check that `contains_sequence_types` returns true when there is a Sequence type in the interface
+ assert!(ci
+ .types
+ .add_type_definition("TestSequence{}", Type::Sequence(Box::new(Type::UInt64)))
+ .is_ok());
+ assert!(ci.contains_sequence_types());
+ }
+
+ #[test]
+ fn test_contains_map_types() {
+ let mut ci = ComponentInterface {
+ ..Default::default()
+ };
+
+ // check that `contains_map_types` returns false when there is no Map type in the interface
+ assert!(!ci.contains_map_types());
+
+ // check that `contains_map_types` returns true when there is a Map type in the interface
+ assert!(ci
+ .types
+ .add_type_definition(
+ "Map{}",
+ Type::Map(Box::new(Type::String), Box::new(Type::Boolean))
+ )
+ .is_ok());
+ assert!(ci.contains_map_types());
+ }
+
+ #[test]
+ fn test_no_infinite_recursion_when_walking_types() {
+ const UDL: &str = r#"
+ namespace test{};
+ interface Testing {
+ void tester(Testing foo);
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert!(!ci.item_contains_unsigned_types(&Type::Object("Testing".into())));
+ }
+
+ #[test]
+ fn test_correct_recursion_when_walking_types() {
+ const UDL: &str = r#"
+ namespace test{};
+ interface TestObj {
+ void tester(TestRecord foo);
+ };
+ dictionary TestRecord {
+ NestedRecord bar;
+ };
+ dictionary NestedRecord {
+ u64 baz;
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert!(ci.item_contains_unsigned_types(&Type::Object("TestObj".into())));
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/namespace.rs b/third_party/rust/uniffi_bindgen/src/interface/namespace.rs
new file mode 100644
index 0000000000..4a57d0ff41
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/namespace.rs
@@ -0,0 +1,132 @@
+/* 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/. */
+
+//! # Namespace definition for a `ComponentInterface`.
+//!
+//! This module converts a namespace definition from UDL into structures that
+//! can be added to a `ComponentInterface`.
+//!
+//! In WebIDL proper, each `namespace` declares a set of functions and attributes that
+//! are exposed as a global object of that name, and there can be any number of such
+//! namespace definitions.
+//!
+//! For our purposes with UDL, we expect just a single `namespace` declaration, which
+//! defines properties of the component as a whole (currently just the name). It also
+//! contains the functions that will be exposed as individual plain functions exported by
+//! the component, if any. So something like this:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! namespace example {
+//! string hello();
+//! };
+//! # "##)?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Declares a component named "example" with a single exported function named "hello":
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {
+//! # string hello();
+//! # };
+//! # "##)?;
+//! assert_eq!(ci.namespace(), "example");
+//! assert_eq!(ci.get_function_definition("hello").unwrap().name(), "hello");
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! While this awkward-looking syntax:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! namespace example {};
+//! # "##)?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Declares a component named "example" with no exported functions:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! # "##)?;
+//! assert_eq!(ci.namespace(), "example");
+//! assert_eq!(ci.function_definitions().len(), 0);
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Yeah, it's a bit of an awkward fit syntactically, but it's enough
+//! to get us up and running for a first version of this tool.
+
+use anyhow::{bail, Result};
+
+use super::{APIBuilder, APIConverter, ComponentInterface};
+
+/// A namespace is currently just a name, but might hold more metadata about
+/// the component in future.
+///
+#[derive(Debug, Clone, Hash)]
+pub struct Namespace {
+ pub(super) name: String,
+}
+
+impl APIBuilder for weedle::NamespaceDefinition<'_> {
+ fn process(&self, ci: &mut ComponentInterface) -> Result<()> {
+ if self.attributes.is_some() {
+ bail!("namespace attributes are not supported yet");
+ }
+ ci.add_namespace_definition(Namespace {
+ name: self.identifier.0.to_string(),
+ })?;
+ for func in self.members.body.convert(ci)? {
+ ci.add_function_definition(func)?;
+ }
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_empty_namespace() {
+ const UDL: &str = r#"
+ namespace foobar{};
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.namespace(), "foobar");
+ }
+
+ #[test]
+ fn test_namespace_with_functions() {
+ const UDL: &str = r#"
+ namespace foobar{
+ boolean hello();
+ void world();
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.namespace(), "foobar");
+ assert_eq!(ci.function_definitions().len(), 2);
+ assert!(ci.get_function_definition("hello").is_some());
+ assert!(ci.get_function_definition("world").is_some());
+ assert!(ci.get_function_definition("potato").is_none());
+ }
+
+ #[test]
+ fn test_rejects_duplicate_namespaces() {
+ const UDL: &str = r#"
+ namespace foobar{
+ boolean hello();
+ void world();
+ };
+ namespace something_else{};
+ "#;
+ let err = ComponentInterface::from_webidl(UDL).unwrap_err();
+ assert_eq!(err.to_string(), "duplicate namespace definition");
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/object.rs b/third_party/rust/uniffi_bindgen/src/interface/object.rs
new file mode 100644
index 0000000000..e8ba089261
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/object.rs
@@ -0,0 +1,599 @@
+/* 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/. */
+
+//! # Object definitions for a `ComponentInterface`.
+//!
+//! This module converts "interface" definitions from UDL into [`Object`] structures
+//! that can be added to a `ComponentInterface`, which are the main way we define stateful
+//! objects with behaviour for a UniFFI Rust Component. An [`Object`] is an opaque handle
+//! to some state on which methods can be invoked.
+//!
+//! (The terminology mismatch between "interface" and "object" is a historical artifact of
+//! this tool prior to committing to WebIDL syntax).
+//!
+//! A declaration in the UDL like this:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! interface Example {
+//! constructor(string? name);
+//! string my_name();
+//! };
+//! # "##)?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Will result in an [`Object`] member with one [`Constructor`] and one [`Method`] being added
+//! to the resulting [`ComponentInterface`]:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! # interface Example {
+//! # constructor(string? name);
+//! # string my_name();
+//! # };
+//! # "##)?;
+//! let obj = ci.get_object_definition("Example").unwrap();
+//! assert_eq!(obj.name(), "Example");
+//! assert_eq!(obj.constructors().len(), 1);
+//! assert_eq!(obj.constructors()[0].arguments()[0].name(), "name");
+//! assert_eq!(obj.methods().len(),1 );
+//! assert_eq!(obj.methods()[0].name(), "my_name");
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! It's not necessary for all interfaces to have constructors.
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! # interface Example {};
+//! # "##)?;
+//! let obj = ci.get_object_definition("Example").unwrap();
+//! assert_eq!(obj.name(), "Example");
+//! assert_eq!(obj.constructors().len(), 0);
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+
+use std::convert::TryFrom;
+use std::{collections::HashSet, iter};
+
+use anyhow::{bail, Result};
+use uniffi_meta::Checksum;
+
+use super::attributes::{Attribute, ConstructorAttributes, InterfaceAttributes, MethodAttributes};
+use super::ffi::{FfiArgument, FfiFunction, FfiType};
+use super::function::Argument;
+use super::types::{Type, TypeIterator};
+use super::{convert_type, APIConverter, ComponentInterface};
+
+/// An "object" is an opaque type that can be instantiated and passed around by reference,
+/// have methods called on it, and so on - basically your classic Object Oriented Programming
+/// type of deal, except without elaborate inheritance hierarchies.
+///
+/// In UDL these correspond to the `interface` keyword.
+///
+/// At the FFI layer, objects are represented by an opaque integer handle and a set of functions
+/// a common prefix. The object's constructors are functions that return new objects by handle,
+/// and its methods are functions that take a handle as first argument. The foreign language
+/// binding code is expected to stitch these functions back together into an appropriate class
+/// definition (or that language's equivalent thereof).
+///
+/// TODO:
+/// - maybe "Class" would be a better name than "Object" here?
+#[derive(Debug, Clone, Checksum)]
+pub struct Object {
+ pub(super) name: String,
+ pub(super) constructors: Vec<Constructor>,
+ pub(super) methods: Vec<Method>,
+ // We don't include the FFIFunc in the hash calculation, because:
+ // - it is entirely determined by the other fields,
+ // so excluding it is safe.
+ // - its `name` property includes a checksum derived from the very
+ // hash value we're trying to calculate here, so excluding it
+ // avoids a weird circular dependency in the calculation.
+ #[checksum_ignore]
+ pub(super) ffi_func_free: FfiFunction,
+ #[checksum_ignore]
+ pub(super) uses_deprecated_threadsafe_attribute: bool,
+}
+
+impl Object {
+ pub(super) fn new(name: String) -> Object {
+ Object {
+ name,
+ constructors: Default::default(),
+ methods: Default::default(),
+ ffi_func_free: Default::default(),
+ uses_deprecated_threadsafe_attribute: false,
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn type_(&self) -> Type {
+ Type::Object(self.name.clone())
+ }
+
+ pub fn constructors(&self) -> Vec<&Constructor> {
+ self.constructors.iter().collect()
+ }
+
+ pub fn primary_constructor(&self) -> Option<&Constructor> {
+ self.constructors
+ .iter()
+ .find(|cons| cons.is_primary_constructor())
+ }
+
+ pub fn alternate_constructors(&self) -> Vec<&Constructor> {
+ self.constructors
+ .iter()
+ .filter(|cons| !cons.is_primary_constructor())
+ .collect()
+ }
+
+ pub fn methods(&self) -> Vec<&Method> {
+ self.methods.iter().collect()
+ }
+
+ pub fn get_method(&self, name: &str) -> Method {
+ let matches: Vec<_> = self.methods.iter().filter(|m| m.name() == name).collect();
+ match matches.len() {
+ 1 => matches[0].clone(),
+ n => panic!("{n} methods named {name}"),
+ }
+ }
+
+ pub fn ffi_object_free(&self) -> &FfiFunction {
+ &self.ffi_func_free
+ }
+
+ pub fn uses_deprecated_threadsafe_attribute(&self) -> bool {
+ self.uses_deprecated_threadsafe_attribute
+ }
+
+ pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = &FfiFunction> {
+ iter::once(&self.ffi_func_free)
+ .chain(self.constructors.iter().map(|f| &f.ffi_func))
+ .chain(self.methods.iter().map(|f| &f.ffi_func))
+ }
+
+ pub fn derive_ffi_funcs(&mut self, ci_prefix: &str) -> Result<()> {
+ // The name is already set if the function is defined through a proc-macro invocation
+ // rather than in UDL. Don't overwrite it in that case.
+ if self.ffi_func_free.name().is_empty() {
+ self.ffi_func_free.name = format!("ffi_{ci_prefix}_{}_object_free", self.name);
+ }
+ self.ffi_func_free.arguments = vec![FfiArgument {
+ name: "ptr".to_string(),
+ type_: FfiType::RustArcPtr(self.name().to_string()),
+ }];
+ self.ffi_func_free.return_type = None;
+
+ for cons in self.constructors.iter_mut() {
+ cons.derive_ffi_func(ci_prefix, &self.name);
+ }
+ for meth in self.methods.iter_mut() {
+ meth.derive_ffi_func(ci_prefix, &self.name)?;
+ }
+
+ Ok(())
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ Box::new(
+ self.methods
+ .iter()
+ .map(Method::iter_types)
+ .chain(self.constructors.iter().map(Constructor::iter_types))
+ .flatten(),
+ )
+ }
+}
+
+impl APIConverter<Object> for weedle::InterfaceDefinition<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Object> {
+ if self.inheritance.is_some() {
+ bail!("interface inheritance is not supported");
+ }
+ let mut object = Object::new(self.identifier.0.to_string());
+ let attributes = match &self.attributes {
+ Some(attrs) => InterfaceAttributes::try_from(attrs)?,
+ None => Default::default(),
+ };
+ object.uses_deprecated_threadsafe_attribute = attributes.threadsafe();
+ // Convert each member into a constructor or method, guarding against duplicate names.
+ let mut member_names = HashSet::new();
+ for member in &self.members.body {
+ match member {
+ weedle::interface::InterfaceMember::Constructor(t) => {
+ let cons: Constructor = t.convert(ci)?;
+ if !member_names.insert(cons.name.clone()) {
+ bail!("Duplicate interface member name: \"{}\"", cons.name())
+ }
+ object.constructors.push(cons);
+ }
+ weedle::interface::InterfaceMember::Operation(t) => {
+ let mut method: Method = t.convert(ci)?;
+ if !member_names.insert(method.name.clone()) {
+ bail!("Duplicate interface member name: \"{}\"", method.name())
+ }
+ method.object_name = object.name.clone();
+ object.methods.push(method);
+ }
+ _ => bail!("no support for interface member type {:?} yet", member),
+ }
+ }
+ Ok(object)
+ }
+}
+
+// Represents a constructor for an object type.
+//
+// In the FFI, this will be a function that returns a pointer to an instance
+// of the corresponding object type.
+#[derive(Debug, Clone, Checksum)]
+pub struct Constructor {
+ pub(super) name: String,
+ pub(super) arguments: Vec<Argument>,
+ // We don't include the FFIFunc in the hash calculation, because:
+ // - it is entirely determined by the other fields,
+ // so excluding it is safe.
+ // - its `name` property includes a checksum derived from the very
+ // hash value we're trying to calculate here, so excluding it
+ // avoids a weird circular dependency in the calculation.
+ #[checksum_ignore]
+ pub(super) ffi_func: FfiFunction,
+ pub(super) attributes: ConstructorAttributes,
+}
+
+impl Constructor {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn arguments(&self) -> Vec<&Argument> {
+ self.arguments.iter().collect()
+ }
+
+ pub fn full_arguments(&self) -> Vec<Argument> {
+ self.arguments.to_vec()
+ }
+
+ pub fn ffi_func(&self) -> &FfiFunction {
+ &self.ffi_func
+ }
+
+ pub fn throws(&self) -> bool {
+ self.attributes.get_throws_err().is_some()
+ }
+
+ pub fn throws_name(&self) -> Option<&str> {
+ self.attributes.get_throws_err()
+ }
+
+ pub fn throws_type(&self) -> Option<Type> {
+ self.attributes
+ .get_throws_err()
+ .map(|name| Type::Error(name.to_owned()))
+ }
+
+ pub fn is_primary_constructor(&self) -> bool {
+ self.name == "new"
+ }
+
+ fn derive_ffi_func(&mut self, ci_prefix: &str, obj_name: &str) {
+ self.ffi_func.name = format!("{ci_prefix}_{obj_name}_{}", self.name);
+ self.ffi_func.arguments = self.arguments.iter().map(Into::into).collect();
+ self.ffi_func.return_type = Some(FfiType::RustArcPtr(obj_name.to_string()));
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ Box::new(self.arguments.iter().flat_map(Argument::iter_types))
+ }
+}
+
+impl Default for Constructor {
+ fn default() -> Self {
+ Constructor {
+ name: String::from("new"),
+ arguments: Vec::new(),
+ ffi_func: Default::default(),
+ attributes: Default::default(),
+ }
+ }
+}
+
+impl APIConverter<Constructor> for weedle::interface::ConstructorInterfaceMember<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Constructor> {
+ let attributes = match &self.attributes {
+ Some(attr) => ConstructorAttributes::try_from(attr)?,
+ None => Default::default(),
+ };
+ Ok(Constructor {
+ name: String::from(attributes.get_name().unwrap_or("new")),
+ arguments: self.args.body.list.convert(ci)?,
+ ffi_func: Default::default(),
+ attributes,
+ })
+ }
+}
+
+// Represents an instance method for an object type.
+//
+// The FFI will represent this as a function whose first/self argument is a
+// `FfiType::RustArcPtr` to the instance.
+#[derive(Debug, Clone, Checksum)]
+pub struct Method {
+ pub(super) name: String,
+ pub(super) object_name: String,
+ pub(super) arguments: Vec<Argument>,
+ pub(super) return_type: Option<Type>,
+ // We don't include the FFIFunc in the hash calculation, because:
+ // - it is entirely determined by the other fields,
+ // so excluding it is safe.
+ // - its `name` property includes a checksum derived from the very
+ // hash value we're trying to calculate here, so excluding it
+ // avoids a weird circular dependency in the calculation.
+ #[checksum_ignore]
+ pub(super) ffi_func: FfiFunction,
+ pub(super) attributes: MethodAttributes,
+}
+
+impl Method {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn arguments(&self) -> Vec<&Argument> {
+ self.arguments.iter().collect()
+ }
+
+ // Methods have a special implicit first argument for the object instance,
+ // hence `arguments` and `full_arguments` are different.
+ pub fn full_arguments(&self) -> Vec<Argument> {
+ vec![Argument {
+ name: "ptr".to_string(),
+ // TODO: ideally we'd get this via `ci.resolve_type_expression` so that it
+ // is contained in the proper `TypeUniverse`, but this works for now.
+ type_: Type::Object(self.object_name.clone()),
+ by_ref: !self.attributes.get_self_by_arc(),
+ optional: false,
+ default: None,
+ }]
+ .into_iter()
+ .chain(self.arguments.iter().cloned())
+ .collect()
+ }
+
+ pub fn return_type(&self) -> Option<&Type> {
+ self.return_type.as_ref()
+ }
+
+ pub fn ffi_func(&self) -> &FfiFunction {
+ &self.ffi_func
+ }
+
+ pub fn throws(&self) -> bool {
+ self.attributes.get_throws_err().is_some()
+ }
+
+ pub fn throws_name(&self) -> Option<&str> {
+ self.attributes.get_throws_err()
+ }
+
+ pub fn throws_type(&self) -> Option<Type> {
+ self.attributes
+ .get_throws_err()
+ .map(|name| Type::Error(name.to_owned()))
+ }
+
+ pub fn takes_self_by_arc(&self) -> bool {
+ self.attributes.get_self_by_arc()
+ }
+
+ pub fn derive_ffi_func(&mut self, ci_prefix: &str, obj_prefix: &str) -> Result<()> {
+ // The name is already set if the function is defined through a proc-macro invocation
+ // rather than in UDL. Don't overwrite it in that case.
+ if self.ffi_func.name.is_empty() {
+ self.ffi_func.name = format!("{ci_prefix}_{obj_prefix}_{}", self.name);
+ }
+
+ self.ffi_func.arguments = self.full_arguments().iter().map(Into::into).collect();
+ self.ffi_func.return_type = self.return_type.as_ref().map(Into::into);
+ Ok(())
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ Box::new(
+ self.arguments
+ .iter()
+ .flat_map(Argument::iter_types)
+ .chain(self.return_type.iter().flat_map(Type::iter_types)),
+ )
+ }
+}
+
+impl From<uniffi_meta::MethodMetadata> for Method {
+ fn from(meta: uniffi_meta::MethodMetadata) -> Self {
+ let ffi_name = meta.ffi_symbol_name();
+
+ let return_type = meta.return_type.map(|out| convert_type(&out));
+ let arguments = meta.inputs.into_iter().map(Into::into).collect();
+
+ let ffi_func = FfiFunction {
+ name: ffi_name,
+ ..FfiFunction::default()
+ };
+
+ Self {
+ name: meta.name,
+ object_name: meta.self_name,
+ arguments,
+ return_type,
+ ffi_func,
+ attributes: meta.throws.map(Attribute::Throws).into_iter().collect(),
+ }
+ }
+}
+
+impl APIConverter<Method> for weedle::interface::OperationInterfaceMember<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Method> {
+ if self.special.is_some() {
+ bail!("special operations not supported");
+ }
+ if self.modifier.is_some() {
+ bail!("method modifiers are not supported")
+ }
+ let return_type = ci.resolve_return_type_expression(&self.return_type)?;
+ Ok(Method {
+ name: match self.identifier {
+ None => bail!("anonymous methods are not supported {:?}", self),
+ Some(id) => {
+ let name = id.0.to_string();
+ if name == "new" {
+ bail!("the method name \"new\" is reserved for the default constructor");
+ }
+ name
+ }
+ },
+ // We don't know the name of the containing `Object` at this point, fill it in later.
+ object_name: Default::default(),
+ arguments: self.args.body.list.convert(ci)?,
+ return_type,
+ ffi_func: Default::default(),
+ attributes: MethodAttributes::try_from(self.attributes.as_ref())?,
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_that_all_argument_and_return_types_become_known() {
+ const UDL: &str = r#"
+ namespace test{};
+ interface Testing {
+ constructor(string? name, u16 age);
+ sequence<u32> code_points_of_name();
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.object_definitions().len(), 1);
+ ci.get_object_definition("Testing").unwrap();
+
+ assert_eq!(ci.iter_types().count(), 6);
+ assert!(ci.iter_types().any(|t| t.canonical_name() == "u16"));
+ assert!(ci.iter_types().any(|t| t.canonical_name() == "u32"));
+ assert!(ci.iter_types().any(|t| t.canonical_name() == "Sequenceu32"));
+ assert!(ci.iter_types().any(|t| t.canonical_name() == "string"));
+ assert!(ci
+ .iter_types()
+ .any(|t| t.canonical_name() == "Optionalstring"));
+ assert!(ci.iter_types().any(|t| t.canonical_name() == "TypeTesting"));
+ }
+
+ #[test]
+ fn test_alternate_constructors() {
+ const UDL: &str = r#"
+ namespace test{};
+ interface Testing {
+ constructor();
+ [Name=new_with_u32]
+ constructor(u32 v);
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.object_definitions().len(), 1);
+
+ let obj = ci.get_object_definition("Testing").unwrap();
+ assert!(obj.primary_constructor().is_some());
+ assert_eq!(obj.alternate_constructors().len(), 1);
+ assert_eq!(obj.methods().len(), 0);
+
+ let cons = obj.primary_constructor().unwrap();
+ assert_eq!(cons.name(), "new");
+ assert_eq!(cons.arguments.len(), 0);
+ assert_eq!(cons.ffi_func.arguments.len(), 0);
+
+ let cons = obj.alternate_constructors()[0];
+ assert_eq!(cons.name(), "new_with_u32");
+ assert_eq!(cons.arguments.len(), 1);
+ assert_eq!(cons.ffi_func.arguments.len(), 1);
+ }
+
+ #[test]
+ fn test_the_name_new_identifies_the_primary_constructor() {
+ const UDL: &str = r#"
+ namespace test{};
+ interface Testing {
+ [Name=newish]
+ constructor();
+ [Name=new]
+ constructor(u32 v);
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.object_definitions().len(), 1);
+
+ let obj = ci.get_object_definition("Testing").unwrap();
+ assert!(obj.primary_constructor().is_some());
+ assert_eq!(obj.alternate_constructors().len(), 1);
+ assert_eq!(obj.methods().len(), 0);
+
+ let cons = obj.primary_constructor().unwrap();
+ assert_eq!(cons.name(), "new");
+ assert_eq!(cons.arguments.len(), 1);
+
+ let cons = obj.alternate_constructors()[0];
+ assert_eq!(cons.name(), "newish");
+ assert_eq!(cons.arguments.len(), 0);
+ assert_eq!(cons.ffi_func.arguments.len(), 0);
+ }
+
+ #[test]
+ fn test_the_name_new_is_reserved_for_constructors() {
+ const UDL: &str = r#"
+ namespace test{};
+ interface Testing {
+ constructor();
+ void new(u32 v);
+ };
+ "#;
+ let err = ComponentInterface::from_webidl(UDL).unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "the method name \"new\" is reserved for the default constructor"
+ );
+ }
+
+ #[test]
+ fn test_duplicate_primary_constructors_not_allowed() {
+ const UDL: &str = r#"
+ namespace test{};
+ interface Testing {
+ constructor();
+ constructor(u32 v);
+ };
+ "#;
+ let err = ComponentInterface::from_webidl(UDL).unwrap_err();
+ assert_eq!(err.to_string(), "Duplicate interface member name: \"new\"");
+
+ const UDL2: &str = r#"
+ namespace test{};
+ interface Testing {
+ constructor();
+ [Name=new]
+ constructor(u32 v);
+ };
+ "#;
+ let err = ComponentInterface::from_webidl(UDL2).unwrap_err();
+ assert_eq!(err.to_string(), "Duplicate interface member name: \"new\"");
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/record.rs b/third_party/rust/uniffi_bindgen/src/interface/record.rs
new file mode 100644
index 0000000000..b000510884
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/record.rs
@@ -0,0 +1,243 @@
+/* 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/. */
+
+//! # Record definitions for a `ComponentInterface`.
+//!
+//! This module converts "dictionary" definitions from UDL into [`Record`] structures
+//! that can be added to a `ComponentInterface`, which are the main way we define structured
+//! data types for a UniFFI Rust Component. A [`Record`] has a fixed set of named fields,
+//! each of a specific type.
+//!
+//! (The terminology mismatch between "dictionary" and "record" is a historical artifact
+//! due to this tool being loosely inspired by WebAssembly Interface Types, which used
+//! the term "record" for this sort of data).
+//!
+//! A declaration in the UDL like this:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! dictionary Example {
+//! string name;
+//! u32 value;
+//! };
+//! # "##)?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+//!
+//! Will result in a [`Record`] member with two [`Field`]s being added to the resulting
+//! [`ComponentInterface`]:
+//!
+//! ```
+//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
+//! # namespace example {};
+//! # dictionary Example {
+//! # string name;
+//! # u32 value;
+//! # };
+//! # "##)?;
+//! let record = ci.get_record_definition("Example").unwrap();
+//! assert_eq!(record.name(), "Example");
+//! assert_eq!(record.fields()[0].name(), "name");
+//! assert_eq!(record.fields()[1].name(), "value");
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+
+use anyhow::{bail, Result};
+use uniffi_meta::Checksum;
+
+use super::types::{Type, TypeIterator};
+use super::{
+ convert_type,
+ literal::{convert_default_value, Literal},
+};
+use super::{APIConverter, ComponentInterface};
+
+/// Represents a "data class" style object, for passing around complex values.
+///
+/// In the FFI these are represented as a byte buffer, which one side explicitly
+/// serializes the data into and the other serializes it out of. So I guess they're
+/// kind of like "pass by clone" values.
+#[derive(Debug, Clone, PartialEq, Eq, Checksum)]
+pub struct Record {
+ pub(super) name: String,
+ pub(super) fields: Vec<Field>,
+}
+
+impl Record {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn type_(&self) -> Type {
+ // *sigh* at the clone here, the relationship between a ComponentInterface
+ // and its contained types could use a bit of a cleanup.
+ Type::Record(self.name.clone())
+ }
+
+ pub fn fields(&self) -> &[Field] {
+ &self.fields
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ Box::new(self.fields.iter().flat_map(Field::iter_types))
+ }
+}
+
+impl From<uniffi_meta::RecordMetadata> for Record {
+ fn from(meta: uniffi_meta::RecordMetadata) -> Self {
+ Self {
+ name: meta.name,
+ fields: meta.fields.into_iter().map(Into::into).collect(),
+ }
+ }
+}
+
+impl APIConverter<Record> for weedle::DictionaryDefinition<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Record> {
+ if self.attributes.is_some() {
+ bail!("dictionary attributes are not supported yet");
+ }
+ if self.inheritance.is_some() {
+ bail!("dictionary inheritance is not supported");
+ }
+ Ok(Record {
+ name: self.identifier.0.to_string(),
+ fields: self.members.body.convert(ci)?,
+ })
+ }
+}
+
+// Represents an individual field on a Record.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Checksum)]
+pub struct Field {
+ pub(super) name: String,
+ pub(super) type_: Type,
+ pub(super) default: Option<Literal>,
+}
+
+impl Field {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn type_(&self) -> &Type {
+ &self.type_
+ }
+
+ pub fn default_value(&self) -> Option<&Literal> {
+ self.default.as_ref()
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ self.type_.iter_types()
+ }
+}
+
+impl From<uniffi_meta::FieldMetadata> for Field {
+ fn from(meta: uniffi_meta::FieldMetadata) -> Self {
+ Self {
+ name: meta.name,
+ type_: convert_type(&meta.ty),
+ default: None,
+ }
+ }
+}
+
+impl APIConverter<Field> for weedle::dictionary::DictionaryMember<'_> {
+ fn convert(&self, ci: &mut ComponentInterface) -> Result<Field> {
+ if self.attributes.is_some() {
+ bail!("dictionary member attributes are not supported yet");
+ }
+ let type_ = ci.resolve_type_expression(&self.type_)?;
+ let default = match self.default {
+ None => None,
+ Some(v) => Some(convert_default_value(&v.value, &type_)?),
+ };
+ Ok(Field {
+ name: self.identifier.0.to_string(),
+ type_,
+ default,
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::super::literal::Radix;
+ use super::*;
+
+ #[test]
+ fn test_multiple_record_types() {
+ const UDL: &str = r#"
+ namespace test{};
+ dictionary Empty {};
+ dictionary Simple {
+ u32 field;
+ };
+ dictionary Complex {
+ string? key;
+ u32 value = 0;
+ required boolean spin;
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.record_definitions().count(), 3);
+
+ let record = ci.get_record_definition("Empty").unwrap();
+ assert_eq!(record.name(), "Empty");
+ assert_eq!(record.fields().len(), 0);
+
+ let record = ci.get_record_definition("Simple").unwrap();
+ assert_eq!(record.name(), "Simple");
+ assert_eq!(record.fields().len(), 1);
+ assert_eq!(record.fields()[0].name(), "field");
+ assert_eq!(record.fields()[0].type_().canonical_name(), "u32");
+ assert!(record.fields()[0].default_value().is_none());
+
+ let record = ci.get_record_definition("Complex").unwrap();
+ assert_eq!(record.name(), "Complex");
+ assert_eq!(record.fields().len(), 3);
+ assert_eq!(record.fields()[0].name(), "key");
+ assert_eq!(
+ record.fields()[0].type_().canonical_name(),
+ "Optionalstring"
+ );
+ assert!(record.fields()[0].default_value().is_none());
+ assert_eq!(record.fields()[1].name(), "value");
+ assert_eq!(record.fields()[1].type_().canonical_name(), "u32");
+ assert!(matches!(
+ record.fields()[1].default_value(),
+ Some(Literal::UInt(0, Radix::Decimal, Type::UInt32))
+ ));
+ assert_eq!(record.fields()[2].name(), "spin");
+ assert_eq!(record.fields()[2].type_().canonical_name(), "bool");
+ assert!(record.fields()[2].default_value().is_none());
+ }
+
+ #[test]
+ fn test_that_all_field_types_become_known() {
+ const UDL: &str = r#"
+ namespace test{};
+ dictionary Testing {
+ string? maybe_name;
+ u32 value;
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL).unwrap();
+ assert_eq!(ci.record_definitions().count(), 1);
+ let record = ci.get_record_definition("Testing").unwrap();
+ assert_eq!(record.fields().len(), 2);
+ assert_eq!(record.fields()[0].name(), "maybe_name");
+ assert_eq!(record.fields()[1].name(), "value");
+
+ assert_eq!(ci.iter_types().count(), 4);
+ assert!(ci.iter_types().any(|t| t.canonical_name() == "u32"));
+ assert!(ci.iter_types().any(|t| t.canonical_name() == "string"));
+ assert!(ci
+ .iter_types()
+ .any(|t| t.canonical_name() == "Optionalstring"));
+ assert!(ci.iter_types().any(|t| t.canonical_name() == "TypeTesting"));
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/types/finder.rs b/third_party/rust/uniffi_bindgen/src/interface/types/finder.rs
new file mode 100644
index 0000000000..991f910a4d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/types/finder.rs
@@ -0,0 +1,244 @@
+/* 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/. */
+
+//! # Helpers for finding the named types defined in a UDL interface.
+//!
+//! This module provides the [`TypeFinder`] trait, an abstraction for walking
+//! the weedle parse tree, looking for type definitions, and accumulating them
+//! in a [`TypeUniverse`].
+//!
+//! The type-finding process only discovers very basic information about names
+//! and their corresponding types. For example, it can discover that "Foobar"
+//! names a Record, but it won't discover anything about the fields of that
+//! record.
+//!
+//! Factoring this functionality out into a separate phase makes the subsequent
+//! work of more *detailed* parsing of the UDL a lot simpler, we know how to resolve
+//! names to types when building up the full interface definition.
+
+use std::convert::TryFrom;
+
+use anyhow::{bail, Result};
+
+use super::super::attributes::{EnumAttributes, InterfaceAttributes, TypedefAttributes};
+use super::{Type, TypeUniverse};
+
+/// Trait to help with an early "type discovery" phase when processing the UDL.
+///
+/// This trait does structural matching against weedle AST nodes from a parsed
+/// UDL file, looking for all the newly-defined types in the file and accumulating
+/// them in the given `TypeUniverse`.
+pub(in super::super) trait TypeFinder {
+ fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()>;
+}
+
+impl<T: TypeFinder> TypeFinder for &[T] {
+ fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> {
+ for item in *self {
+ item.add_type_definitions_to(types)?;
+ }
+ Ok(())
+ }
+}
+
+impl TypeFinder for weedle::Definition<'_> {
+ fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> {
+ match self {
+ weedle::Definition::Interface(d) => d.add_type_definitions_to(types),
+ weedle::Definition::Dictionary(d) => d.add_type_definitions_to(types),
+ weedle::Definition::Enum(d) => d.add_type_definitions_to(types),
+ weedle::Definition::Typedef(d) => d.add_type_definitions_to(types),
+ weedle::Definition::CallbackInterface(d) => d.add_type_definitions_to(types),
+ _ => Ok(()),
+ }
+ }
+}
+
+impl TypeFinder for weedle::InterfaceDefinition<'_> {
+ fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> {
+ let name = self.identifier.0.to_string();
+ // Some enum types are defined using an `interface` with a special attribute.
+ if InterfaceAttributes::try_from(self.attributes.as_ref())?.contains_enum_attr() {
+ types.add_type_definition(self.identifier.0, Type::Enum(name))
+ } else if InterfaceAttributes::try_from(self.attributes.as_ref())?.contains_error_attr() {
+ types.add_type_definition(self.identifier.0, Type::Error(name))
+ } else {
+ types.add_type_definition(self.identifier.0, Type::Object(name))
+ }
+ }
+}
+
+impl TypeFinder for weedle::DictionaryDefinition<'_> {
+ fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> {
+ let name = self.identifier.0.to_string();
+ types.add_type_definition(self.identifier.0, Type::Record(name))
+ }
+}
+
+impl TypeFinder for weedle::EnumDefinition<'_> {
+ fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> {
+ let name = self.identifier.0.to_string();
+ // Our error types are defined using an `enum` with a special attribute.
+ if EnumAttributes::try_from(self.attributes.as_ref())?.contains_error_attr() {
+ types.add_type_definition(self.identifier.0, Type::Error(name))
+ } else {
+ types.add_type_definition(self.identifier.0, Type::Enum(name))
+ }
+ }
+}
+
+impl TypeFinder for weedle::TypedefDefinition<'_> {
+ fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> {
+ let name = self.identifier.0;
+ let attrs = TypedefAttributes::try_from(self.attributes.as_ref())?;
+ // If we wanted simple `typedef`s, it would be as easy as:
+ // > let t = types.resolve_type_expression(&self.type_)?;
+ // > types.add_type_definition(name, t)
+ // But we don't - `typedef`s are reserved for external types.
+ if attrs.is_custom() {
+ // A local type which wraps a builtin and for which we will generate an
+ // `FfiConverter` implementation.
+ let builtin = types.resolve_type_expression(&self.type_)?;
+ types.add_type_definition(
+ name,
+ Type::Custom {
+ name: name.to_string(),
+ builtin: builtin.into(),
+ },
+ )
+ } else {
+ // A crate which can supply an `FfiConverter`.
+ // We don't reference `self._type`, so ideally we could insist on it being
+ // the literal 'extern' but that's tricky
+ types.add_type_definition(
+ name,
+ Type::External {
+ name: name.to_string(),
+ crate_name: attrs.get_crate_name(),
+ },
+ )
+ }
+ }
+}
+
+impl TypeFinder for weedle::CallbackInterfaceDefinition<'_> {
+ fn add_type_definitions_to(&self, types: &mut TypeUniverse) -> Result<()> {
+ if self.attributes.is_some() {
+ bail!("no typedef attributes are currently supported");
+ }
+ let name = self.identifier.0.to_string();
+ types.add_type_definition(self.identifier.0, Type::CallbackInterface(name))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ // A helper to take valid UDL and a closure to check what's in it.
+ fn test_a_finding<F>(udl: &str, tester: F)
+ where
+ F: FnOnce(TypeUniverse),
+ {
+ let idl = weedle::parse(udl).unwrap();
+ let mut types = TypeUniverse::default();
+ types.add_type_definitions_from(idl.as_ref()).unwrap();
+ tester(types);
+ }
+
+ #[test]
+ fn test_type_finding() {
+ test_a_finding(
+ r#"
+ callback interface TestCallbacks {
+ string hello(u32 count);
+ };
+ "#,
+ |types| {
+ assert!(
+ matches!(types.get_type_definition("TestCallbacks").unwrap(), Type::CallbackInterface(nm) if nm == "TestCallbacks")
+ );
+ },
+ );
+
+ test_a_finding(
+ r#"
+ dictionary TestRecord {
+ u32 field;
+ };
+ "#,
+ |types| {
+ assert!(
+ matches!(types.get_type_definition("TestRecord").unwrap(), Type::Record(nm) if nm == "TestRecord")
+ );
+ },
+ );
+
+ test_a_finding(
+ r#"
+ enum TestItems { "one", "two" };
+
+ [Error]
+ enum TestError { "ErrorOne", "ErrorTwo" };
+ "#,
+ |types| {
+ assert!(
+ matches!(types.get_type_definition("TestItems").unwrap(), Type::Enum(nm) if nm == "TestItems")
+ );
+ assert!(
+ matches!(types.get_type_definition("TestError").unwrap(), Type::Error(nm) if nm == "TestError")
+ );
+ },
+ );
+
+ test_a_finding(
+ r#"
+ interface TestObject {
+ constructor();
+ };
+ "#,
+ |types| {
+ assert!(
+ matches!(types.get_type_definition("TestObject").unwrap(), Type::Object(nm) if nm == "TestObject")
+ );
+ },
+ );
+
+ test_a_finding(
+ r#"
+ [External="crate-name"]
+ typedef extern ExternalType;
+
+ [Custom]
+ typedef string CustomType;
+ "#,
+ |types| {
+ assert!(
+ matches!(types.get_type_definition("ExternalType").unwrap(), Type::External { name, crate_name }
+ if name == "ExternalType" && crate_name == "crate-name")
+ );
+ assert!(
+ matches!(types.get_type_definition("CustomType").unwrap(), Type::Custom { name, builtin }
+ if name == "CustomType" && builtin == Box::new(Type::String))
+ );
+ },
+ );
+ }
+
+ fn get_err(udl: &str) -> String {
+ let parsed = weedle::parse(udl).unwrap();
+ let mut types = TypeUniverse::default();
+ let err = types
+ .add_type_definitions_from(parsed.as_ref())
+ .unwrap_err();
+ err.to_string()
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_typedef_error_on_no_attr() {
+ // Sorry, still working out what we want for non-imported typedefs..
+ get_err("typedef string Custom;");
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/types/mod.rs b/third_party/rust/uniffi_bindgen/src/interface/types/mod.rs
new file mode 100644
index 0000000000..c31c9b3ebd
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/types/mod.rs
@@ -0,0 +1,342 @@
+/* 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/. */
+
+//! # Basic typesystem for defining a component interface.
+//!
+//! This module provides the "API-level" typesystem of a UniFFI Rust Component, that is,
+//! the types provided by the Rust implementation and consumed callers of the foreign language
+//! bindings. Think "objects" and "enums" and "records".
+//!
+//! The [`Type`] enum represents high-level types that would appear in the public API of
+//! a component, such as enums and records as well as primitives like ints and strings.
+//! The Rust code that implements a component, and the foreign language bindings that consume it,
+//! will both typically deal with such types as their core concern.
+//!
+//! The set of all [`Type`]s used in a component interface is represented by a `TypeUniverse`,
+//! which can be used by the bindings generator code to determine what type-related helper
+//! functions to emit for a given component.
+//!
+//! As a developer working on UniFFI itself, you're likely to spend a fair bit of time thinking
+//! about how these API-level types map into the lower-level types of the FFI layer as represented
+//! by the [`ffi::FfiType`](super::ffi::FfiType) enum, but that's a detail that is invisible to end users.
+
+use std::{collections::hash_map::Entry, collections::BTreeSet, collections::HashMap, iter};
+
+use anyhow::{bail, Result};
+use heck::ToUpperCamelCase;
+use uniffi_meta::Checksum;
+
+use super::ffi::FfiType;
+
+mod finder;
+pub(super) use finder::TypeFinder;
+mod resolver;
+pub(super) use resolver::{resolve_builtin_type, TypeResolver};
+
+/// Represents all the different high-level types that can be used in a component interface.
+/// At this level we identify user-defined types by name, without knowing any details
+/// of their internal structure apart from what type of thing they are (record, enum, etc).
+#[derive(Debug, Clone, Eq, PartialEq, Checksum, Ord, PartialOrd)]
+pub enum Type {
+ // Primitive types.
+ UInt8,
+ Int8,
+ UInt16,
+ Int16,
+ UInt32,
+ Int32,
+ UInt64,
+ Int64,
+ Float32,
+ Float64,
+ Boolean,
+ String,
+ Timestamp,
+ Duration,
+ // Types defined in the component API, each of which has a string name.
+ Object(String),
+ Record(String),
+ Enum(String),
+ Error(String),
+ CallbackInterface(String),
+ // Structurally recursive types.
+ Optional(Box<Type>),
+ Sequence(Box<Type>),
+ Map(Box<Type>, Box<Type>),
+ // An FfiConverter we `use` from an external crate
+ External { name: String, crate_name: String },
+ // Custom type on the scaffolding side
+ Custom { name: String, builtin: Box<Type> },
+ // An unresolved user-defined type inside a proc-macro exported function
+ // signature. Must be replaced by another type before bindings generation.
+ Unresolved { name: String },
+}
+
+impl Type {
+ /// Get the canonical, unique-within-this-component name for a type.
+ ///
+ /// When generating helper code for foreign language bindings, it's sometimes useful to be
+ /// able to name a particular type in order to e.g. call a helper function that is specific
+ /// to that type. We support this by defining a naming convention where each type gets a
+ /// unique canonical name, constructed recursively from the names of its component types (if any).
+ pub fn canonical_name(&self) -> String {
+ match self {
+ // Builtin primitive types, with plain old names.
+ Type::Int8 => "i8".into(),
+ Type::UInt8 => "u8".into(),
+ Type::Int16 => "i16".into(),
+ Type::UInt16 => "u16".into(),
+ Type::Int32 => "i32".into(),
+ Type::UInt32 => "u32".into(),
+ Type::Int64 => "i64".into(),
+ Type::UInt64 => "u64".into(),
+ Type::Float32 => "f32".into(),
+ Type::Float64 => "f64".into(),
+ Type::String => "string".into(),
+ Type::Boolean => "bool".into(),
+ // API defined types.
+ // Note that these all get unique names, and the parser ensures that the names do not
+ // conflict with a builtin type. We add a prefix to the name to guard against pathological
+ // cases like a record named `SequenceRecord` interfering with `sequence<Record>`.
+ // However, types that support importing all end up with the same prefix of "Type", so
+ // that the import handling code knows how to find the remote reference.
+ Type::Object(nm) => format!("Type{nm}"),
+ Type::Error(nm) => format!("Type{nm}"),
+ Type::Enum(nm) => format!("Type{nm}"),
+ Type::Record(nm) => format!("Type{nm}"),
+ Type::CallbackInterface(nm) => format!("CallbackInterface{nm}"),
+ Type::Timestamp => "Timestamp".into(),
+ Type::Duration => "Duration".into(),
+ // Recursive types.
+ // These add a prefix to the name of the underlying type.
+ // The component API definition cannot give names to recursive types, so as long as the
+ // prefixes we add here are all unique amongst themselves, then we have no chance of
+ // acccidentally generating name collisions.
+ Type::Optional(t) => format!("Optional{}", t.canonical_name()),
+ Type::Sequence(t) => format!("Sequence{}", t.canonical_name()),
+ Type::Map(k, v) => format!(
+ "Map{}{}",
+ k.canonical_name().to_upper_camel_case(),
+ v.canonical_name().to_upper_camel_case()
+ ),
+ // A type that exists externally.
+ Type::External { name, .. } | Type::Custom { name, .. } => format!("Type{name}"),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling canonical_name")
+ }
+ }
+ }
+
+ pub fn ffi_type(&self) -> FfiType {
+ self.into()
+ }
+
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ let nested_types = match self {
+ Type::Optional(t) | Type::Sequence(t) => t.iter_types(),
+ Type::Map(k, v) => Box::new(k.iter_types().chain(v.iter_types())),
+ _ => Box::new(iter::empty()),
+ };
+ Box::new(std::iter::once(self).chain(nested_types))
+ }
+}
+
+/// When passing data across the FFI, each `Type` value will be lowered into a corresponding
+/// `FfiType` value. This conversion tells you which one.
+///
+/// Note that the conversion is one-way - given an FfiType, it is not in general possible to
+/// tell what the corresponding Type is that it's being used to represent.
+impl From<&Type> for FfiType {
+ fn from(t: &Type) -> FfiType {
+ match t {
+ // Types that are the same map to themselves, naturally.
+ Type::UInt8 => FfiType::UInt8,
+ Type::Int8 => FfiType::Int8,
+ Type::UInt16 => FfiType::UInt16,
+ Type::Int16 => FfiType::Int16,
+ Type::UInt32 => FfiType::UInt32,
+ Type::Int32 => FfiType::Int32,
+ Type::UInt64 => FfiType::UInt64,
+ Type::Int64 => FfiType::Int64,
+ Type::Float32 => FfiType::Float32,
+ Type::Float64 => FfiType::Float64,
+ // Booleans lower into an Int8, to work around a bug in JNA.
+ Type::Boolean => FfiType::Int8,
+ // Strings are always owned rust values.
+ // We might add a separate type for borrowed strings in future.
+ Type::String => FfiType::RustBuffer(None),
+ // Objects are pointers to an Arc<>
+ Type::Object(name) => FfiType::RustArcPtr(name.to_owned()),
+ // Callback interfaces are passed as opaque integer handles.
+ Type::CallbackInterface(_) => FfiType::UInt64,
+ // Other types are serialized into a bytebuffer and deserialized on the other side.
+ Type::Enum(_)
+ | Type::Error(_)
+ | Type::Record(_)
+ | Type::Optional(_)
+ | Type::Sequence(_)
+ | Type::Map(_, _)
+ | Type::Timestamp
+ | Type::Duration => FfiType::RustBuffer(None),
+ Type::External { name, .. } => FfiType::RustBuffer(Some(name.clone())),
+ Type::Custom { builtin, .. } => FfiType::from(builtin.as_ref()),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before lowering to FfiType")
+ }
+ }
+ }
+}
+
+// Needed for rust scaffolding askama template
+impl From<&&Type> for FfiType {
+ fn from(ty: &&Type) -> Self {
+ (*ty).into()
+ }
+}
+
+/// The set of all possible types used in a particular component interface.
+///
+/// Every component API uses a finite number of types, including primitive types, API-defined
+/// types like records and enums, and recursive types such as sequences of the above. Our
+/// component API doesn't support fancy generics so this is a finitely-enumerable set, which
+/// is useful to be able to operate on explicitly.
+///
+/// You could imagine this struct doing some clever interning of names and so-on in future,
+/// to reduce the overhead of passing around [Type] instances. For now we just do a whole
+/// lot of cloning.
+#[derive(Debug, Default)]
+pub(crate) struct TypeUniverse {
+ // Named type definitions (including aliases).
+ type_definitions: HashMap<String, Type>,
+ // All the types in the universe, by canonical type name, in a well-defined order.
+ all_known_types: BTreeSet<Type>,
+}
+
+impl TypeUniverse {
+ /// Add the definitions of all named [Type]s from a given WebIDL definition.
+ ///
+ /// This will fail if you try to add a name for which an existing type definition exists.
+ pub(super) fn add_type_definitions_from<T: TypeFinder>(&mut self, defn: T) -> Result<()> {
+ defn.add_type_definitions_to(self)
+ }
+
+ /// Add the definition of a named [Type].
+ ///
+ /// This will fail if you try to add a name for which an existing type definition exists.
+ pub fn add_type_definition(&mut self, name: &str, type_: Type) -> Result<()> {
+ if let Type::Unresolved { name: name_ } = &type_ {
+ assert_eq!(name, name_);
+ bail!("attempted to add type definition of Unresolved for `{name}`");
+ }
+
+ if resolve_builtin_type(name).is_some() {
+ bail!(
+ "please don't shadow builtin types ({name}, {})",
+ type_.canonical_name(),
+ );
+ }
+ self.add_known_type(&type_)?;
+ match self.type_definitions.entry(name.to_string()) {
+ Entry::Occupied(o) => {
+ let existing_def = o.get();
+ if type_ == *existing_def
+ && matches!(type_, Type::Record(_) | Type::Enum(_) | Type::Error(_))
+ {
+ // UDL and proc-macro metadata are allowed to define the same record, enum and
+ // error types, if the definitions match (fields and variants are checked in
+ // add_{record,enum,error}_definition)
+ Ok(())
+ } else {
+ bail!(
+ "Conflicting type definition for `{name}`! \
+ existing definition: {existing_def:?}, \
+ new definition: {type_:?}"
+ );
+ }
+ }
+ Entry::Vacant(e) => {
+ e.insert(type_);
+ Ok(())
+ }
+ }
+ }
+
+ /// Get the [Type] corresponding to a given name, if any.
+ pub(super) fn get_type_definition(&self, name: &str) -> Option<Type> {
+ self.type_definitions.get(name).cloned()
+ }
+
+ /// Get the [Type] corresponding to a given WebIDL type node.
+ ///
+ /// If the node is a structural type (e.g. a sequence) then this will also add
+ /// it to the set of all types seen in the component interface.
+ pub(crate) fn resolve_type_expression<T: TypeResolver>(&mut self, expr: T) -> Result<Type> {
+ expr.resolve_type_expression(self)
+ }
+
+ /// Add a [Type] to the set of all types seen in the component interface.
+ pub fn add_known_type(&mut self, type_: &Type) -> Result<()> {
+ // Adding potentially-unresolved types is a footgun, make sure we don't do that.
+ if matches!(type_, Type::Unresolved { .. }) {
+ bail!("Unresolved types must be resolved before being added to known types");
+ }
+
+ // Types are more likely to already be known than not, so avoid unnecessary cloning.
+ if !self.all_known_types.contains(type_) {
+ self.all_known_types.insert(type_.to_owned());
+
+ // Add inner types. For UDL, this is actually pointless extra work (as is calling
+ // add_known_type from add_function_definition), but for the proc-macro frontend
+ // this is important if the inner type isn't ever mentioned outside one of these
+ // generic builtin types.
+ match type_ {
+ Type::Optional(t) => self.add_known_type(t)?,
+ Type::Sequence(t) => self.add_known_type(t)?,
+ Type::Map(k, v) => {
+ self.add_known_type(k)?;
+ self.add_known_type(v)?;
+ }
+ _ => {}
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Iterator over all the known types in this universe.
+ pub fn iter_known_types(&self) -> impl Iterator<Item = &Type> {
+ self.all_known_types.iter()
+ }
+}
+
+/// An abstract type for an iterator over &Type references.
+///
+/// Ideally we would not need to name this type explicitly, and could just
+/// use an `impl Iterator<Item = &Type>` on any method that yields types.
+pub type TypeIterator<'a> = Box<dyn Iterator<Item = &'a Type> + 'a>;
+
+#[cfg(test)]
+mod test_type {
+ use super::*;
+
+ #[test]
+ fn test_canonical_names() {
+ // Non-exhaustive, but gives a bit of a flavour of what we want.
+ assert_eq!(Type::UInt8.canonical_name(), "u8");
+ assert_eq!(Type::String.canonical_name(), "string");
+ assert_eq!(
+ Type::Optional(Box::new(Type::Sequence(Box::new(Type::Object(
+ "Example".into()
+ )))))
+ .canonical_name(),
+ "OptionalSequenceTypeExample"
+ );
+ }
+}
+
+#[cfg(test)]
+mod test_type_universe {
+ // All the useful functionality of the `TypeUniverse` struct
+ // is tested as part of the `TypeFinder` and `TypeResolver` test suites.
+}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/types/resolver.rs b/third_party/rust/uniffi_bindgen/src/interface/types/resolver.rs
new file mode 100644
index 0000000000..35ae640254
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/interface/types/resolver.rs
@@ -0,0 +1,367 @@
+/* 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/. */
+
+//! # Helpers for resolving UDL type expressions into concrete types.
+//!
+//! This module provides the [`TypeResolver`] trait, an abstraction for walking
+//! the parse tree of a weedle type expression and using a [`TypeUniverse`] to
+//! convert it into a concrete type definition (so it assumes that you're already
+//! used a [`TypeFinder`](super::TypeFinder) to populate the universe).
+//!
+//! Perhaps most importantly, it knows how to error out if the UDL tries to reference
+//! an undefined or invalid type.
+
+use anyhow::{bail, Result};
+
+use super::{Type, TypeUniverse};
+
+/// Trait to help resolving an UDL type node to a [`Type`].
+///
+/// This trait does structural matching against type-related weedle AST nodes from
+/// a parsed UDL file, turning them into a corresponding [`Type`] struct. It uses the
+/// known type definitions in a [`TypeUniverse`] to resolve names to types.
+///
+/// As a side-effect, resolving a type expression will grow the type universe with
+/// references to the types seen during traversal. For example resolving the type
+/// expression `sequence<<TestRecord>?` will:
+///
+/// * add `Optional<Sequence<TestRecord>` and `Sequence<TestRecord>` to the
+/// known types in the universe.
+/// * error out if the type name `TestRecord` is not already known.
+///
+pub(crate) trait TypeResolver {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type>;
+}
+
+impl TypeResolver for &weedle::types::Type<'_> {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ (*self).resolve_type_expression(types)
+ }
+}
+
+impl TypeResolver for weedle::types::Type<'_> {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ match self {
+ weedle::types::Type::Single(t) => match t {
+ weedle::types::SingleType::Any(_) => bail!("no support for `any` types"),
+ weedle::types::SingleType::NonAny(t) => t.resolve_type_expression(types),
+ },
+ weedle::types::Type::Union(_) => bail!("no support for union types yet"),
+ }
+ }
+}
+
+impl TypeResolver for weedle::types::NonAnyType<'_> {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ match self {
+ weedle::types::NonAnyType::Boolean(t) => t.resolve_type_expression(types),
+ weedle::types::NonAnyType::Identifier(t) => t.resolve_type_expression(types),
+ weedle::types::NonAnyType::Integer(t) => t.resolve_type_expression(types),
+ weedle::types::NonAnyType::FloatingPoint(t) => t.resolve_type_expression(types),
+ weedle::types::NonAnyType::Sequence(t) => t.resolve_type_expression(types),
+ weedle::types::NonAnyType::RecordType(t) => t.resolve_type_expression(types),
+ _ => bail!("no support for type {:?}", self),
+ }
+ }
+}
+
+impl TypeResolver for &weedle::types::AttributedNonAnyType<'_> {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ if self.attributes.is_some() {
+ bail!("type attributes are not supported yet");
+ }
+ self.type_.resolve_type_expression(types)
+ }
+}
+
+impl TypeResolver for &weedle::types::AttributedType<'_> {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ if self.attributes.is_some() {
+ bail!("type attributes are not supported yet");
+ }
+ self.type_.resolve_type_expression(types)
+ }
+}
+
+impl<T: TypeResolver> TypeResolver for weedle::types::MayBeNull<T> {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ let type_ = self.type_.resolve_type_expression(types)?;
+ match self.q_mark {
+ None => Ok(type_),
+ Some(_) => {
+ let ty = Type::Optional(Box::new(type_));
+ types.add_known_type(&ty)?;
+ Ok(ty)
+ }
+ }
+ }
+}
+
+impl TypeResolver for weedle::types::IntegerType {
+ fn resolve_type_expression(&self, _types: &mut TypeUniverse) -> Result<Type> {
+ bail!(
+ "WebIDL integer types not implemented ({:?}); consider using u8, u16, u32 or u64",
+ self
+ )
+ }
+}
+
+impl TypeResolver for weedle::types::FloatingPointType {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ match self {
+ weedle::types::FloatingPointType::Float(t) => t.resolve_type_expression(types),
+ weedle::types::FloatingPointType::Double(t) => t.resolve_type_expression(types),
+ }
+ }
+}
+
+impl TypeResolver for weedle::types::SequenceType<'_> {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ let t = self.generics.body.as_ref().resolve_type_expression(types)?;
+ let ty = Type::Sequence(Box::new(t));
+ types.add_known_type(&ty)?;
+ Ok(ty)
+ }
+}
+
+impl TypeResolver for weedle::types::RecordKeyType<'_> {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ use weedle::types::RecordKeyType::*;
+ match self {
+ Byte(_) | USV(_) => bail!(
+ "WebIDL Byte or USV string type not implemented ({self:?}); \
+ consider using DOMString or string",
+ ),
+ DOM(_) => {
+ types.add_known_type(&Type::String)?;
+ Ok(Type::String)
+ }
+ NonAny(t) => t.resolve_type_expression(types),
+ }
+ }
+}
+
+impl TypeResolver for weedle::types::RecordType<'_> {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ let key_type = self.generics.body.0.resolve_type_expression(types)?;
+ let value_type = self.generics.body.2.resolve_type_expression(types)?;
+ let map = Type::Map(Box::new(key_type), Box::new(value_type));
+ types.add_known_type(&map)?;
+ Ok(map)
+ }
+}
+
+impl TypeResolver for weedle::common::Identifier<'_> {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ match resolve_builtin_type(self.0) {
+ Some(type_) => {
+ types.add_known_type(&type_)?;
+ Ok(type_)
+ }
+ None => match types.get_type_definition(self.0) {
+ Some(type_) => {
+ types.add_known_type(&type_)?;
+ Ok(type_)
+ }
+ None => bail!("unknown type reference: {}", self.0),
+ },
+ }
+ }
+}
+
+impl TypeResolver for weedle::term::Boolean {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ types.add_known_type(&Type::Boolean)?;
+ Ok(Type::Boolean)
+ }
+}
+
+impl TypeResolver for weedle::types::FloatType {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ if self.unrestricted.is_some() {
+ bail!("we don't support `unrestricted float`");
+ }
+ types.add_known_type(&Type::Float32)?;
+ Ok(Type::Float32)
+ }
+}
+
+impl TypeResolver for weedle::types::DoubleType {
+ fn resolve_type_expression(&self, types: &mut TypeUniverse) -> Result<Type> {
+ if self.unrestricted.is_some() {
+ bail!("we don't support `unrestricted double`");
+ }
+ types.add_known_type(&Type::Float64)?;
+ Ok(Type::Float64)
+ }
+}
+
+/// Resolve built-in API types by name.
+///
+/// Given an identifier from the UDL, this will return `Some(Type)` if it names one of the
+/// built-in primitive types or `None` if it names something else.
+pub(in super::super) fn resolve_builtin_type(name: &str) -> Option<Type> {
+ match name {
+ "string" => Some(Type::String),
+ "u8" => Some(Type::UInt8),
+ "i8" => Some(Type::Int8),
+ "u16" => Some(Type::UInt16),
+ "i16" => Some(Type::Int16),
+ "u32" => Some(Type::UInt32),
+ "i32" => Some(Type::Int32),
+ "u64" => Some(Type::UInt64),
+ "i64" => Some(Type::Int64),
+ "f32" => Some(Type::Float32),
+ "f64" => Some(Type::Float64),
+ "timestamp" => Some(Type::Timestamp),
+ "duration" => Some(Type::Duration),
+ _ => None,
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use weedle::Parse;
+
+ #[test]
+ fn test_named_type_resolution() -> Result<()> {
+ let mut types = TypeUniverse::default();
+ types.add_type_definition("TestRecord", Type::Record("TestRecord".into()))?;
+ assert_eq!(types.iter_known_types().count(), 1);
+
+ let (_, expr) = weedle::types::Type::parse("TestRecord").unwrap();
+ let t = types.resolve_type_expression(expr).unwrap();
+ assert!(matches!(t, Type::Record(nm) if nm == "TestRecord"));
+ assert_eq!(types.iter_known_types().count(), 1);
+
+ let (_, expr) = weedle::types::Type::parse("TestRecord?").unwrap();
+ let t = types.resolve_type_expression(expr).unwrap();
+ assert!(matches!(t, Type::Optional(_)));
+ // Matching the Box<T> is hard, use names as a convenient workaround.
+ assert_eq!(t.canonical_name(), "OptionalTypeTestRecord");
+ assert_eq!(types.iter_known_types().count(), 2);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_resolving_optional_type_adds_inner_type() {
+ let mut types = TypeUniverse::default();
+ assert_eq!(types.iter_known_types().count(), 0);
+ let (_, expr) = weedle::types::Type::parse("u32?").unwrap();
+ let t = types.resolve_type_expression(expr).unwrap();
+ assert_eq!(t.canonical_name(), "Optionalu32");
+ assert_eq!(types.iter_known_types().count(), 2);
+ assert!(types
+ .iter_known_types()
+ .any(|t| t.canonical_name() == "u32"));
+ assert!(types
+ .iter_known_types()
+ .any(|t| t.canonical_name() == "Optionalu32"));
+ }
+
+ #[test]
+ fn test_resolving_sequence_type_adds_inner_type() {
+ let mut types = TypeUniverse::default();
+ assert_eq!(types.iter_known_types().count(), 0);
+ let (_, expr) = weedle::types::Type::parse("sequence<string>").unwrap();
+ let t = types.resolve_type_expression(expr).unwrap();
+ assert_eq!(t.canonical_name(), "Sequencestring");
+ assert_eq!(types.iter_known_types().count(), 2);
+ assert!(types
+ .iter_known_types()
+ .any(|t| t.canonical_name() == "Sequencestring"));
+ assert!(types
+ .iter_known_types()
+ .any(|t| t.canonical_name() == "string"));
+ }
+
+ #[test]
+ fn test_resolving_map_type_adds_string_and_inner_type() {
+ let mut types = TypeUniverse::default();
+ assert_eq!(types.iter_known_types().count(), 0);
+ let (_, expr) = weedle::types::Type::parse("record<DOMString, float>").unwrap();
+ let t = types.resolve_type_expression(expr).unwrap();
+ assert_eq!(t.canonical_name(), "MapStringF32");
+ assert_eq!(types.iter_known_types().count(), 3);
+ assert!(types
+ .iter_known_types()
+ .any(|t| t.canonical_name() == "MapStringF32"));
+ assert!(types
+ .iter_known_types()
+ .any(|t| t.canonical_name() == "string"));
+ assert!(types
+ .iter_known_types()
+ .any(|t| t.canonical_name() == "f32"));
+ }
+
+ #[test]
+ fn test_resolving_map_type_adds_key_type_and_inner_type() {
+ let mut types = TypeUniverse::default();
+ assert_eq!(types.iter_known_types().count(), 0);
+ let (_, expr) = weedle::types::Type::parse("record<u64, float>").unwrap();
+ let t = types.resolve_type_expression(expr).unwrap();
+ assert_eq!(t.canonical_name(), "MapU64F32");
+ assert_eq!(types.iter_known_types().count(), 3);
+ assert!(types
+ .iter_known_types()
+ .any(|t| t.canonical_name() == "MapU64F32"));
+ assert!(types
+ .iter_known_types()
+ .any(|t| t.canonical_name() == "u64"));
+ assert!(types
+ .iter_known_types()
+ .any(|t| t.canonical_name() == "f32"));
+ }
+
+ #[test]
+ fn test_error_on_unknown_type() -> Result<()> {
+ let mut types = TypeUniverse::default();
+ types.add_type_definition("TestRecord", Type::Record("TestRecord".into()))?;
+ // Oh no, someone made a typo in the type-o...
+ let (_, expr) = weedle::types::Type::parse("TestRecrd").unwrap();
+ let err = types.resolve_type_expression(expr).unwrap_err();
+ assert_eq!(err.to_string(), "unknown type reference: TestRecrd");
+ Ok(())
+ }
+
+ #[test]
+ fn test_error_on_union_type() -> Result<()> {
+ let mut types = TypeUniverse::default();
+ types.add_type_definition("TestRecord", Type::Record("TestRecord".into()))?;
+ let (_, expr) = weedle::types::Type::parse("(TestRecord or u32)").unwrap();
+ let err = types.resolve_type_expression(expr).unwrap_err();
+ assert_eq!(err.to_string(), "no support for union types yet");
+ Ok(())
+ }
+
+ #[test]
+ fn test_type_set_is_well_ordered() -> Result<()> {
+ // The set (universe) of types should have a well-defined order. When
+ // the data structure does not guarantee the order of its elements, such as
+ // HashSet, then the resulting generated source code is likely not
+ // deterministic, and the compiled binary file may not be reproducible. We
+ // avoid this issue by using an implementation that defines the order of its
+ // elements. This test verifies that the elements are sorted as expected.
+ let mut types = TypeUniverse::default();
+ types.add_type_definition("TestRecord", Type::Record("TestRecord".into()))?;
+ assert_eq!(types.iter_known_types().count(), 1);
+ types.add_type_definition("TestRecord2", Type::Record("TestRecord2".into()))?;
+ assert_eq!(types.iter_known_types().count(), 2);
+ types.add_type_definition("TestInt64", Type::Int64)?;
+ types.add_type_definition("TestInt8", Type::Int8)?;
+ types.add_type_definition("TestUInt8", Type::UInt8)?;
+ types.add_type_definition("TestBoolean", Type::Boolean)?;
+ assert_eq!(types.iter_known_types().count(), 6);
+ let mut iter = types.iter_known_types();
+ assert_eq!(Some(&Type::UInt8), iter.next());
+ assert_eq!(Some(&Type::Int8), iter.next());
+ assert_eq!(Some(&Type::Int64), iter.next());
+ assert_eq!(Some(&Type::Boolean), iter.next());
+ assert_eq!(Some(&Type::Record("TestRecord".into())), iter.next());
+ assert_eq!(Some(&Type::Record("TestRecord2".into())), iter.next());
+ Ok(())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/lib.rs b/third_party/rust/uniffi_bindgen/src/lib.rs
new file mode 100644
index 0000000000..4959f0324a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/lib.rs
@@ -0,0 +1,483 @@
+/* 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/. */
+
+//! # Uniffi: easily build cross-platform software components in Rust
+//!
+//! This is a highly-experimental crate for building cross-language software components
+//! in Rust, based on things we've learned and patterns we've developed in the
+//! [mozilla/application-services](https://github.com/mozilla/application-services) project.
+//!
+//! The idea is to let you write your code once, in Rust, and then re-use it from many
+//! other programming languages via Rust's C-compatible FFI layer and some automagically
+//! generated binding code. If you think of it as a kind of [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen)
+//! wannabe, with a clunkier developer experience but support for more target languages,
+//! you'll be pretty close to the mark.
+//!
+//! Currently supported target languages include Kotlin, Swift and Python.
+//!
+//! ## Usage
+//
+//! To build a cross-language component using `uniffi`, follow these steps.
+//!
+//! ### 1) Specify your Component Interface
+//!
+//! Start by thinking about the interface you want to expose for use
+//! from other languages. Use the Interface Definition Language to specify your interface
+//! in a `.udl` file, where it can be processed by the tools from this crate.
+//! For example you might define an interface like this:
+//!
+//! ```text
+//! namespace example {
+//! u32 foo(u32 bar);
+//! }
+//!
+//! dictionary MyData {
+//! u32 num_foos;
+//! bool has_a_bar;
+//! }
+//! ```
+//!
+//! ### 2) Implement the Component Interface as a Rust crate
+//!
+//! With the interface, defined, provide a corresponding implementation of that interface
+//! as a standard-looking Rust crate, using functions and structs and so-on. For example
+//! an implementation of the above Component Interface might look like this:
+//!
+//! ```text
+//! fn foo(bar: u32) -> u32 {
+//! // TODO: a better example!
+//! bar + 42
+//! }
+//!
+//! struct MyData {
+//! num_foos: u32,
+//! has_a_bar: bool
+//! }
+//! ```
+//!
+//! ### 3) Generate and include component scaffolding from the UDL file
+//!
+//! First you will need to install `uniffi-bindgen` on your system using `cargo install uniffi_bindgen`.
+//! Then add to your crate `uniffi_build` under `[build-dependencies]`.
+//! Finally, add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding`
+//! to process your `.udl` file. This will generate some Rust code to be included in the top-level source
+//! code of your crate. If your UDL file is named `example.udl`, then your build script would call:
+//!
+//! ```text
+//! uniffi_build::generate_scaffolding("./src/example.udl")
+//! ```
+//!
+//! This would output a rust file named `example.uniffi.rs`, ready to be
+//! included into the code of your rust crate like this:
+//!
+//! ```text
+//! include!(concat!(env!("OUT_DIR"), "/example.uniffi.rs"));
+//! ```
+//!
+//! ### 4) Generate foreign language bindings for the library
+//!
+//! The `uniffi-bindgen` utility provides a command-line tool that can produce code to
+//! consume the Rust library in any of several supported languages.
+//! It is done by calling (in kotlin for example):
+//!
+//! ```text
+//! uniffi-bindgen --language kotlin ./src/example.udl
+//! ```
+//!
+//! This will produce a file `example.kt` in the same directory as the .udl file, containing kotlin bindings
+//! to load and use the compiled rust code via its C-compatible FFI.
+//!
+
+#![warn(rust_2018_idioms, unused_qualifications)]
+#![allow(unknown_lints)]
+
+const BINDGEN_VERSION: &str = env!("CARGO_PKG_VERSION");
+
+use anyhow::{anyhow, bail, Context, Result};
+use camino::{Utf8Path, Utf8PathBuf};
+use fs_err::{self as fs, File};
+use serde::{Deserialize, Serialize};
+use std::io::prelude::*;
+use std::io::ErrorKind;
+use std::{collections::HashMap, env, process::Command, str::FromStr};
+
+pub mod backend;
+pub mod bindings;
+pub mod interface;
+pub mod macro_metadata;
+pub mod scaffolding;
+
+pub use interface::ComponentInterface;
+use scaffolding::RustScaffolding;
+
+/// A trait representing a Binding Generator Configuration
+///
+/// External crates that implement binding generators need to implement this trait and set it as
+/// the `BindingGenerator.config` associated type. `generate_external_bindings()` then uses it to
+/// generate the config that's passed to `BindingGenerator.write_bindings()`
+pub trait BindingGeneratorConfig: for<'de> Deserialize<'de> {
+ /// Get the entry for this config from the `bindings` table.
+ fn get_entry_from_bindings_table(bindings: &toml::Value) -> Option<toml::Value>;
+
+ /// Get default config values from the `ComponentInterface`
+ ///
+ /// These will replace missing entries in the bindings-specific config
+ fn get_config_defaults(ci: &ComponentInterface) -> Vec<(String, toml::Value)>;
+}
+
+fn load_bindings_config<BC: BindingGeneratorConfig>(
+ ci: &ComponentInterface,
+ crate_root: &Utf8Path,
+ config_file_override: Option<&Utf8Path>,
+) -> Result<BC> {
+ // Load the config from the TOML value, falling back to an empty map if it doesn't exist
+ let mut config_map: toml::value::Table =
+ match load_bindings_config_toml::<BC>(crate_root, config_file_override)? {
+ Some(value) => value
+ .try_into()
+ .context("Bindings config must be a TOML table")?,
+ None => toml::map::Map::new(),
+ };
+
+ // Update it with the defaults from the component interface
+ for (key, value) in BC::get_config_defaults(ci) {
+ config_map.entry(key).or_insert(value);
+ }
+
+ // Leverage serde to convert toml::Value into the config type
+ toml::Value::from(config_map)
+ .try_into()
+ .context("Generating bindings config from toml::Value")
+}
+
+/// Binding generator config with no members
+#[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Ord, Eq)]
+pub struct EmptyBindingGeneratorConfig;
+
+impl BindingGeneratorConfig for EmptyBindingGeneratorConfig {
+ fn get_entry_from_bindings_table(_bindings: &toml::Value) -> Option<toml::Value> {
+ None
+ }
+
+ fn get_config_defaults(_ci: &ComponentInterface) -> Vec<(String, toml::Value)> {
+ Vec::new()
+ }
+}
+
+// EmptyBindingGeneratorConfig is a unit struct, so the `derive(Deserialize)` implementation
+// expects a null value rather than the empty map that we pass it. So we need to implement
+// `Deserialize` ourselves.
+impl<'de> Deserialize<'de> for EmptyBindingGeneratorConfig {
+ fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ Ok(EmptyBindingGeneratorConfig)
+ }
+}
+
+// Load the binding-specific config
+//
+// This function calculates the location of the config TOML file, parses it, and returns the result
+// as a toml::Value
+//
+// If there is an error parsing the file then Err will be returned. If the file is missing or the
+// entry for the bindings is missing, then Ok(None) will be returned.
+fn load_bindings_config_toml<BC: BindingGeneratorConfig>(
+ crate_root: &Utf8Path,
+ config_file_override: Option<&Utf8Path>,
+) -> Result<Option<toml::Value>> {
+ let config_path = match config_file_override {
+ Some(cfg) => cfg.to_owned(),
+ None => crate_root.join("uniffi.toml"),
+ };
+
+ if !config_path.exists() {
+ return Ok(None);
+ }
+
+ let contents = fs::read_to_string(&config_path)
+ .with_context(|| format!("Failed to read config file from {config_path}"))?;
+ let full_config = toml::Value::from_str(&contents)
+ .with_context(|| format!("Failed to parse config file {config_path}"))?;
+
+ Ok(full_config
+ .get("bindings")
+ .and_then(BC::get_entry_from_bindings_table))
+}
+
+/// A trait representing a UniFFI Binding Generator
+///
+/// External crates that implement binding generators, should implement this type
+/// and call the [`generate_external_bindings`] using a type that implements this trait.
+pub trait BindingGenerator: Sized {
+ /// Associated type representing a the bindings-specifig configuration parsed from the
+ /// uniffi.toml
+ type Config: BindingGeneratorConfig;
+
+ /// Writes the bindings to the output directory
+ ///
+ /// # Arguments
+ /// - `ci`: A [`ComponentInterface`] representing the interface
+ /// - `config`: A instance of the BindingGeneratorConfig associated with this type
+ /// - `out_dir`: The path to where the binding generator should write the output bindings
+ fn write_bindings(
+ &self,
+ ci: ComponentInterface,
+ config: Self::Config,
+ out_dir: &Utf8Path,
+ ) -> Result<()>;
+}
+
+/// Generate bindings for an external binding generator
+/// Ideally, this should replace the [`generate_bindings`] function below
+///
+/// Implements an entry point for external binding generators.
+/// The function does the following:
+/// - It parses the `udl` in a [`ComponentInterface`]
+/// - Parses the `uniffi.toml` and loads it into the type that implements [`BindingGeneratorConfig`]
+/// - Creates an instance of [`BindingGenerator`], based on type argument `B`, and run [`BindingGenerator::write_bindings`] on it
+///
+/// # Arguments
+/// - `binding_generator`: Type that implements BindingGenerator
+/// - `udl_file`: The path to the UDL file
+/// - `config_file_override`: The path to the configuration toml file, most likely called `uniffi.toml`. If [`None`], the function will try to guess based on the crate's root.
+/// - `out_dir_override`: The path to write the bindings to. If [`None`], it will be the path to the parent directory of the `udl_file`
+pub fn generate_external_bindings(
+ binding_generator: impl BindingGenerator,
+ udl_file: impl AsRef<Utf8Path>,
+ config_file_override: Option<impl AsRef<Utf8Path>>,
+ out_dir_override: Option<impl AsRef<Utf8Path>>,
+) -> Result<()> {
+ let out_dir_override = out_dir_override.as_ref().map(|p| p.as_ref());
+ let config_file_override = config_file_override.as_ref().map(|p| p.as_ref());
+
+ let crate_root = guess_crate_root(udl_file.as_ref())?;
+ let out_dir = get_out_dir(udl_file.as_ref(), out_dir_override)?;
+ let component = parse_udl(udl_file.as_ref()).context("Error parsing UDL")?;
+ let bindings_config = load_bindings_config(&component, crate_root, config_file_override)?;
+ binding_generator.write_bindings(component, bindings_config, &out_dir)
+}
+
+// Generate the infrastructural Rust code for implementing the UDL interface,
+// such as the `extern "C"` function definitions and record data types.
+pub fn generate_component_scaffolding(
+ udl_file: &Utf8Path,
+ config_file_override: Option<&Utf8Path>,
+ out_dir_override: Option<&Utf8Path>,
+ format_code: bool,
+) -> Result<()> {
+ let component = parse_udl(udl_file)?;
+ let _config = get_config(
+ &component,
+ guess_crate_root(udl_file)?,
+ config_file_override,
+ );
+ let file_stem = udl_file.file_stem().context("not a file")?;
+ let filename = format!("{file_stem}.uniffi.rs");
+ let out_path = get_out_dir(udl_file, out_dir_override)?.join(filename);
+ let mut f = File::create(&out_path)?;
+ write!(f, "{}", RustScaffolding::new(&component)).context("Failed to write output file")?;
+ if format_code {
+ format_code_with_rustfmt(&out_path)?;
+ }
+ Ok(())
+}
+
+// Generate the bindings in the target languages that call the scaffolding
+// Rust code.
+pub fn generate_bindings(
+ udl_file: &Utf8Path,
+ config_file_override: Option<&Utf8Path>,
+ target_languages: Vec<&str>,
+ out_dir_override: Option<&Utf8Path>,
+ library_file: Option<&Utf8Path>,
+ try_format_code: bool,
+) -> Result<()> {
+ let mut component = parse_udl(udl_file)?;
+ if let Some(library_file) = library_file {
+ macro_metadata::add_to_ci_from_library(&mut component, library_file)?;
+ }
+ let crate_root = &guess_crate_root(udl_file)?;
+
+ let config = get_config(&component, crate_root, config_file_override)?;
+ let out_dir = get_out_dir(udl_file, out_dir_override)?;
+ for language in target_languages {
+ bindings::write_bindings(
+ &config.bindings,
+ &component,
+ &out_dir,
+ language.try_into()?,
+ try_format_code,
+ )?;
+ }
+
+ Ok(())
+}
+
+pub fn dump_json(library_path: &Utf8Path) -> Result<String> {
+ let metadata = macro_metadata::extract_from_library(library_path)?;
+ Ok(serde_json::to_string_pretty(&metadata)?)
+}
+
+pub fn print_json(library_path: &Utf8Path) -> Result<()> {
+ println!("{}", dump_json(library_path)?);
+ Ok(())
+}
+
+/// Guess the root directory of the crate from the path of its UDL file.
+///
+/// For now, we assume that the UDL file is in `./src/something.udl` relative
+/// to the crate root. We might consider something more sophisticated in
+/// future.
+pub fn guess_crate_root(udl_file: &Utf8Path) -> Result<&Utf8Path> {
+ let path_guess = udl_file
+ .parent()
+ .context("UDL file has no parent folder!")?
+ .parent()
+ .context("UDL file has no grand-parent folder!")?;
+ if !path_guess.join("Cargo.toml").is_file() {
+ bail!("UDL file does not appear to be inside a crate")
+ }
+ Ok(path_guess)
+}
+
+fn get_config(
+ component: &ComponentInterface,
+ crate_root: &Utf8Path,
+ config_file_override: Option<&Utf8Path>,
+) -> Result<Config> {
+ let default_config: Config = component.into();
+
+ let config_file = match config_file_override {
+ Some(cfg) => Some(cfg.to_owned()),
+ None => crate_root.join("uniffi.toml").canonicalize_utf8().ok(),
+ };
+
+ match config_file {
+ Some(path) => {
+ let contents = fs::read_to_string(&path)
+ .with_context(|| format!("Failed to read config file from {path}"))?;
+ let loaded_config: Config = toml::de::from_str(&contents)
+ .with_context(|| format!("Failed to generate config from file {path}"))?;
+ Ok(loaded_config.merge_with(&default_config))
+ }
+ None => Ok(default_config),
+ }
+}
+
+fn get_out_dir(udl_file: &Utf8Path, out_dir_override: Option<&Utf8Path>) -> Result<Utf8PathBuf> {
+ Ok(match out_dir_override {
+ Some(s) => {
+ // Create the directory if it doesn't exist yet.
+ fs::create_dir_all(s)?;
+ s.canonicalize_utf8().context("Unable to find out-dir")?
+ }
+ None => udl_file
+ .parent()
+ .context("File has no parent directory")?
+ .to_owned(),
+ })
+}
+
+fn parse_udl(udl_file: &Utf8Path) -> Result<ComponentInterface> {
+ let udl = fs::read_to_string(udl_file)
+ .with_context(|| format!("Failed to read UDL from {udl_file}"))?;
+ ComponentInterface::from_webidl(&udl).context("Failed to parse UDL")
+}
+
+fn format_code_with_rustfmt(path: &Utf8Path) -> Result<()> {
+ let status = Command::new("rustfmt").arg(path).status().map_err(|e| {
+ let ctx = match e.kind() {
+ ErrorKind::NotFound => "formatting was requested, but rustfmt was not found",
+ _ => "unknown error when calling rustfmt",
+ };
+ anyhow!(e).context(ctx)
+ })?;
+ if !status.success() {
+ bail!("rustmt failed when formatting scaffolding. Note: --no-format can be used to skip formatting");
+ }
+ Ok(())
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+struct Config {
+ #[serde(default)]
+ bindings: bindings::Config,
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ bindings: ci.into(),
+ }
+ }
+}
+
+pub trait MergeWith {
+ fn merge_with(&self, other: &Self) -> Self;
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ bindings: self.bindings.merge_with(&other.bindings),
+ }
+ }
+}
+
+impl<T: Clone> MergeWith for Option<T> {
+ fn merge_with(&self, other: &Self) -> Self {
+ match (self, other) {
+ (Some(_), _) => self.clone(),
+ (None, Some(_)) => other.clone(),
+ (None, None) => None,
+ }
+ }
+}
+
+impl<V: Clone> MergeWith for HashMap<String, V> {
+ fn merge_with(&self, other: &Self) -> Self {
+ let mut merged = HashMap::new();
+ // Iterate through other first so our keys override theirs
+ for (key, value) in other.iter().chain(self) {
+ merged.insert(key.clone(), value.clone());
+ }
+ merged
+ }
+}
+
+// FIXME(HACK):
+// Include the askama config file into the build.
+// That way cargo tracks the file and other tools relying on file tracking see it as well.
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1774585
+// In the future askama should handle that itself by using the `track_path::path` API,
+// see https://github.com/rust-lang/rust/pull/84029
+#[allow(dead_code)]
+mod __unused {
+ const _: &[u8] = include_bytes!("../askama.toml");
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_guessing_of_crate_root_directory_from_udl_file() {
+ // When running this test, this will be the ./uniffi_bindgen directory.
+ let this_crate_root = Utf8PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
+
+ let example_crate_root = this_crate_root
+ .parent()
+ .expect("should have a parent directory")
+ .join("./examples/arithmetic");
+ assert_eq!(
+ guess_crate_root(&example_crate_root.join("./src/arthmetic.udl")).unwrap(),
+ example_crate_root
+ );
+
+ let not_a_crate_root = &this_crate_root.join("./src/templates");
+ assert!(guess_crate_root(&not_a_crate_root.join("./src/example.udl")).is_err());
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs
new file mode 100644
index 0000000000..b6fe94dd20
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs
@@ -0,0 +1,100 @@
+/* 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::interface::{ComponentInterface, Enum, Error, Record, Type};
+use anyhow::anyhow;
+use uniffi_meta::Metadata;
+
+/// Add Metadata items to the ComponentInterface
+///
+/// This function exists to support the transition period where the `uniffi::export` macro can only
+/// handle some components. This means that crates need to continue using UDL files to define the
+/// parts of the components that aren't supported yet.
+///
+/// To make things work, we generate a `ComponentInterface` from the UDL file, then combine it with
+/// the `Metadata` items that the macro creates.
+pub fn add_to_ci(
+ iface: &mut ComponentInterface,
+ metadata_items: Vec<Metadata>,
+) -> anyhow::Result<()> {
+ for item in metadata_items {
+ let (item_desc, crate_name) = match &item {
+ Metadata::Func(meta) => (
+ format!("function `{}`", meta.name),
+ meta.module_path.first().unwrap(),
+ ),
+ Metadata::Method(meta) => (
+ format!("method `{}.{}`", meta.self_name, meta.name),
+ meta.module_path.first().unwrap(),
+ ),
+ Metadata::Record(meta) => (
+ format!("record `{}`", meta.name),
+ meta.module_path.first().unwrap(),
+ ),
+ Metadata::Enum(meta) => (
+ format!("enum `{}`", meta.name),
+ meta.module_path.first().unwrap(),
+ ),
+ Metadata::Object(meta) => (
+ format!("object `{}`", meta.name),
+ meta.module_path.first().unwrap(),
+ ),
+ Metadata::Error(meta) => (
+ format!("error `{}`", meta.name),
+ meta.module_path.first().unwrap(),
+ ),
+ };
+
+ let ns = iface.namespace();
+ if crate_name != ns {
+ return Err(anyhow!("Found {item_desc} from crate `{crate_name}`.")
+ .context(format!(
+ "Main crate is expected to be named `{ns}` based on the UDL namespace."
+ ))
+ .context("Mixing symbols from multiple crates is not supported yet."));
+ }
+
+ match item {
+ Metadata::Func(meta) => {
+ iface.add_fn_meta(meta)?;
+ }
+ Metadata::Method(meta) => {
+ iface.add_method_meta(meta);
+ }
+ Metadata::Record(meta) => {
+ let ty = Type::Record(meta.name.clone());
+ iface.types.add_known_type(&ty)?;
+ iface.types.add_type_definition(&meta.name, ty)?;
+
+ let record: Record = meta.into();
+ iface.add_record_definition(record)?;
+ }
+ Metadata::Enum(meta) => {
+ let ty = Type::Enum(meta.name.clone());
+ iface.types.add_known_type(&ty)?;
+ iface.types.add_type_definition(&meta.name, ty)?;
+
+ let enum_: Enum = meta.into();
+ iface.add_enum_definition(enum_)?;
+ }
+ Metadata::Object(meta) => {
+ iface.add_object_free_fn(meta);
+ }
+ Metadata::Error(meta) => {
+ let ty = Type::Error(meta.name.clone());
+ iface.types.add_known_type(&ty)?;
+ iface.types.add_type_definition(&meta.name, ty)?;
+
+ let error: Error = meta.into();
+ iface.add_error_definition(error)?;
+ }
+ }
+ }
+
+ iface.resolve_types()?;
+ iface.derive_ffi_funcs()?;
+ iface.check_consistency()?;
+
+ Ok(())
+}
diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs
new file mode 100644
index 0000000000..3de10bd087
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs
@@ -0,0 +1,185 @@
+/* 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 anyhow::{bail, Context};
+use camino::Utf8Path;
+use fs_err as fs;
+use goblin::{
+ archive::Archive,
+ elf::Elf,
+ mach::{segment::Section, symbols, Mach, MachO, SingleArch},
+ pe::PE,
+ Object,
+};
+use std::collections::HashSet;
+use uniffi_meta::Metadata;
+
+/// Extract metadata written by the `uniffi::export` macro from a library file
+///
+/// In addition to generating the scaffolding, that macro and also encodes the
+/// `uniffi_meta::Metadata` for the components which can be used to generate the bindings side of
+/// the interface.
+pub fn extract_from_library(path: &Utf8Path) -> anyhow::Result<Vec<Metadata>> {
+ extract_from_bytes(&fs::read(path)?)
+}
+
+fn extract_from_bytes(file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> {
+ match Object::parse(file_data)? {
+ Object::Elf(elf) => extract_from_elf(elf, file_data),
+ Object::PE(pe) => extract_from_pe(pe, file_data),
+ Object::Mach(mach) => extract_from_mach(mach, file_data),
+ Object::Archive(archive) => extract_from_archive(archive, file_data),
+ Object::Unknown(_) => bail!("Unknown library format"),
+ }
+}
+
+pub fn extract_from_elf(elf: Elf<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> {
+ let mut extracted = ExtractedItems::new();
+ let iter = elf
+ .syms
+ .iter()
+ .filter_map(|sym| elf.section_headers.get(sym.st_shndx).map(|sh| (sym, sh)));
+
+ for (sym, sh) in iter {
+ let name = elf
+ .strtab
+ .get_at(sym.st_name)
+ .context("Error getting symbol name")?;
+ if is_metadata_symbol(name) {
+ // Offset relative to the start of the section.
+ let section_offset = sym.st_value - sh.sh_addr;
+ // Offset relative to the start of the file contents
+ extracted.extract_item(name, file_data, (sh.sh_offset + section_offset) as usize)?;
+ }
+ }
+ Ok(extracted.into_metadata())
+}
+
+pub fn extract_from_pe(pe: PE<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> {
+ let mut extracted = ExtractedItems::new();
+ for export in pe.exports {
+ if let Some(name) = export.name {
+ if is_metadata_symbol(name) {
+ extracted.extract_item(
+ name,
+ file_data,
+ export.offset.context("Error getting symbol offset")?,
+ )?;
+ }
+ }
+ }
+ Ok(extracted.into_metadata())
+}
+
+pub fn extract_from_mach(mach: Mach<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> {
+ match mach {
+ Mach::Binary(macho) => extract_from_macho(macho, file_data),
+ // Multi-binary library, just extract the first one
+ Mach::Fat(multi_arch) => match multi_arch.get(0)? {
+ SingleArch::MachO(macho) => extract_from_macho(macho, file_data),
+ SingleArch::Archive(archive) => extract_from_archive(archive, file_data),
+ },
+ }
+}
+
+pub fn extract_from_macho(macho: MachO<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> {
+ let mut sections: Vec<Section> = Vec::new();
+ for sects in macho.segments.sections() {
+ sections.extend(sects.map(|r| r.expect("section").0));
+ }
+ let mut extracted = ExtractedItems::new();
+ sections.sort_by_key(|s| s.addr);
+
+ // Iterate through the symbols. This picks up symbols from the .o files embedded in a Darwin
+ // archive.
+ for (name, nlist) in macho.symbols().flatten() {
+ // Check that the symbol:
+ // - Is global (exported)
+ // - Has type=N_SECT (it's regular data as opposed to something like
+ // "undefined" or "indirect")
+ // - Has a metadata symbol name
+ if nlist.is_global() && nlist.get_type() == symbols::N_SECT && is_metadata_symbol(name) {
+ let section = &sections[nlist.n_sect];
+ // `nlist.n_value` is an address, so we can calculating the offset inside the section
+ // using the difference between that and `section.addr`
+ let offset = section.offset as usize + nlist.n_value as usize - section.addr as usize;
+ extracted.extract_item(name, file_data, offset)?;
+ }
+ }
+
+ // Iterate through the exports. This picks up symbols from .dylib files.
+ for export in macho.exports()? {
+ let name = &export.name;
+ if is_metadata_symbol(name) {
+ extracted.extract_item(name, file_data, export.offset as usize)?;
+ }
+ }
+ Ok(extracted.into_metadata())
+}
+
+pub fn extract_from_archive(
+ archive: Archive<'_>,
+ file_data: &[u8],
+) -> anyhow::Result<Vec<Metadata>> {
+ // Store the names of archive members that have metadata symbols in them
+ let mut members_to_check: HashSet<&str> = HashSet::new();
+ for (member_name, _, symbols) in archive.summarize() {
+ for name in symbols {
+ if is_metadata_symbol(name) {
+ members_to_check.insert(member_name);
+ }
+ }
+ }
+
+ let mut items = vec![];
+ for member_name in members_to_check {
+ items.append(&mut extract_from_bytes(
+ archive.extract(member_name, file_data)?,
+ )?);
+ }
+ Ok(items)
+}
+
+/// Container for extracted metadata items
+#[derive(Default)]
+struct ExtractedItems {
+ items: Vec<Metadata>,
+ /// symbol names for the extracted items, we use this to ensure that we don't extract the same
+ /// symbol twice
+ names: HashSet<String>,
+}
+
+impl ExtractedItems {
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn extract_item(&mut self, name: &str, file_data: &[u8], offset: usize) -> anyhow::Result<()> {
+ if self.names.contains(name) {
+ // Already extracted this item
+ return Ok(());
+ }
+
+ // Use the file data starting from offset, without specifying the end position. We don't
+ // always know the end position, because goblin reports the symbol size as 0 for PE and
+ // MachO files.
+ //
+ // This works fine, because bincode knows when the serialized data is terminated and will
+ // just ignore the trailing data.
+ let data = &file_data[offset..];
+ self.items.push(bincode::deserialize::<Metadata>(data)?);
+ self.names.insert(name.to_string());
+ Ok(())
+ }
+
+ fn into_metadata(self) -> Vec<Metadata> {
+ self.items
+ }
+}
+
+fn is_metadata_symbol(name: &str) -> bool {
+ // Skip the "_" char that Darwin prepends, if present
+ let name = name.strip_prefix('_').unwrap_or(name);
+ name.starts_with("UNIFFI_META")
+}
diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs
new file mode 100644
index 0000000000..d303418ece
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs
@@ -0,0 +1,19 @@
+/* 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 super::ComponentInterface;
+use camino::Utf8Path;
+
+mod ci;
+mod extract;
+
+pub use ci::add_to_ci;
+pub use extract::extract_from_library;
+
+pub fn add_to_ci_from_library(
+ iface: &mut ComponentInterface,
+ library_path: &Utf8Path,
+) -> anyhow::Result<()> {
+ add_to_ci(iface, extract_from_library(library_path)?)
+}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs
new file mode 100644
index 0000000000..7fc60dede4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs
@@ -0,0 +1,152 @@
+/* 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 anyhow::Result;
+use askama::Template;
+use std::borrow::Borrow;
+
+use super::interface::*;
+use heck::ToSnakeCase;
+
+#[derive(Template)]
+#[template(syntax = "rs", escape = "none", path = "scaffolding_template.rs")]
+pub struct RustScaffolding<'a> {
+ ci: &'a ComponentInterface,
+ uniffi_version: &'static str,
+}
+impl<'a> RustScaffolding<'a> {
+ pub fn new(ci: &'a ComponentInterface) -> Self {
+ Self {
+ ci,
+ uniffi_version: crate::BINDGEN_VERSION,
+ }
+ }
+}
+mod filters {
+ use super::*;
+
+ pub fn type_rs(type_: &Type) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ Type::Int8 => "i8".into(),
+ Type::UInt8 => "u8".into(),
+ Type::Int16 => "i16".into(),
+ Type::UInt16 => "u16".into(),
+ Type::Int32 => "i32".into(),
+ Type::UInt32 => "u32".into(),
+ Type::Int64 => "i64".into(),
+ Type::UInt64 => "u64".into(),
+ Type::Float32 => "f32".into(),
+ Type::Float64 => "f64".into(),
+ Type::Boolean => "bool".into(),
+ Type::String => "String".into(),
+ Type::Timestamp => "std::time::SystemTime".into(),
+ Type::Duration => "std::time::Duration".into(),
+ Type::Enum(name) | Type::Record(name) | Type::Error(name) => format!("r#{name}"),
+ Type::Object(name) => format!("std::sync::Arc<r#{name}>"),
+ Type::CallbackInterface(name) => format!("Box<dyn r#{name}>"),
+ Type::Optional(t) => format!("std::option::Option<{}>", type_rs(t)?),
+ Type::Sequence(t) => format!("std::vec::Vec<{}>", type_rs(t)?),
+ Type::Map(k, v) => format!(
+ "std::collections::HashMap<{}, {}>",
+ type_rs(k)?,
+ type_rs(v)?
+ ),
+ Type::Custom { name, .. } => format!("r#{name}"),
+ Type::External { .. } => panic!("External types coming to a uniffi near you soon!"),
+ Type::Unresolved { .. } => {
+ unreachable!("UDL scaffolding code never contains unresolved types")
+ }
+ })
+ }
+
+ pub fn type_ffi(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ FfiType::Int8 => "i8".into(),
+ FfiType::UInt8 => "u8".into(),
+ FfiType::Int16 => "i16".into(),
+ FfiType::UInt16 => "u16".into(),
+ FfiType::Int32 => "i32".into(),
+ FfiType::UInt32 => "u32".into(),
+ FfiType::Int64 => "i64".into(),
+ FfiType::UInt64 => "u64".into(),
+ FfiType::Float32 => "f32".into(),
+ FfiType::Float64 => "f64".into(),
+ FfiType::RustArcPtr(_) => "*const std::os::raw::c_void".into(),
+ FfiType::RustBuffer(_) => "uniffi::RustBuffer".into(),
+ FfiType::ForeignBytes => "uniffi::ForeignBytes".into(),
+ FfiType::ForeignCallback => "uniffi::ForeignCallback".into(),
+ })
+ }
+
+ /// Get the name of the FfiConverter implementation for this type
+ ///
+ /// - For primitives / standard types this is the type itself.
+ /// - For user-defined types, this is a unique generated name. We then generate a unit-struct
+ /// in the scaffolding code that implements FfiConverter.
+ pub fn ffi_converter_name(type_: &Type) -> askama::Result<String> {
+ Ok(match type_ {
+ // Timestamp/Duraration are handled by standard types
+ Type::Timestamp => "std::time::SystemTime".into(),
+ Type::Duration => "std::time::Duration".into(),
+ // Object is handled by Arc<T>
+ Type::Object(name) => format!("std::sync::Arc<r#{name}>"),
+ // Other user-defined types are handled by a unit-struct that we generate. The
+ // FfiConverter implementation for this can be found in one of the scaffolding template code.
+ //
+ // We generate a unit-struct to sidestep Rust's orphan rules (ADR-0006).
+ //
+ // CallbackInterface is handled by special case code on both the scaffolding and
+ // bindings side. It's not a unit-struct, but the same name generation code works.
+ Type::Enum(_) | Type::Record(_) | Type::Error(_) | Type::CallbackInterface(_) => {
+ format!("FfiConverter{}", type_.canonical_name())
+ }
+ // Wrapper types are implemented by generics that wrap the FfiConverter implementation of the
+ // inner type.
+ Type::Optional(inner) => {
+ format!("std::option::Option<{}>", ffi_converter_name(inner)?)
+ }
+ Type::Sequence(inner) => format!("std::vec::Vec<{}>", ffi_converter_name(inner)?),
+ Type::Map(k, v) => format!(
+ "std::collections::HashMap<{}, {}>",
+ ffi_converter_name(k)?,
+ ffi_converter_name(v)?
+ ),
+ // External and Wrapped bytes have FfiConverters with a predictable name based on the type name.
+ Type::Custom { name, .. } | Type::External { name, .. } => {
+ format!("FfiConverterType{name}")
+ }
+ // Primitive types / strings are implemented by their rust type
+ Type::Int8 => "i8".into(),
+ Type::UInt8 => "u8".into(),
+ Type::Int16 => "i16".into(),
+ Type::UInt16 => "u16".into(),
+ Type::Int32 => "i32".into(),
+ Type::UInt32 => "u32".into(),
+ Type::Int64 => "i64".into(),
+ Type::UInt64 => "u64".into(),
+ Type::Float32 => "f32".into(),
+ Type::Float64 => "f64".into(),
+ Type::String => "String".into(),
+ Type::Boolean => "bool".into(),
+ Type::Unresolved { .. } => {
+ unreachable!("UDL scaffolding code never contains unresolved types")
+ }
+ })
+ }
+
+ // Map a type to Rust code that specifies the FfiConverter implementation.
+ //
+ // This outputs something like `<TheFfiConverterStruct as FfiConverter>`
+ pub fn ffi_converter(type_: &Type) -> Result<String, askama::Error> {
+ Ok(format!(
+ "<{} as uniffi::FfiConverter>",
+ ffi_converter_name(type_)?
+ ))
+ }
+
+ // Turns a `crate-name` into the `crate_name` the .rs code needs to specify.
+ pub fn crate_name_rs(nm: &str) -> Result<String, askama::Error> {
+ Ok(nm.to_string().to_snake_case())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs
new file mode 100644
index 0000000000..58489fd6f6
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs
@@ -0,0 +1,197 @@
+{#
+// For each Callback Interface definition, we assume that there is a corresponding trait defined in Rust client code.
+// If the UDL callback interface and Rust trait's methods don't match, the Rust compiler will complain.
+// We generate:
+// * an init function to accept that `ForeignCallback` from the foreign language, and stores it.
+// * a holder for a `ForeignCallback`, of type `uniffi::ForeignCallbackInternals`.
+// * a proxy `struct` which implements the `trait` that the Callback Interface corresponds to. This
+// is the object that client code interacts with.
+// - for each method, arguments will be packed into a `RustBuffer` and sent over the `ForeignCallback` to be
+// unpacked and called. The return value is packed into another `RustBuffer` and sent back to Rust.
+// - a `Drop` `impl`, which tells the foreign language to forget about the real callback object.
+#}
+{% let trait_name = cbi.name() -%}
+{% let trait_impl = cbi.type_().borrow()|ffi_converter_name -%}
+{% let foreign_callback_internals = format!("foreign_callback_{}_internals", trait_name)|upper -%}
+
+// Register a foreign callback for getting across the FFI.
+#[doc(hidden)]
+static {{ foreign_callback_internals }}: uniffi::ForeignCallbackInternals = uniffi::ForeignCallbackInternals::new();
+
+#[doc(hidden)]
+#[no_mangle]
+pub extern "C" fn {{ cbi.ffi_init_callback().name() }}(callback: uniffi::ForeignCallback, _: &mut uniffi::RustCallStatus) {
+ {{ foreign_callback_internals }}.set_callback(callback);
+ // The call status should be initialized to CALL_SUCCESS, so no need to modify it.
+}
+
+// Make an implementation which will shell out to the foreign language.
+#[doc(hidden)]
+#[derive(Debug)]
+struct {{ trait_impl }} {
+ handle: u64
+}
+
+impl Drop for {{ trait_impl }} {
+ fn drop(&mut self) {
+ let callback = {{ foreign_callback_internals }}.get_callback().unwrap();
+ let mut rbuf = uniffi::RustBuffer::new();
+ unsafe { callback(self.handle, uniffi::IDX_CALLBACK_FREE, Default::default(), &mut rbuf) };
+ }
+}
+
+uniffi::deps::static_assertions::assert_impl_all!({{ trait_impl }}: Send);
+
+impl r#{{ trait_name }} for {{ trait_impl }} {
+ {%- for meth in cbi.methods() %}
+
+ {#- Method declaration #}
+ fn r#{{ meth.name() -}}
+ ({% call rs::arg_list_decl_with_prefix("&self", meth) %})
+ {%- match (meth.return_type(), meth.throws_type()) %}
+ {%- when (Some(return_type), None) %} -> {{ return_type.borrow()|type_rs }}
+ {%- when (Some(return_type), Some(err)) %} -> ::std::result::Result<{{ return_type.borrow()|type_rs }}, {{ err|type_rs }}>
+ {%- when (None, Some(err)) %} -> ::std::result::Result<(), {{ err|type_rs }}>
+ {% else -%}
+ {%- endmatch -%} {
+ {#- Method body #}
+
+ {#- Packing args into a RustBuffer #}
+ {% if meth.arguments().len() == 0 -%}
+ let args_buf = Vec::new();
+ {% else -%}
+ let mut args_buf = Vec::new();
+ {% endif -%}
+ {%- for arg in meth.arguments() %}
+ {{ arg.type_().borrow()|ffi_converter }}::write(r#{{ arg.name() }}, &mut args_buf);
+ {%- endfor -%}
+ let args_rbuf = uniffi::RustBuffer::from_vec(args_buf);
+
+ {#- Calling into foreign code. #}
+ let callback = {{ foreign_callback_internals }}.get_callback().unwrap();
+
+ unsafe {
+ // SAFETY:
+ // * We're passing in a pointer to an empty buffer.
+ // * Nothing allocated, so nothing to drop.
+ // * We expect the callback to write into that a valid allocated instance of a
+ // RustBuffer.
+ let mut ret_rbuf = uniffi::RustBuffer::new();
+ let ret = callback(self.handle, {{ loop.index }}, args_rbuf, &mut ret_rbuf);
+ #[allow(clippy::let_and_return, clippy::let_unit_value)]
+ match ret {
+ 1 => {
+ // 1 indicates success with the return value written to the RustBuffer for
+ // non-void calls.
+ let result = {
+ {% match meth.return_type() -%}
+ {%- when Some(return_type) -%}
+ let vec = ret_rbuf.destroy_into_vec();
+ let mut ret_buf = vec.as_slice();
+ {{ return_type|ffi_converter }}::try_read(&mut ret_buf).unwrap()
+ {%- else %}
+ uniffi::RustBuffer::destroy(ret_rbuf);
+ {%- endmatch %}
+ };
+ {%- if meth.throws() %}
+ Ok(result)
+ {%- else %}
+ result
+ {%- endif %}
+ }
+ -2 => {
+ // -2 indicates an error written to the RustBuffer
+ {% match meth.throws_type() -%}
+ {% when Some(error_type) -%}
+ let vec = ret_rbuf.destroy_into_vec();
+ let mut ret_buf = vec.as_slice();
+ Err({{ error_type|ffi_converter }}::try_read(&mut ret_buf).unwrap())
+ {%- else -%}
+ panic!("Callback return -2, but throws_type() is None");
+ {%- endmatch %}
+ }
+ // 0 is a deprecated method to indicates success for void returns
+ 0 => {
+ uniffi::deps::log::error!("UniFFI: Callback interface returned 0. Please update the bindings code to return 1 for all successful calls");
+ {% match (meth.return_type(), meth.throws()) %}
+ {% when (Some(_), _) %}
+ panic!("Callback returned 0 when we were expecting a return value");
+ {% when (None, false) %}
+ {% when (None, true) %}
+ Ok(())
+ {%- endmatch %}
+ }
+ // -1 indicates an unexpected error
+ {% match meth.throws_type() %}
+ {%- when Some(error_type) -%}
+ -1 => {
+ let reason = if !ret_rbuf.is_empty() {
+ match {{ Type::String.borrow()|ffi_converter }}::try_lift(ret_rbuf) {
+ Ok(s) => s,
+ Err(e) => {
+ uniffi::deps::log::error!("{{ trait_name }} Error reading ret_buf: {e}");
+ String::from("[Error reading reason]")
+ }
+ }
+ } else {
+ uniffi::RustBuffer::destroy(ret_rbuf);
+ String::from("[Unknown Reason]")
+ };
+ let e: {{ error_type|type_rs }} = uniffi::UnexpectedUniFFICallbackError::from_reason(reason).into();
+ Err(e)
+ }
+ {%- else %}
+ -1 => {
+ if !ret_rbuf.is_empty() {
+ let reason = match {{ Type::String.borrow()|ffi_converter }}::try_lift(ret_rbuf) {
+ Ok(s) => s,
+ Err(_) => {
+ String::from("[Error reading reason]")
+ }
+ };
+ panic!("callback failed. Reason: {}", reason);
+ } else {
+ panic!("Callback failed")
+ }
+ },
+ {%- endmatch %}
+ // Other values should never be returned
+ _ => panic!("Callback failed with unexpected return code"),
+ }
+ }
+ }
+ {%- endfor %}
+}
+
+unsafe impl uniffi::FfiConverter for {{ trait_impl }} {
+ // This RustType allows for rust code that inputs this type as a Box<dyn CallbackInterfaceTrait> param
+ type RustType = Box<dyn r#{{ trait_name }}>;
+ type FfiType = u64;
+
+ // Lower and write are tricky to implement because we have a dyn trait as our type. There's
+ // probably a way to, but this carries lots of thread safety risks, down to impedance
+ // mismatches between Rust and foreign languages, and our uncertainty around implementations of
+ // concurrent handlemaps.
+ //
+ // The use case for them is also quite exotic: it's passing a foreign callback back to the foreign
+ // language.
+ //
+ // Until we have some certainty, and use cases, we shouldn't use them.
+ fn lower(_obj: Self::RustType) -> Self::FfiType {
+ panic!("Lowering CallbackInterface not supported")
+ }
+
+ fn write(_obj: Self::RustType, _buf: &mut std::vec::Vec<u8>) {
+ panic!("Writing CallbackInterface not supported")
+ }
+
+ fn try_lift(v: Self::FfiType) -> uniffi::deps::anyhow::Result<Self::RustType> {
+ Ok(Box::new(Self { handle: v }))
+ }
+
+ fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<Self::RustType> {
+ use uniffi::deps::bytes::Buf;
+ uniffi::check_remaining(buf, 8)?;
+ <Self as uniffi::FfiConverter>::try_lift(buf.get_u64())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs
new file mode 100644
index 0000000000..0d83a99b90
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs
@@ -0,0 +1,45 @@
+{#
+// For each enum declared in the UDL, we assume the caller has provided a corresponding
+// rust `enum`. We provide the traits for sending it across the FFI, which will fail to
+// compile if the provided struct has a different shape to the one declared in the UDL.
+//
+// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's
+// public so other crates can refer to it via an `[External='crate'] typedef`
+#}
+
+#[doc(hidden)]
+pub struct {{ e.type_().borrow()|ffi_converter_name }};
+
+#[doc(hidden)]
+impl uniffi::RustBufferFfiConverter for {{ e.type_().borrow()|ffi_converter_name }} {
+ type RustType = r#{{ e.name() }};
+
+ fn write(obj: Self::RustType, buf: &mut std::vec::Vec<u8>) {
+ use uniffi::deps::bytes::BufMut;
+ match obj {
+ {%- for variant in e.variants() %}
+ r#{{ e.name() }}::r#{{ variant.name() }} { {% for field in variant.fields() %}r#{{ field.name() }}, {%- endfor %} } => {
+ buf.put_i32({{ loop.index }});
+ {% for field in variant.fields() -%}
+ {{ field.type_().borrow()|ffi_converter }}::write(r#{{ field.name() }}, buf);
+ {%- endfor %}
+ },
+ {%- endfor %}
+ };
+ }
+
+ fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<r#{{ e.name() }}> {
+ use uniffi::deps::bytes::Buf;
+ uniffi::check_remaining(buf, 4)?;
+ Ok(match buf.get_i32() {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} => r#{{ e.name() }}::r#{{ variant.name() }}{% if variant.has_fields() %} {
+ {% for field in variant.fields() %}
+ r#{{ field.name() }}: {{ field.type_().borrow()|ffi_converter }}::try_read(buf)?,
+ {%- endfor %}
+ }{% endif %},
+ {%- endfor %}
+ v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v),
+ })
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs
new file mode 100644
index 0000000000..7ca1d104fe
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs
@@ -0,0 +1,95 @@
+{#
+// For each error declared in the UDL, we assume the caller has provided a corresponding
+// rust `enum`. We provide the traits for sending it across the FFI, which will fail to
+// compile if the provided struct has a different shape to the one declared in the UDL.
+//
+// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's
+// public so other crates can refer to it via an `[External='crate'] typedef`
+#}
+
+#[doc(hidden)]
+pub struct {{ e.type_().borrow()|ffi_converter_name }};
+
+#[doc(hidden)]
+impl uniffi::RustBufferFfiConverter for {{ e.type_().borrow()|ffi_converter_name }} {
+ type RustType = r#{{ e.name() }};
+
+ {% if e.is_flat() %}
+
+ // For "flat" error enums, we stringify the error on the Rust side and surface that
+ // as the error message in the foreign language.
+
+
+ fn write(obj: r#{{ e.name() }}, buf: &mut std::vec::Vec<u8>) {
+ use uniffi::deps::bytes::BufMut;
+ let msg = obj.to_string();
+ match obj {
+ {%- for variant in e.variants() %}
+ r#{{ e.name() }}::r#{{ variant.name() }}{..} => {
+ buf.put_i32({{ loop.index }});
+ <String as uniffi::FfiConverter>::write(msg, buf);
+ },
+ {%- endfor %}
+ };
+ }
+
+ {%- if ci.should_generate_error_read(e) %}
+ fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<r#{{ e.name() }}> {
+ use uniffi::deps::bytes::Buf;
+ uniffi::check_remaining(buf, 4)?;
+ Ok(match buf.get_i32() {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} => r#{{ e.name() }}::r#{{ variant.name() }}{ },
+ {%- endfor %}
+ v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v),
+ })
+ }
+ {%- else %}
+ fn try_read(_buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<r#{{ e.name() }}> {
+ panic!("try_read not supported for flat errors");
+ }
+ {%- endif %}
+
+ {% else %}
+
+ // For rich structured enums, we map individual fields on the Rust side over to
+ // corresponding fields on the foreign-language side.
+ //
+ // If a variant doesn't have fields defined in the UDL, it's currently still possible that
+ // the Rust enum has fields and they're just not listed. In that case we use the `Variant{..}`
+ // syntax to match the variant while ignoring its fields.
+
+ fn write(obj: r#{{ e.name() }}, buf: &mut std::vec::Vec<u8>) {
+ use uniffi::deps::bytes::BufMut;
+ match obj {
+ {%- for variant in e.variants() %}
+ r#{{ e.name() }}::r#{{ variant.name() }}{% if variant.has_fields() %} { {% for field in variant.fields() %}r#{{ field.name() }}, {%- endfor %} }{% else %}{..}{% endif %} => {
+ buf.put_i32({{ loop.index }});
+ {% for field in variant.fields() -%}
+ {{ field.type_().borrow()|ffi_converter }}::write(r#{{ field.name() }}, buf);
+ {%- endfor %}
+ },
+ {%- endfor %}
+ };
+ }
+
+ fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<r#{{ e.name() }}> {
+ // Note: no need to call should_generate_error_read here, since it is always true for
+ // non-flat errors
+ use uniffi::deps::bytes::Buf;
+ uniffi::check_remaining(buf, 4)?;
+ Ok(match buf.get_i32() {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} => r#{{ e.name() }}::r#{{ variant.name() }}{% if variant.has_fields() %} {
+ {% for field in variant.fields() %}
+ r#{{ field.name() }}: {{ field.type_().borrow()|ffi_converter }}::try_read(buf)?,
+ {%- endfor %}
+ }{% endif %},
+ {%- endfor %}
+ v => uniffi::deps::anyhow::bail!("Invalid {{ e.name() }} enum value: {}", v),
+ })
+ }
+ {% endif %}
+}
+
+impl uniffi::FfiError for {{ e.type_().borrow()|ffi_converter_name }} { }
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs
new file mode 100644
index 0000000000..cfbe080190
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs
@@ -0,0 +1,48 @@
+// Support for external types.
+
+// Types with an external `FfiConverter`...
+{% for (name, crate_name) in ci.iter_external_types() %}
+// `{{ name }}` is defined in `{{ crate_name }}`
+use {{ crate_name|crate_name_rs }}::FfiConverterType{{ name }};
+{% endfor %}
+
+// For custom scaffolding types we need to generate an FfiConverterType based on the
+// UniffiCustomTypeConverter implementation that the library supplies
+{% for (name, builtin) in ci.iter_custom_types() %}
+{% if loop.first %}
+
+// A trait that's in our crate for our external wrapped types to implement.
+trait UniffiCustomTypeConverter {
+ type Builtin;
+
+ fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> where Self: Sized;
+ fn from_custom(obj: Self) -> Self::Builtin;
+}
+
+{%- endif -%}
+
+// Type `{{ name }}` wraps a `{{ builtin.canonical_name() }}`
+#[doc(hidden)]
+pub struct FfiConverterType{{ name }};
+
+unsafe impl uniffi::FfiConverter for FfiConverterType{{ name }} {
+ type RustType = r#{{ name }};
+ type FfiType = {{ FfiType::from(builtin).borrow()|type_ffi }};
+
+ fn lower(obj: {{ name }} ) -> Self::FfiType {
+ <{{ builtin|type_rs }} as uniffi::FfiConverter>::lower(<{{ name }} as UniffiCustomTypeConverter>::from_custom(obj))
+ }
+
+ fn try_lift(v: Self::FfiType) -> uniffi::Result<{{ name }}> {
+ <r#{{ name }} as UniffiCustomTypeConverter>::into_custom(<{{ builtin|type_rs }} as uniffi::FfiConverter>::try_lift(v)?)
+ }
+
+ fn write(obj: {{ name }}, buf: &mut Vec<u8>) {
+ <{{ builtin|type_rs }} as uniffi::FfiConverter>::write(<{{ name }} as UniffiCustomTypeConverter>::from_custom(obj), buf);
+ }
+
+ fn try_read(buf: &mut &[u8]) -> uniffi::Result<r#{{ name }}> {
+ <{{ name }} as UniffiCustomTypeConverter>::into_custom(<{{ builtin|type_rs }} as uniffi::FfiConverter>::try_read(buf)?)
+ }
+}
+{%- endfor -%}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs
new file mode 100644
index 0000000000..3834ad994e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs
@@ -0,0 +1,73 @@
+// For each Object definition, we assume the caller has provided an appropriately-shaped `struct T`
+// with an `impl` for each method on the object. We create an `Arc<T>` for "safely" handing out
+// references to these structs to foreign language code, and we provide a `pub extern "C"` function
+// corresponding to each method.
+//
+// (Note that "safely" is in "scare quotes" - that's because we use functions on an `Arc` that
+// that are inherently unsafe, but the code we generate is safe in practice.)
+//
+// If the caller's implementation of the struct does not match with the methods or types specified
+// in the UDL, then the rust compiler will complain with a (hopefully at least somewhat helpful!)
+// error message when processing this generated code.
+
+{% if obj.uses_deprecated_threadsafe_attribute() %}
+// We want to mark this as `deprecated` - long story short, the only way to
+// sanely do this using `#[deprecated(..)]` is to generate a function with that
+// attribute, then generate call to that function in the object constructors.
+#[deprecated(
+ since = "0.11.0",
+ note = "The `[Threadsafe]` attribute on interfaces is now the default and its use is deprecated - you should upgrade \
+ `{{ obj.name() }}` to remove the `[ThreadSafe]` attribute. \
+ See https://github.com/mozilla/uniffi-rs/#thread-safety for more details"
+)]
+#[allow(non_snake_case)]
+fn uniffi_note_threadsafe_deprecation_{{ obj.name() }}() {}
+{% endif %}
+
+
+// All Object structs must be `Sync + Send`. The generated scaffolding will fail to compile
+// if they are not, but unfortunately it fails with an unactionably obscure error message.
+// By asserting the requirement explicitly, we help Rust produce a more scrutable error message
+// and thus help the user debug why the requirement isn't being met.
+uniffi::deps::static_assertions::assert_impl_all!(r#{{ obj.name() }}: Sync, Send);
+
+{% let ffi_free = obj.ffi_object_free() -%}
+#[doc(hidden)]
+#[no_mangle]
+pub extern "C" fn {{ ffi_free.name() }}(ptr: *const std::os::raw::c_void, call_status: &mut uniffi::RustCallStatus) {
+ uniffi::call_with_output(call_status, || {
+ assert!(!ptr.is_null());
+ {#- turn it into an Arc and explicitly drop it. #}
+ drop(unsafe { std::sync::Arc::from_raw(ptr as *const r#{{ obj.name() }}) })
+ })
+}
+
+{%- for cons in obj.constructors() %}
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern "C" fn r#{{ cons.ffi_func().name() }}(
+ {%- call rs::arg_list_ffi_decl(cons.ffi_func()) %}) -> *const std::os::raw::c_void /* *const {{ obj.name() }} */ {
+ uniffi::deps::log::debug!("{{ cons.ffi_func().name() }}");
+ {% if obj.uses_deprecated_threadsafe_attribute() %}
+ uniffi_note_threadsafe_deprecation_{{ obj.name() }}();
+ {% endif %}
+
+ // If the constructor does not have the same signature as declared in the UDL, then
+ // this attempt to call it will fail with a (somewhat) helpful compiler error.
+ {% call rs::to_rs_constructor_call(obj, cons) %}
+ }
+{%- endfor %}
+
+{%- for meth in obj.methods() %}
+ #[doc(hidden)]
+ #[no_mangle]
+ #[allow(clippy::let_unit_value)] // Sometimes we generate code that binds `_retval` to `()`.
+ pub extern "C" fn r#{{ meth.ffi_func().name() }}(
+ {%- call rs::arg_list_ffi_decl(meth.ffi_func()) %}
+ ) {% call rs::return_signature(meth) %} {
+ uniffi::deps::log::debug!("{{ meth.ffi_func().name() }}");
+ // If the method does not have the same signature as declared in the UDL, then
+ // this attempt to call it will fail with a (somewhat) helpful compiler error.
+ {% call rs::to_rs_method_call(obj, meth) %}
+ }
+{% endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs
new file mode 100644
index 0000000000..6ec85ed036
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs
@@ -0,0 +1,33 @@
+{#
+// For each record declared in the UDL, we assume the caller has provided a corresponding
+// rust `struct` with the declared fields. We provide the traits for sending it across the FFI.
+// If the caller's struct does not match the shape and types declared in the UDL then the rust
+// compiler will complain with a type error.
+//
+// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's
+// public so other crates can refer to it via an `[External='crate'] typedef`
+#}
+
+#[doc(hidden)]
+pub struct {{ rec.type_().borrow()|ffi_converter_name }};
+
+#[doc(hidden)]
+impl uniffi::RustBufferFfiConverter for {{ rec.type_().borrow()|ffi_converter_name }} {
+ type RustType = r#{{ rec.name() }};
+
+ fn write(obj: r#{{ rec.name() }}, buf: &mut std::vec::Vec<u8>) {
+ // If the provided struct doesn't match the fields declared in the UDL, then
+ // the generated code here will fail to compile with somewhat helpful error.
+ {%- for field in rec.fields() %}
+ {{ field.type_().borrow()|ffi_converter }}::write(obj.r#{{ field.name() }}, buf);
+ {%- endfor %}
+ }
+
+ fn try_read(buf: &mut &[u8]) -> uniffi::deps::anyhow::Result<r#{{ rec.name() }}> {
+ Ok(r#{{ rec.name() }} {
+ {%- for field in rec.fields() %}
+ r#{{ field.name() }}: {{ field.type_().borrow()|ffi_converter }}::try_read(buf)?,
+ {%- endfor %}
+ })
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ReexportUniFFIScaffolding.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ReexportUniFFIScaffolding.rs
new file mode 100644
index 0000000000..668f1a2e63
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ReexportUniFFIScaffolding.rs
@@ -0,0 +1,27 @@
+// Code to re-export the UniFFI scaffolding functions.
+//
+// Rust won't always re-export the functions from dependencies
+// ([rust-lang#50007](https://github.com/rust-lang/rust/issues/50007))
+//
+// A workaround for this is to have the dependent crate reference a function from its dependency in
+// an extern "C" function. This is clearly hacky and brittle, but at least we have some unittests
+// that check if this works (fixtures/reexport-scaffolding-macro).
+//
+// The main way we use this macro is for that contain multiple UniFFI components (libxul,
+// megazord). The combined library has a cargo dependency for each component and calls
+// uniffi_reexport_scaffolding!() for each one.
+
+#[doc(hidden)]
+pub fn uniffi_reexport_hack() {
+}
+
+#[macro_export]
+macro_rules! uniffi_reexport_scaffolding {
+ () => {
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern "C" fn {{ ci.namespace() }}_uniffi_reexport_hack() {
+ $crate::uniffi_reexport_hack()
+ }
+ };
+}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RustBuffer.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RustBuffer.rs
new file mode 100644
index 0000000000..39d3f32dcc
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RustBuffer.rs
@@ -0,0 +1,27 @@
+// Everybody gets basic buffer support, since it's needed for passing complex types over the FFI.
+//
+// See `uniffi/src/ffi/rustbuffer.rs` for documentation on these functions
+
+#[allow(clippy::missing_safety_doc)]
+#[no_mangle]
+pub extern "C" fn {{ ci.ffi_rustbuffer_alloc().name() }}(size: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
+ uniffi::ffi::uniffi_rustbuffer_alloc(size, call_status)
+}
+
+#[allow(clippy::missing_safety_doc)]
+#[no_mangle]
+pub unsafe extern "C" fn {{ ci.ffi_rustbuffer_from_bytes().name() }}(bytes: uniffi::ForeignBytes, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
+ uniffi::ffi::uniffi_rustbuffer_from_bytes(bytes, call_status)
+}
+
+#[allow(clippy::missing_safety_doc)]
+#[no_mangle]
+pub unsafe extern "C" fn {{ ci.ffi_rustbuffer_free().name() }}(buf: uniffi::RustBuffer, call_status: &mut uniffi::RustCallStatus) {
+ uniffi::ffi::uniffi_rustbuffer_free(buf, call_status)
+}
+
+#[allow(clippy::missing_safety_doc)]
+#[no_mangle]
+pub unsafe extern "C" fn {{ ci.ffi_rustbuffer_reserve().name() }}(buf: uniffi::RustBuffer, additional: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
+ uniffi::ffi::uniffi_rustbuffer_reserve(buf, additional, call_status)
+}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs
new file mode 100644
index 0000000000..0c6d5a47de
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs
@@ -0,0 +1,17 @@
+{#
+// For each top-level function declared in the UDL, we assume the caller has provided a corresponding
+// rust function of the same name. We provide a `pub extern "C"` wrapper that does type conversions to
+// send data across the FFI, which will fail to compile if the provided function does not match what's
+// specified in the UDL.
+#}
+#[doc(hidden)]
+#[no_mangle]
+#[allow(clippy::let_unit_value)] // Sometimes we generate code that binds `_retval` to `()`.
+pub extern "C" fn r#{{ func.ffi_func().name() }}(
+ {% call rs::arg_list_ffi_decl(func.ffi_func()) %}
+) {% call rs::return_signature(func) %} {
+ // If the provided function does not match the signature specified in the UDL
+ // then this attempt to call it will not compile, and will give guidance as to why.
+ uniffi::deps::log::debug!("{{ func.ffi_func().name() }}");
+ {% call rs::to_rs_function_call(func) %}
+}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/macros.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/macros.rs
new file mode 100644
index 0000000000..7ee37c234e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/macros.rs
@@ -0,0 +1,118 @@
+{#
+// Template to receive calls into rust.
+#}
+
+{%- macro to_rs_call(func) -%}
+r#{{ func.name() }}({% call _arg_list_rs_call(func) -%})
+{%- endmacro -%}
+
+{%- macro _arg_list_rs_call(func) %}
+ {%- for arg in func.full_arguments() %}
+ match {{- arg.type_().borrow()|ffi_converter }}::try_lift(r#{{ arg.name() }}) {
+ {%- if arg.by_ref() %}
+ Ok(ref val) => val,
+ {%- else %}
+ Ok(val) => val,
+ {%- endif %}
+
+ {#- If this function returns an error, we attempt to downcast errors doing arg
+ conversions to this error. If the downcast fails or the function doesn't
+ return an error, we just panic.
+ -#}
+ {%- match func.throws_type() -%}
+ {%- when Some with (e) %}
+ Err(err) => return Err(uniffi::lower_anyhow_error_or_panic::<{{ e|ffi_converter_name }}>(err, "{{ arg.name() }}")),
+ {%- else %}
+ Err(err) => panic!("Failed to convert arg '{}': {}", "{{ arg.name() }}", err),
+ {%- endmatch %}
+ }
+ {%- if !loop.last %},{% endif %}
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in the _UniFFILib function declarations.
+// Note unfiltered name but type_ffi filters.
+-#}
+{%- macro arg_list_ffi_decl(func) %}
+ {%- for arg in func.arguments() %}
+ r#{{- arg.name() }}: {{ arg.type_().borrow()|type_ffi -}},
+ {%- endfor %}
+ call_status: &mut uniffi::RustCallStatus
+{%- endmacro -%}
+
+{%- macro arg_list_decl_with_prefix(prefix, meth) %}
+ {{- prefix -}}
+ {%- if meth.arguments().len() > 0 %}, {# whitespace #}
+ {%- for arg in meth.arguments() %}
+ r#{{- arg.name() }}: {{ arg.type_().borrow()|type_rs -}}{% if loop.last %}{% else %},{% endif %}
+ {%- endfor %}
+ {%- endif %}
+{%- endmacro -%}
+
+{% macro return_signature(func) %}{% match func.ffi_func().return_type() %}{% when Some with (return_type) %} -> {% call return_type_func(func) %}{%- else -%}{%- endmatch -%}{%- endmacro -%}
+
+{% macro return_type_func(func) %}{% match func.ffi_func().return_type() %}{% when Some with (return_type) %}{{ return_type|type_ffi }}{%- else -%}(){%- endmatch -%}{%- endmacro -%}
+
+{% macro ret(func) %}{% match func.return_type() %}{% when Some with (return_type) %}{{ return_type|ffi_converter }}::lower(_retval){% else %}_retval{% endmatch %}{% endmacro %}
+
+{% macro construct(obj, cons) %}
+ r#{{- obj.name() }}::{% call to_rs_call(cons) -%}
+{% endmacro %}
+
+{% macro to_rs_constructor_call(obj, cons) %}
+{% match cons.throws_type() %}
+{% when Some with (e) %}
+ uniffi::call_with_result(call_status, || {
+ let _new = {% call construct(obj, cons) %}.map_err(Into::into).map_err({{ e|ffi_converter }}::lower)?;
+ let _arc = std::sync::Arc::new(_new);
+ Ok({{ obj.type_().borrow()|ffi_converter }}::lower(_arc))
+ })
+{% else %}
+ uniffi::call_with_output(call_status, || {
+ let _new = {% call construct(obj, cons) %};
+ let _arc = std::sync::Arc::new(_new);
+ {{ obj.type_().borrow()|ffi_converter }}::lower(_arc)
+ })
+{% endmatch %}
+{% endmacro %}
+
+{% macro to_rs_method_call(obj, meth) -%}
+{% match meth.throws_type() -%}
+{% when Some with (e) -%}
+uniffi::call_with_result(call_status, || {
+ let _retval = r#{{ obj.name() }}::{% call to_rs_call(meth) %}.map_err(Into::into).map_err({{ e|ffi_converter }}::lower)?;
+ Ok({% call ret(meth) %})
+})
+{% else %}
+uniffi::call_with_output(call_status, || {
+ {% match meth.return_type() -%}
+ {% when Some with (return_type) -%}
+ let retval = r#{{ obj.name() }}::{% call to_rs_call(meth) %};
+ {{ return_type|ffi_converter }}::lower(retval)
+ {% else -%}
+ r#{{ obj.name() }}::{% call to_rs_call(meth) %}
+ {% endmatch -%}
+})
+{% endmatch -%}
+{% endmacro -%}
+
+{% macro to_rs_function_call(func) %}
+{% match func.throws_type() %}
+{% when Some with (e) %}
+uniffi::call_with_result(call_status, || {
+ let _retval = {% call to_rs_call(func) %}.map_err(Into::into).map_err({{ e|ffi_converter }}::lower)?;
+ Ok({% call ret(func) %})
+})
+{% else %}
+uniffi::call_with_output(call_status, || {
+ {% match func.return_type() -%}
+ {% when Some with (return_type) -%}
+ {{ return_type|ffi_converter }}::lower({% call to_rs_call(func) %})
+ {% else -%}
+ {% if func.full_arguments().is_empty() %}#[allow(clippy::redundant_closure)]{% endif %}
+ {% call to_rs_call(func) %}
+ {% endmatch -%}
+})
+{% endmatch %}
+{% endmacro %}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs
new file mode 100644
index 0000000000..87414659ae
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs
@@ -0,0 +1,58 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+{% import "macros.rs" as rs %}
+
+// Check for compatibility between `uniffi` and `uniffi_bindgen` versions.
+// Note that we have an error message on the same line as the assertion.
+// This is important, because if the assertion fails, the compiler only
+// seems to show that single line as context for the user.
+uniffi::assert_compatible_version!("{{ uniffi_version }}"); // Please check that you depend on version {{ uniffi_version }} of the `uniffi` crate.
+
+{% for ty in ci.iter_types() %}
+{%- match ty %}
+{%- when Type::Map with (k, v) -%}
+{# Next comment MUST be after the line to be in the compiler output #}
+uniffi::deps::static_assertions::assert_impl_all!({{ k|type_rs }}: ::std::cmp::Eq, ::std::hash::Hash); // record<{{ k|type_rs }}, {{ v|type_rs }}>
+{%- else %}
+{%- endmatch %}
+{% endfor %}
+
+{% include "RustBuffer.rs" %}
+
+// Error definitions, corresponding to `error` in the UDL.
+{% for e in ci.error_definitions() %}
+{% include "ErrorTemplate.rs" %}
+{% endfor %}
+
+// Enum definitions, corresponding to `enum` in UDL.
+{% for e in ci.enum_definitions() %}
+{% include "EnumTemplate.rs" %}
+{% endfor %}
+
+// Record definitions, implemented as method-less structs, corresponding to `dictionary` objects.
+{% for rec in ci.record_definitions() %}
+{% include "RecordTemplate.rs" %}
+{% endfor %}
+
+// Top level functions, corresponding to UDL `namespace` functions.
+{%- for func in ci.function_definitions() %}
+{% include "TopLevelFunctionTemplate.rs" %}
+{% endfor -%}
+
+// Object definitions, corresponding to UDL `interface` definitions.
+{% for obj in ci.object_definitions() %}
+{% include "ObjectTemplate.rs" %}
+{% endfor %}
+
+// Callback Interface definitions, corresponding to UDL `callback interface` definitions.
+{% for cbi in ci.callback_interface_definitions() %}
+{% include "CallbackInterfaceTemplate.rs" %}
+{% endfor %}
+
+// External and Wrapped types
+{% include "ExternalTypesTemplate.rs" %}
+
+// The `reexport_uniffi_scaffolding` macro
+{% include "ReexportUniFFIScaffolding.rs" %}
+
+{%- import "macros.rs" as rs -%}
diff --git a/third_party/rust/uniffi_build/.cargo-checksum.json b/third_party/rust/uniffi_build/.cargo-checksum.json
new file mode 100644
index 0000000000..c5bdbe8be2
--- /dev/null
+++ b/third_party/rust/uniffi_build/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"b0825cba8bdc4e5592b196c95bc55ef417324b440270642b8d6ed7c87f6c853b","src/lib.rs":"e344fb4e5390a17f151456d8d902b554399ef8934744ea4c7aa27f285ab59dfa"},"package":"0ee1a28368ff3d83717e3d3e2e15a66269c43488c3f036914131bb68892f29fb"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_build/Cargo.toml b/third_party/rust/uniffi_build/Cargo.toml
new file mode 100644
index 0000000000..c7a0cced61
--- /dev/null
+++ b/third_party/rust/uniffi_build/Cargo.toml
@@ -0,0 +1,39 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi_build"
+version = "0.23.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+description = "a multi-language bindings generator for rust (build script helpers)"
+homepage = "https://mozilla.github.io/uniffi-rs"
+documentation = "https://mozilla.github.io/uniffi-rs"
+keywords = [
+ "ffi",
+ "bindgen",
+]
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/uniffi-rs"
+
+[dependencies.anyhow]
+version = "1"
+
+[dependencies.camino]
+version = "1.0.8"
+
+[dependencies.uniffi_bindgen]
+version = "=0.23.0"
+default-features = false
+
+[features]
+builtin-bindgen = []
+default = []
diff --git a/third_party/rust/uniffi_build/src/lib.rs b/third_party/rust/uniffi_build/src/lib.rs
new file mode 100644
index 0000000000..6a5bb9bad8
--- /dev/null
+++ b/third_party/rust/uniffi_build/src/lib.rs
@@ -0,0 +1,31 @@
+/* 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 anyhow::{Context, Result};
+use camino::Utf8Path;
+use std::env;
+
+/// Generate the rust "scaffolding" required to build a uniffi component.
+///
+/// Given the path to an UDL file, this function will call the `uniffi-bindgen`
+/// command-line tool to generate the `pub extern "C"` functions and other supporting
+/// code required to expose the defined interface from Rust. The expectation is that
+/// this will be called from a crate's build script, and the resulting file will
+/// be `include!()`ed into the build.
+///
+/// Given an UDL file named `example.udl`, the generated scaffolding will be written
+/// into a file named `example.uniffi.rs` in the `$OUT_DIR` directory.
+pub fn generate_scaffolding(udl_file: impl AsRef<Utf8Path>) -> Result<()> {
+ let udl_file = udl_file.as_ref();
+
+ println!("cargo:rerun-if-changed={udl_file}");
+ // The UNIFFI_TESTS_DISABLE_EXTENSIONS variable disables some bindings, but it is evaluated
+ // at *build* time, so we need to rebuild when it changes.
+ println!("cargo:rerun-if-env-changed=UNIFFI_TESTS_DISABLE_EXTENSIONS");
+ // Why don't we just depend on uniffi-bindgen and call the public functions?
+ // Calling the command line helps making sure that the generated swift/Kotlin/whatever
+ // bindings were generated with the same version of uniffi as the Rust scaffolding code.
+ let out_dir = env::var("OUT_DIR").context("$OUT_DIR missing?!")?;
+ uniffi_bindgen::generate_component_scaffolding(udl_file, None, Some(out_dir.as_ref()), false)
+}
diff --git a/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json b/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json
new file mode 100644
index 0000000000..659eac132f
--- /dev/null
+++ b/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"6754bdd7074b1fda85a100034806a9fe0e66f3a04606a5e514fd3a80736259a6","src/lib.rs":"c68c69a1cf6a69e5fe78f7b069364a265c5bb6ce8c0abf0b5745eca01c79604a"},"package":"03de61393a42b4ad4984a3763c0600594ac3e57e5aaa1d05cede933958987c03"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_checksum_derive/Cargo.toml b/third_party/rust/uniffi_checksum_derive/Cargo.toml
new file mode 100644
index 0000000000..236e6f56ab
--- /dev/null
+++ b/third_party/rust/uniffi_checksum_derive/Cargo.toml
@@ -0,0 +1,42 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi_checksum_derive"
+version = "0.23.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+description = "a multi-language bindings generator for rust (checksum custom derive)"
+homepage = "https://mozilla.github.io/uniffi-rs"
+documentation = "https://mozilla.github.io/uniffi-rs"
+keywords = [
+ "ffi",
+ "bindgen",
+]
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/uniffi-rs"
+
+[lib]
+proc-macro = true
+
+[dependencies.quote]
+version = "1.0"
+
+[dependencies.syn]
+version = "1.0"
+features = [
+ "derive",
+ "parsing",
+]
+
+[features]
+default = []
+nightly = []
diff --git a/third_party/rust/uniffi_checksum_derive/src/lib.rs b/third_party/rust/uniffi_checksum_derive/src/lib.rs
new file mode 100644
index 0000000000..2fefdb2574
--- /dev/null
+++ b/third_party/rust/uniffi_checksum_derive/src/lib.rs
@@ -0,0 +1,134 @@
+/* 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/. */
+#![cfg_attr(feature = "nightly", feature(proc_macro_expand))]
+
+//! Custom derive for uniffi_meta::Checksum
+
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use syn::{parse_macro_input, Attribute, Data, DeriveInput, Expr, ExprLit, Fields, Index, Lit};
+
+fn has_ignore_attribute(attrs: &[Attribute]) -> bool {
+ attrs.iter().any(|attr| {
+ if attr.path.is_ident("checksum_ignore") {
+ if !attr.tokens.is_empty() {
+ panic!("#[checksum_ignore] doesn't accept extra information");
+ }
+ true
+ } else {
+ false
+ }
+ })
+}
+
+#[proc_macro_derive(Checksum, attributes(checksum_ignore))]
+pub fn checksum_derive(input: TokenStream) -> TokenStream {
+ let input: DeriveInput = parse_macro_input!(input);
+
+ let name = input.ident;
+
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+ let code = match input.data {
+ Data::Enum(enum_)
+ if enum_.variants.len() == 1
+ && enum_
+ .variants
+ .iter()
+ .all(|variant| matches!(variant.fields, Fields::Unit)) =>
+ {
+ quote!()
+ }
+ Data::Enum(enum_) => {
+ let mut next_discriminant = 0u64;
+ let match_inner = enum_.variants.iter().map(|variant| {
+ let ident = &variant.ident;
+ if has_ignore_attribute(&variant.attrs) {
+ panic!("#[checksum_ignore] is not supported in enums");
+ }
+ match &variant.discriminant {
+ Some((_, Expr::Lit(ExprLit { lit: Lit::Int(value), .. }))) => {
+ next_discriminant = value.base10_parse::<u64>().unwrap();
+ }
+ Some(_) => {
+ panic!("#[derive(Checksum)] doesn't support non-numeric explicit discriminants in enums");
+ }
+ None => {}
+ }
+ let discriminant = quote! { state.write(&#next_discriminant.to_le_bytes()) };
+ next_discriminant += 1;
+ match &variant.fields {
+ Fields::Unnamed(fields) => {
+ let field_idents = fields
+ .unnamed
+ .iter()
+ .enumerate()
+ .map(|(num, _)| format_ident!("__self_{}", num));
+ let field_stmts = field_idents
+ .clone()
+ .map(|ident| quote! { Checksum::checksum(#ident, state); });
+ quote! {
+ Self::#ident(#(#field_idents,)*) => {
+ #discriminant;
+ #(#field_stmts)*
+ }
+ }
+ }
+ Fields::Named(fields) => {
+ let field_idents = fields
+ .named
+ .iter()
+ .map(|field| field.ident.as_ref().unwrap());
+ let field_stmts = field_idents
+ .clone()
+ .map(|ident| quote! { Checksum::checksum(#ident, state); });
+ quote! {
+ Self::#ident { #(#field_idents,)* } => {
+ #discriminant;
+ #(#field_stmts)*
+ }
+ }
+ }
+ Fields::Unit => quote! { Self::#ident => #discriminant, },
+ }
+ });
+ quote! {
+ match self {
+ #(#match_inner)*
+ }
+ }
+ }
+ Data::Struct(struct_) => {
+ let stmts = struct_
+ .fields
+ .iter()
+ .enumerate()
+ .filter_map(|(num, field)| {
+ (!has_ignore_attribute(&field.attrs)).then(|| match field.ident.as_ref() {
+ Some(ident) => quote! { Checksum::checksum(&self.#ident, state); },
+ None => {
+ let i = Index::from(num);
+ quote! { Checksum::checksum(&self.#i, state); }
+ }
+ })
+ });
+ quote! {
+ #(#stmts)*
+ }
+ }
+ Data::Union(_) => {
+ panic!("#[derive(Checksum)] is not supported for unions");
+ }
+ };
+
+ quote! {
+ #[automatically_derived]
+ impl #impl_generics Checksum for #name #ty_generics #where_clause {
+ fn checksum<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
+ #code
+ }
+ }
+ }
+ .into()
+}
diff --git a/third_party/rust/uniffi_core/.cargo-checksum.json b/third_party/rust/uniffi_core/.cargo-checksum.json
new file mode 100644
index 0000000000..eac1ca0334
--- /dev/null
+++ b/third_party/rust/uniffi_core/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"60d3960c22668433dd40198e39b2e6fbe584a041e05929dd243a1836979aa685","release.toml":"661f6aa0b76c438ea2ca1138594286482444d345c69414854acfcb3812fa42b9","src/ffi/ffidefault.rs":"c7ab752fffed17c3fabb60e818ad1d093482f95dd0bdeae6871287695c583e48","src/ffi/foreignbytes.rs":"37061e2da7135576abccb86fe27b4fefc054586a040f2ca81fe9858d5649e887","src/ffi/foreigncallbacks.rs":"d28fec5caf3fce7c52e230df061551c51064105a8e2b8ca3fc1b6b02073f6704","src/ffi/mod.rs":"3fb3b74607066e0052fc91168e9473dbf82dbae562f85c33774a7f5f6b350616","src/ffi/rustbuffer.rs":"b773637d9e4651b80cd16f7a02ed75846d02ce0a9e32b718ce644cdba3a83cdd","src/ffi/rustcalls.rs":"4a85a90b0d46974ab9e80e403a1cddaa252337b227e1a672bb6fdbbca5a66259","src/lib.rs":"6cb9e1d4f0a9e0acb1f65044f7af26ce1320a3ce19af363feb90b1c9789846d7","src/panichook.rs":"9f49c7994a8e5489c1105c488bb3f8c5571bc5f813e7be90441eca15da5c9851"},"package":"7a2b4852d638d74ca2d70e450475efb6d91fe6d54a7cd8d6bd80ad2ee6cd7daa"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_core/Cargo.toml b/third_party/rust/uniffi_core/Cargo.toml
new file mode 100644
index 0000000000..6dcfd8fdcd
--- /dev/null
+++ b/third_party/rust/uniffi_core/Cargo.toml
@@ -0,0 +1,52 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi_core"
+version = "0.23.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+description = "a multi-language bindings generator for rust (runtime support code)"
+homepage = "https://mozilla.github.io/uniffi-rs"
+documentation = "https://mozilla.github.io/uniffi-rs"
+keywords = [
+ "ffi",
+ "bindgen",
+]
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/uniffi-rs"
+
+[dependencies.anyhow]
+version = "1"
+
+[dependencies.bytes]
+version = "1.0"
+
+[dependencies.camino]
+version = "1.0.8"
+
+[dependencies.cargo_metadata]
+version = "0.15"
+
+[dependencies.log]
+version = "0.4"
+
+[dependencies.once_cell]
+version = "1.12"
+
+[dependencies.paste]
+version = "1.0"
+
+[dependencies.static_assertions]
+version = "1.1.0"
+
+[features]
+default = []
diff --git a/third_party/rust/uniffi_core/release.toml b/third_party/rust/uniffi_core/release.toml
new file mode 100644
index 0000000000..a06da056ec
--- /dev/null
+++ b/third_party/rust/uniffi_core/release.toml
@@ -0,0 +1,16 @@
+# Note that this `release.toml` exists to capture things that must only be
+# done once for `cargo release-backend-crates`.
+#
+# [../uniffi/release.toml](../uniffi/release.toml) captures things that must only be done for `cargo release-uniffi`
+#
+# All other config exists in [../release.toml](../release.toml).
+
+tag = false
+
+# This is how we manage the sections in CHANGELOG.md
+pre-release-replacements = [
+ {file="../CHANGELOG.md", search="\\[\\[UnreleasedBackendVersion\\]\\]", replace="v{{version}}", exactly=1},
+ {file="../CHANGELOG.md", search="\\[\\[ReleaseDate\\]\\]", replace="{{date}}", exactly=1},
+ {file="../CHANGELOG.md", search="\\.\\.\\.HEAD\\)", replace="...{{tag_name}})", exactly=1},
+ {file="../CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n\n## [[UnreleasedUniFFIVersion]] (backend crates: [[UnreleasedBackendVersion]]) - (_[[ReleaseDate]]_)\n\n[All changes in [[UnreleasedVersion]]](https://github.com/mozilla/uniffi-rs/compare/v{{version}}...HEAD).", exactly=1},
+]
diff --git a/third_party/rust/uniffi_core/src/ffi/ffidefault.rs b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs
new file mode 100644
index 0000000000..f247312be8
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs
@@ -0,0 +1,52 @@
+/* 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/. */
+
+//! FfiDefault trait
+//!
+//! When we make a FFI call into Rust we always need to return a value, even if that value will be
+//! ignored because we're flagging an exception. This trait defines what that value is for our
+//! supported FFI types.
+
+use paste::paste;
+
+pub trait FfiDefault {
+ fn ffi_default() -> Self;
+}
+
+// Most types can be handled by delegating to Default
+macro_rules! impl_ffi_default_with_default {
+ ($($T:ty,)+) => { impl_ffi_default_with_default!($($T),+); };
+ ($($T:ty),*) => {
+ $(
+ paste! {
+ impl FfiDefault for $T {
+ fn ffi_default() -> Self {
+ $T::default()
+ }
+ }
+ }
+ )*
+ };
+}
+
+impl_ffi_default_with_default! {
+ i8, u8, i16, u16, i32, u32, i64, u64, f32, f64
+}
+
+// Implement FfiDefault for the remaining types
+impl FfiDefault for () {
+ fn ffi_default() {}
+}
+
+impl FfiDefault for *const std::ffi::c_void {
+ fn ffi_default() -> Self {
+ std::ptr::null()
+ }
+}
+
+impl FfiDefault for crate::RustBuffer {
+ fn ffi_default() -> Self {
+ unsafe { Self::from_raw_parts(std::ptr::null_mut(), 0, 0) }
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/ffi/foreignbytes.rs b/third_party/rust/uniffi_core/src/ffi/foreignbytes.rs
new file mode 100644
index 0000000000..5ec93118ad
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/foreignbytes.rs
@@ -0,0 +1,118 @@
+/* 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/. */
+
+/// Support for reading a slice of foreign-language-allocated bytes over the FFI.
+///
+/// Foreign language code can pass a slice of bytes by providing a data pointer
+/// and length, and this struct provides a convenient wrapper for working with
+/// that pair. Naturally, this can be tremendously unsafe! So here are the details:
+///
+/// * The foreign language code must ensure the provided buffer stays alive
+/// and unchanged for the duration of the call to which the `ForeignBytes`
+/// struct was provided.
+///
+/// To work with the bytes in Rust code, use `as_slice()` to view the data
+/// as a `&[u8]`.
+///
+/// Implementation note: all the fields of this struct are private and it has no
+/// constructors, so consuming crates cant create instances of it. If you've
+/// got a `ForeignBytes`, then you received it over the FFI and are assuming that
+/// the foreign language code is upholding the above invariants.
+///
+/// This struct is based on `ByteBuffer` from the `ffi-support` crate, but modified
+/// to give a read-only view of externally-provided bytes.
+#[repr(C)]
+pub struct ForeignBytes {
+ /// The length of the pointed-to data.
+ /// We use an `i32` for compatibility with JNA.
+ len: i32,
+ /// The pointer to the foreign-owned bytes.
+ data: *const u8,
+}
+
+impl ForeignBytes {
+ /// Creates a `ForeignBytes` from its constituent fields.
+ ///
+ /// This is intended mainly as an internal convenience function and should not
+ /// be used outside of this module.
+ ///
+ /// # Safety
+ ///
+ /// You must ensure that the raw parts uphold the documented invariants of this class.
+ pub unsafe fn from_raw_parts(data: *const u8, len: i32) -> Self {
+ Self { len, data }
+ }
+
+ /// View the foreign bytes as a `&[u8]`.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the provided struct has a null pointer but non-zero length.
+ /// Panics if the provided length is negative.
+ pub fn as_slice(&self) -> &[u8] {
+ if self.data.is_null() {
+ assert!(self.len == 0, "null ForeignBytes had non-zero length");
+ &[]
+ } else {
+ unsafe { std::slice::from_raw_parts(self.data, self.len()) }
+ }
+ }
+
+ /// Get the length of this slice of bytes.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the provided length is negative.
+ pub fn len(&self) -> usize {
+ self.len
+ .try_into()
+ .expect("bytes length negative or overflowed")
+ }
+
+ /// Returns true if the length of this slice of bytes is 0.
+ pub fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ #[test]
+ fn test_foreignbytes_access() {
+ let v = vec![1u8, 2, 3];
+ let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), 3) };
+ assert_eq!(fbuf.len(), 3);
+ assert_eq!(fbuf.as_slice(), &[1u8, 2, 3]);
+ }
+
+ #[test]
+ fn test_foreignbytes_empty() {
+ let v = Vec::<u8>::new();
+ let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), 0) };
+ assert_eq!(fbuf.len(), 0);
+ assert_eq!(fbuf.as_slice(), &[0u8; 0]);
+ }
+
+ #[test]
+ fn test_foreignbytes_null_means_empty() {
+ let fbuf = unsafe { ForeignBytes::from_raw_parts(std::ptr::null_mut(), 0) };
+ assert_eq!(fbuf.as_slice(), &[0u8; 0]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_foreignbytes_null_must_have_zero_length() {
+ let fbuf = unsafe { ForeignBytes::from_raw_parts(std::ptr::null_mut(), 12) };
+ fbuf.as_slice();
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_foreignbytes_provided_len_must_be_non_negative() {
+ let v = vec![0u8, 1, 2];
+ let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), -1) };
+ fbuf.as_slice();
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs
new file mode 100644
index 0000000000..9d513353a6
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs
@@ -0,0 +1,229 @@
+/* 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/. */
+
+//! Callback interfaces are traits specified in UDL which can be implemented by foreign languages.
+//!
+//! # Using callback interfaces
+//!
+//! 1. Define a Rust trait.
+//!
+//! This toy example defines a way of Rust accessing a key-value store exposed
+//! by the host operating system (e.g. the key chain).
+//!
+//! ```
+//! trait Keychain: Send {
+//! fn get(&self, key: String) -> Option<String>;
+//! fn put(&self, key: String, value: String);
+//! }
+//! ```
+//!
+//! 2. Define a callback interface in the UDL
+//!
+//! ```idl
+//! callback interface Keychain {
+//! string? get(string key);
+//! void put(string key, string data);
+//! };
+//! ```
+//!
+//! 3. And allow it to be passed into Rust.
+//!
+//! Here, we define a constructor to pass the keychain to rust, and then another method
+//! which may use it.
+//!
+//! In UDL:
+//! ```idl
+//! object Authenticator {
+//! constructor(Keychain keychain);
+//! void login();
+//! }
+//! ```
+//!
+//! In Rust:
+//!
+//! ```
+//!# trait Keychain: Send {
+//!# fn get(&self, key: String) -> Option<String>;
+//!# fn put(&self, key: String, value: String);
+//!# }
+//! struct Authenticator {
+//! keychain: Box<dyn Keychain>,
+//! }
+//!
+//! impl Authenticator {
+//! pub fn new(keychain: Box<dyn Keychain>) -> Self {
+//! Self { keychain }
+//! }
+//! pub fn login(&self) {
+//! let username = self.keychain.get("username".into());
+//! let password = self.keychain.get("password".into());
+//! }
+//! }
+//! ```
+//! 4. Create an foreign language implementation of the callback interface.
+//!
+//! In this example, here's a Kotlin implementation.
+//!
+//! ```kotlin
+//! class AndroidKeychain: Keychain {
+//! override fun get(key: String): String? {
+//! // … elide the implementation.
+//! return value
+//! }
+//! override fun put(key: String) {
+//! // … elide the implementation.
+//! }
+//! }
+//! ```
+//! 5. Pass the implementation to Rust.
+//!
+//! Again, in Kotlin
+//!
+//! ```kotlin
+//! val authenticator = Authenticator(AndroidKeychain())
+//! authenticator.login()
+//! ```
+//!
+//! # How it works.
+//!
+//! ## High level
+//!
+//! Uniffi generates a protocol or interface in client code in the foreign language must implement.
+//!
+//! For each callback interface, a `CallbackInternals` (on the Foreign Language side) and `ForeignCallbackInternals`
+//! (on Rust side) manages the process through a `ForeignCallback`. There is one `ForeignCallback` per callback interface.
+//!
+//! Passing a callback interface implementation from foreign language (e.g. `AndroidKeychain`) into Rust causes the
+//! `KeychainCallbackInternals` to store the instance in a handlemap.
+//!
+//! The object handle is passed over to Rust, and used to instantiate a struct `KeychainProxy` which implements
+//! the trait. This proxy implementation is generate by Uniffi. The `KeychainProxy` object is then passed to
+//! client code as `Box<dyn Keychain>`.
+//!
+//! Methods on `KeychainProxy` objects (e.g. `self.keychain.get("username".into())`) encode the arguments into a `RustBuffer`.
+//! Using the `ForeignCallback`, it calls the `CallbackInternals` object on the foreign language side using the
+//! object handle, and the method selector.
+//!
+//! The `CallbackInternals` object unpacks the arguments from the passed buffer, gets the object out from the handlemap,
+//! and calls the actual implementation of the method.
+//!
+//! If there's a return value, it is packed up in to another `RustBuffer` and used as the return value for
+//! `ForeignCallback`. The caller of `ForeignCallback`, the `KeychainProxy` unpacks the returned buffer into the correct
+//! type and then returns to client code.
+//!
+
+use super::RustBuffer;
+use std::fmt;
+use std::os::raw::c_int;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+/// ForeignCallback is the Rust representation of a foreign language function.
+/// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface,
+/// at library start up time.
+/// Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language.
+///
+/// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object
+/// that implements the callback interface/trait.
+/// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from
+/// the IDL. The index is 1 indexed. Note that the list of methods is generated by at uniffi from the IDL and used in all
+/// bindings: so we can rely on the method list being stable within the same run of uniffi.
+/// * `args` is a serialized buffer of arguments to the function. UniFFI will deserialize it before
+/// passing individual arguments to the user's callback.
+/// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a
+/// buffer to write the result into.
+/// * A callback returns:
+/// - `-2` An error occurred that was serialized to buf_ptr
+/// - `-1` An unexpected error occurred
+/// - `0` is a deprecated way to signal that if the call succeeded, but did not modify buf_ptr
+/// - `1` If the call succeeded. For non-void functions the return value should be serialized
+/// to buf_ptr.
+/// Note: The output buffer might still contain 0 bytes of data.
+pub type ForeignCallback = unsafe extern "C" fn(
+ handle: u64,
+ method: u32,
+ args: RustBuffer,
+ buf_ptr: *mut RustBuffer,
+) -> c_int;
+
+/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it,
+/// and it can be deleted from the handle map.
+pub const IDX_CALLBACK_FREE: u32 = 0;
+
+// Overly-paranoid sanity checking to ensure that these types are
+// convertible between each-other. `transmute` actually should check this for
+// us too, but this helps document the invariants we rely on in this code.
+//
+// Note that these are guaranteed by
+// https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html
+// and thus this is a little paranoid.
+static_assertions::assert_eq_size!(usize, ForeignCallback);
+static_assertions::assert_eq_size!(usize, Option<ForeignCallback>);
+
+/// Struct to hold a foreign callback.
+pub struct ForeignCallbackInternals {
+ callback_ptr: AtomicUsize,
+}
+
+const EMPTY_PTR: usize = 0;
+
+impl ForeignCallbackInternals {
+ pub const fn new() -> Self {
+ ForeignCallbackInternals {
+ callback_ptr: AtomicUsize::new(EMPTY_PTR),
+ }
+ }
+
+ pub fn set_callback(&self, callback: ForeignCallback) {
+ let as_usize = callback as usize;
+ let old_ptr = self.callback_ptr.compare_exchange(
+ EMPTY_PTR,
+ as_usize,
+ Ordering::SeqCst,
+ Ordering::SeqCst,
+ );
+ match old_ptr {
+ // We get the previous value back. If this is anything except EMPTY_PTR,
+ // then this has been set before we get here.
+ Ok(EMPTY_PTR) => (),
+ _ =>
+ // This is an internal bug, the other side of the FFI should ensure
+ // it sets this only once.
+ {
+ panic!("Bug: call set_callback multiple times. This is likely a uniffi bug")
+ }
+ };
+ }
+
+ pub fn get_callback(&self) -> Option<ForeignCallback> {
+ let ptr_value = self.callback_ptr.load(Ordering::SeqCst);
+ unsafe { std::mem::transmute::<usize, Option<ForeignCallback>>(ptr_value) }
+ }
+}
+
+/// Used when internal/unexpected error happened when calling a foreign callback, for example when
+/// a unknown exception is raised
+///
+/// User callback error types must implement a From impl from this type to their own error type.
+#[derive(Debug)]
+pub struct UnexpectedUniFFICallbackError {
+ pub reason: String,
+}
+
+impl UnexpectedUniFFICallbackError {
+ pub fn from_reason(reason: String) -> Self {
+ Self { reason }
+ }
+}
+
+impl fmt::Display for UnexpectedUniFFICallbackError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "UnexpectedUniFFICallbackError(reason: {:?})",
+ self.reason
+ )
+ }
+}
+
+impl std::error::Error for UnexpectedUniFFICallbackError {}
diff --git a/third_party/rust/uniffi_core/src/ffi/mod.rs b/third_party/rust/uniffi_core/src/ffi/mod.rs
new file mode 100644
index 0000000000..73ee721435
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/mod.rs
@@ -0,0 +1,15 @@
+/* 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/. */
+
+pub mod ffidefault;
+pub mod foreignbytes;
+pub mod foreigncallbacks;
+pub mod rustbuffer;
+pub mod rustcalls;
+
+use ffidefault::FfiDefault;
+pub use foreignbytes::*;
+pub use foreigncallbacks::*;
+pub use rustbuffer::*;
+pub use rustcalls::*;
diff --git a/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs
new file mode 100644
index 0000000000..63af586fb6
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs
@@ -0,0 +1,353 @@
+/* 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::ffi::{call_with_output, ForeignBytes, RustCallStatus};
+
+/// Support for passing an allocated-by-Rust buffer of bytes over the FFI.
+///
+/// We can pass a `Vec<u8>` to foreign language code by decomposing it into
+/// its raw parts (buffer pointer, length, and capacity) and passing those
+/// around as a struct. Naturally, this can be tremendously unsafe! So here
+/// are the details:
+///
+/// * `RustBuffer` structs must only ever be constructed from a `Vec<u8>`,
+/// either explicitly via `RustBuffer::from_vec` or indirectly by calling
+/// one of the `RustBuffer::new*` constructors.
+///
+/// * `RustBuffer` structs do not implement `Drop`, since they are intended
+/// to be passed to foreign-language code outside of the control of Rust's
+/// ownership system. To avoid memory leaks they *must* passed back into
+/// Rust and either explicitly destroyed using `RustBuffer::destroy`, or
+/// converted back to a `Vec<u8>` using `RustBuffer::destroy_into_vec`
+/// (which will then be dropped via Rust's usual ownership-tracking system).
+///
+/// Foreign-language code should not construct `RustBuffer` structs other than
+/// by receiving them from a call into the Rust code, and should not modify them
+/// apart from the following safe operations:
+///
+/// * Writing bytes into the buffer pointed to by `data`, without writing
+/// beyond the indicated `capacity`.
+///
+/// * Adjusting the `len` property to indicate the amount of data written,
+/// while ensuring that 0 <= `len` <= `capacity`.
+///
+/// * As a special case, constructing a `RustBuffer` with zero capacity, zero
+/// length, and a null `data` pointer to indicate an empty buffer.
+///
+/// In particular, it is not safe for foreign-language code to construct a `RustBuffer`
+/// that points to its own allocated memory; use the `ForeignBytes` struct to
+/// pass a view of foreign-owned memory in to Rust code.
+///
+/// Implementation note: all the fields of this struct are private, so you can't
+/// manually construct instances that don't come from a `Vec<u8>`. If you've got
+/// a `RustBuffer` then it either came from a public constructor (all of which
+/// are safe) or it came from foreign-language code (which should have in turn
+/// received it by calling some Rust function, and should be respecting the
+/// invariants listed above).
+///
+/// This struct is based on `ByteBuffer` from the `ffi-support` crate, but modified
+/// to retain unallocated capacity rather than truncating to the occupied length.
+#[repr(C)]
+pub struct RustBuffer {
+ /// The allocated capacity of the underlying `Vec<u8>`.
+ /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA.
+ capacity: i32,
+ /// The occupied length of the underlying `Vec<u8>`.
+ /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA.
+ len: i32,
+ /// The pointer to the allocated buffer of the `Vec<u8>`.
+ data: *mut u8,
+}
+
+impl RustBuffer {
+ /// Creates an empty `RustBuffer`.
+ ///
+ /// The buffer will not allocate.
+ /// The resulting vector will not be automatically dropped; you must
+ /// arrange to call `destroy` or `destroy_into_vec` when finished with it.
+ pub fn new() -> Self {
+ Self::from_vec(Vec::new())
+ }
+
+ /// Creates a `RustBuffer` from its constituent fields.
+ ///
+ /// This is intended mainly as an internal convenience function and should not
+ /// be used outside of this module.
+ ///
+ /// # Safety
+ ///
+ /// You must ensure that the raw parts uphold the documented invariants of this class.
+ pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self {
+ Self {
+ capacity,
+ len,
+ data,
+ }
+ }
+
+ /// Get the current length of the buffer, as a `usize`.
+ ///
+ /// This is mostly a helper function to convert the `i32` length field
+ /// into a `usize`, which is what Rust code usually expects.
+ ///
+ /// # Panics
+ ///
+ /// Panics if called on an invalid struct obtained from foreign-language code,
+ /// in which the `len` field is negative.
+ pub fn len(&self) -> usize {
+ self.len
+ .try_into()
+ .expect("buffer length negative or overflowed")
+ }
+
+ /// Returns true if the length of the buffer is 0.
+ pub fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+
+ /// Creates a `RustBuffer` zero-filed to the requested size.
+ ///
+ /// The resulting vector will not be automatically dropped; you must
+ /// arrange to call `destroy` or `destroy_into_vec` when finished with it.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the requested size is too large to fit in an `i32`, and
+ /// hence would risk incompatibility with some foreign-language code.
+ pub fn new_with_size(size: usize) -> Self {
+ assert!(
+ size < i32::MAX as usize,
+ "RustBuffer requested size too large"
+ );
+ Self::from_vec(vec![0u8; size])
+ }
+
+ /// Consumes a `Vec<u8>` and returns its raw parts as a `RustBuffer`.
+ ///
+ /// The resulting vector will not be automatically dropped; you must
+ /// arrange to call `destroy` or `destroy_into_vec` when finished with it.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the vector's length or capacity are too large to fit in an `i32`,
+ /// and hence would risk incompatibility with some foreign-language code.
+ pub fn from_vec(v: Vec<u8>) -> Self {
+ let capacity = i32::try_from(v.capacity()).expect("buffer capacity cannot fit into a i32.");
+ let len = i32::try_from(v.len()).expect("buffer length cannot fit into a i32.");
+ let mut v = std::mem::ManuallyDrop::new(v);
+ unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) }
+ }
+
+ /// Converts this `RustBuffer` back into an owned `Vec<u8>`.
+ ///
+ /// This restores ownership of the underlying buffer to Rust, meaning it will
+ /// be dropped when the `Vec<u8>` is dropped. The `RustBuffer` *must* have been
+ /// previously obtained from a valid `Vec<u8>` owned by this Rust code.
+ ///
+ /// # Panics
+ ///
+ /// Panics if called on an invalid struct obtained from foreign-language code,
+ /// which does not respect the invairiants on `len` and `capacity`.
+ pub fn destroy_into_vec(self) -> Vec<u8> {
+ // Rust will never give us a null `data` pointer for a `Vec`, but
+ // foreign-language code can use it to cheaply pass an empty buffer.
+ if self.data.is_null() {
+ assert!(self.capacity == 0, "null RustBuffer had non-zero capacity");
+ assert!(self.len == 0, "null RustBuffer had non-zero length");
+ vec![]
+ } else {
+ let capacity: usize = self
+ .capacity
+ .try_into()
+ .expect("buffer capacity negative or overflowed");
+ let len: usize = self
+ .len
+ .try_into()
+ .expect("buffer length negative or overflowed");
+ assert!(len <= capacity, "RustBuffer length exceeds capacity");
+ unsafe { Vec::from_raw_parts(self.data, len, capacity) }
+ }
+ }
+
+ /// Reclaim memory stored in this `RustBuffer`.
+ ///
+ /// # Panics
+ ///
+ /// Panics if called on an invalid struct obtained from foreign-language code,
+ /// which does not respect the invairiants on `len` and `capacity`.
+ pub fn destroy(self) {
+ drop(self.destroy_into_vec());
+ }
+}
+
+impl Default for RustBuffer {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+// extern "C" functions for the RustBuffer functionality.
+//
+// These are used in two ways:
+// 1. Code that statically links to UniFFI can use these directly to handle RustBuffer
+// allocation/destruction. The plan is to use this for the Firefox desktop JS bindings.
+//
+// 2. The scaffolding code re-exports these functions, prefixed with the component name and UDL
+// hash This creates a separate set of functions for each UniFFIed component, which is needed
+// in the case where we create multiple dylib artifacts since each dylib will have its own
+// allocator.
+
+/// This helper allocates a new byte buffer owned by the Rust code, and returns it
+/// to the foreign-language code as a `RustBuffer` struct. Callers must eventually
+/// free the resulting buffer, either by explicitly calling [`uniffi_rustbuffer_free`] defined
+/// below, or by passing ownership of the buffer back into Rust code.
+#[no_mangle]
+pub extern "C" fn uniffi_rustbuffer_alloc(
+ size: i32,
+ call_status: &mut RustCallStatus,
+) -> RustBuffer {
+ call_with_output(call_status, || {
+ RustBuffer::new_with_size(size.max(0) as usize)
+ })
+}
+
+/// This helper copies bytes owned by the foreign-language code into a new byte buffer owned
+/// by the Rust code, and returns it as a `RustBuffer` struct. Callers must eventually
+/// free the resulting buffer, either by explicitly calling the destructor defined below,
+/// or by passing ownership of the buffer back into Rust code.
+///
+/// # Safety
+/// This function will dereference a provided pointer in order to copy bytes from it, so
+/// make sure the `ForeignBytes` struct contains a valid pointer and length.
+#[no_mangle]
+pub unsafe extern "C" fn uniffi_rustbuffer_from_bytes(
+ bytes: ForeignBytes,
+ call_status: &mut RustCallStatus,
+) -> RustBuffer {
+ call_with_output(call_status, || {
+ let bytes = bytes.as_slice();
+ RustBuffer::from_vec(bytes.to_vec())
+ })
+}
+
+/// Free a byte buffer that had previously been passed to the foreign language code.
+///
+/// # Safety
+/// The argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call
+/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or
+/// corrupting the allocator state.
+#[no_mangle]
+pub unsafe extern "C" fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
+ call_with_output(call_status, || RustBuffer::destroy(buf))
+}
+
+/// Reserve additional capacity in a byte buffer that had previously been passed to the
+/// foreign language code.
+///
+/// The first argument *must* be a uniquely-owned `RustBuffer` previously
+/// obtained from a call into the Rust code that returned a buffer. Its underlying data pointer
+/// will be reallocated if necessary and returned in a new `RustBuffer` struct.
+///
+/// The second argument must be the minimum number of *additional* bytes to reserve
+/// capacity for in the buffer; it is likely to reserve additional capacity in practice
+/// due to amortized growth strategy of Rust vectors.
+///
+/// # Safety
+/// The first argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call
+/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or
+/// corrupting the allocator state.
+#[no_mangle]
+pub unsafe extern "C" fn uniffi_rustbuffer_reserve(
+ buf: RustBuffer,
+ additional: i32,
+ call_status: &mut RustCallStatus,
+) -> RustBuffer {
+ call_with_output(call_status, || {
+ let additional: usize = additional
+ .try_into()
+ .expect("additional buffer length negative or overflowed");
+ let mut v = buf.destroy_into_vec();
+ v.reserve(additional);
+ RustBuffer::from_vec(v)
+ })
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ #[test]
+ fn test_rustbuffer_from_vec() {
+ let rbuf = RustBuffer::from_vec(vec![1u8, 2, 3]);
+ assert_eq!(rbuf.len(), 3);
+ assert_eq!(rbuf.destroy_into_vec(), vec![1u8, 2, 3]);
+ }
+
+ #[test]
+ fn test_rustbuffer_empty() {
+ let rbuf = RustBuffer::new();
+ assert_eq!(rbuf.len(), 0);
+ // Rust will never give us a null pointer, even for an empty buffer.
+ assert!(!rbuf.data.is_null());
+ assert_eq!(rbuf.destroy_into_vec(), Vec::<u8>::new());
+ }
+
+ #[test]
+ fn test_rustbuffer_new_with_size() {
+ let rbuf = RustBuffer::new_with_size(5);
+ assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8, 0, 0, 0, 0]);
+
+ let rbuf = RustBuffer::new_with_size(0);
+ assert!(!rbuf.data.is_null());
+ assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]);
+ }
+
+ #[test]
+ fn test_rustbuffer_null_means_empty() {
+ // This is how foreign-language code might cheaply indicate an empty buffer.
+ let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 0) };
+ assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_rustbuffer_null_must_have_no_capacity() {
+ // We guard against foreign-language code providing this kind of invalid struct.
+ let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 1) };
+ rbuf.destroy_into_vec();
+ }
+ #[test]
+ #[should_panic]
+ fn test_rustbuffer_null_must_have_zero_length() {
+ // We guard against foreign-language code providing this kind of invalid struct.
+ let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 12, 0) };
+ rbuf.destroy_into_vec();
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_rustbuffer_provided_capacity_must_be_non_negative() {
+ // We guard against foreign-language code providing this kind of invalid struct.
+ let mut v = vec![0u8, 1, 2];
+ let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, -7) };
+ rbuf.destroy_into_vec();
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_rustbuffer_provided_len_must_be_non_negative() {
+ // We guard against foreign-language code providing this kind of invalid struct.
+ let mut v = vec![0u8, 1, 2];
+ let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), -1, 3) };
+ rbuf.destroy_into_vec();
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_rustbuffer_provided_len_must_not_exceed_capacity() {
+ // We guard against foreign-language code providing this kind of invalid struct.
+ let mut v = vec![0u8, 1, 2];
+ let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, 2) };
+ rbuf.destroy_into_vec();
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/ffi/rustcalls.rs b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs
new file mode 100644
index 0000000000..a22f776d74
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs
@@ -0,0 +1,279 @@
+/* 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/. */
+
+//! # Low-level support for calling rust functions
+//!
+//! This module helps the scaffolding code make calls to rust functions and pass back the result to the FFI bindings code.
+//!
+//! It handles:
+//! - Catching panics
+//! - Adapting `Result<>` types into either a return value or an error
+
+use super::FfiDefault;
+use crate::{FfiConverter, RustBuffer, RustBufferFfiConverter};
+use anyhow::Result;
+use std::mem::MaybeUninit;
+use std::panic;
+
+/// Represents the success/error of a rust call
+///
+/// ## Usage
+///
+/// - The consumer code creates a `RustCallStatus` with an empty `RustBuffer` and `CALL_SUCCESS`
+/// (0) as the status code
+/// - A pointer to this object is passed to the rust FFI function. This is an
+/// "out parameter" which will be updated with any error that occurred during the function's
+/// execution.
+/// - After the call, if `code` is `CALL_ERROR` then `error_buf` will be updated to contain
+/// the serialized error object. The consumer is responsible for freeing `error_buf`.
+///
+/// ## Layout/fields
+///
+/// The layout of this struct is important since consumers on the other side of the FFI need to
+/// construct it. If this were a C struct, it would look like:
+///
+/// ```c,no_run
+/// struct RustCallStatus {
+/// int8_t code;
+/// RustBuffer error_buf;
+/// };
+/// ```
+///
+/// #### The `code` field.
+///
+/// - `CALL_SUCCESS` (0) for successful calls
+/// - `CALL_ERROR` (1) for calls that returned an `Err` value
+/// - `CALL_PANIC` (2) for calls that panicked
+///
+/// #### The `error_buf` field.
+///
+/// - For `CALL_ERROR` this is a `RustBuffer` with the serialized error. The consumer code is
+/// responsible for freeing this `RustBuffer`.
+#[repr(C)]
+pub struct RustCallStatus {
+ pub code: i8,
+ // code is signed because unsigned types are experimental in Kotlin
+ pub error_buf: MaybeUninit<RustBuffer>,
+ // error_buf is MaybeUninit to avoid dropping the value that the consumer code sends in:
+ // - Consumers should send in a zeroed out RustBuffer. In this case dropping is a no-op and
+ // avoiding the drop is a small optimization.
+ // - If consumers pass in invalid data, then we should avoid trying to drop it. In
+ // particular, we don't want to try to free any data the consumer has allocated.
+ //
+ // `MaybeUninit` requires unsafe code, since we are preventing rust from dropping the value.
+ // To use this safely we need to make sure that no code paths set this twice, since that will
+ // leak the first `RustBuffer`.
+}
+
+impl Default for RustCallStatus {
+ fn default() -> Self {
+ Self {
+ code: 0,
+ error_buf: MaybeUninit::uninit(),
+ }
+ }
+}
+
+#[allow(dead_code)]
+const CALL_SUCCESS: i8 = 0; // CALL_SUCCESS is set by the calling code
+const CALL_ERROR: i8 = 1;
+const CALL_PANIC: i8 = 2;
+
+// A trait for errors that can be thrown to the FFI code
+//
+// This gets implemented in uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs
+pub trait FfiError: RustBufferFfiConverter {}
+
+// Generalized rust call handling function
+fn make_call<F, R>(out_status: &mut RustCallStatus, callback: F) -> R
+where
+ F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>,
+ R: FfiDefault,
+{
+ let result = panic::catch_unwind(|| {
+ crate::panichook::ensure_setup();
+ callback()
+ });
+ match result {
+ // Happy path. Note: no need to update out_status in this case because the calling code
+ // initializes it to CALL_SUCCESS
+ Ok(Ok(v)) => v,
+ // Callback returned an Err.
+ Ok(Err(buf)) => {
+ out_status.code = CALL_ERROR;
+ unsafe {
+ // Unsafe because we're setting the `MaybeUninit` value, see above for safety
+ // invariants.
+ out_status.error_buf.as_mut_ptr().write(buf);
+ }
+ R::ffi_default()
+ }
+ // Callback panicked
+ Err(cause) => {
+ out_status.code = CALL_PANIC;
+ // Try to coerce the cause into a RustBuffer containing a String. Since this code can
+ // panic, we need to use a second catch_unwind().
+ let message_result = panic::catch_unwind(panic::AssertUnwindSafe(move || {
+ // The documentation suggests that it will *usually* be a str or String.
+ let message = if let Some(s) = cause.downcast_ref::<&'static str>() {
+ (*s).to_string()
+ } else if let Some(s) = cause.downcast_ref::<String>() {
+ s.clone()
+ } else {
+ "Unknown panic!".to_string()
+ };
+ log::error!("Caught a panic calling rust code: {:?}", message);
+ String::lower(message)
+ }));
+ if let Ok(buf) = message_result {
+ unsafe {
+ // Unsafe because we're setting the `MaybeUninit` value, see above for safety
+ // invariants.
+ out_status.error_buf.as_mut_ptr().write(buf);
+ }
+ }
+ // Ignore the error case. We've done all that we can at this point. In the bindings
+ // code, we handle this by checking if `error_buf` still has an empty `RustBuffer` and
+ // using a generic message.
+ R::ffi_default()
+ }
+ }
+}
+
+/// Wrap a rust function call and return the result directly
+///
+/// `callback` is responsible for making the call to the Rust function. It must convert any return
+/// value into a type that implements `IntoFfi` (typically handled with `FfiConverter::lower()`).
+///
+/// - If the function succeeds then the function's return value will be returned to the outer code
+/// - If the function panics:
+/// - `out_status.code` will be set to `CALL_PANIC`
+/// - the return value is undefined
+pub fn call_with_output<F, R>(out_status: &mut RustCallStatus, callback: F) -> R
+where
+ F: panic::UnwindSafe + FnOnce() -> R,
+ R: FfiDefault,
+{
+ make_call(out_status, || Ok(callback()))
+}
+
+/// Wrap a rust function call that returns a `Result<_, RustBuffer>`
+///
+/// `callback` is responsible for making the call to the Rust function.
+/// - `callback` must convert any return value into a type that implements `IntoFfi`
+/// - `callback` must convert any `Error` the into a `RustBuffer` to be returned over the FFI
+/// - (Both of these are typically handled with `FfiConverter::lower()`)
+///
+/// - If the function returns an `Ok` value it will be unwrapped and returned
+/// - If the function returns an `Err`:
+/// - `out_status.code` will be set to `CALL_ERROR`
+/// - `out_status.error_buf` will be set to a newly allocated `RustBuffer` containing the error. The calling
+/// code is responsible for freeing the `RustBuffer`
+/// - the return value is undefined
+/// - If the function panics:
+/// - `out_status.code` will be set to `CALL_PANIC`
+/// - the return value is undefined
+pub fn call_with_result<F, R>(out_status: &mut RustCallStatus, callback: F) -> R
+where
+ F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>,
+ R: FfiDefault,
+{
+ make_call(out_status, callback)
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::{FfiConverter, RustBufferFfiConverter};
+
+ fn function(a: u8) -> i8 {
+ match a {
+ 0 => 100,
+ x => panic!("Unexpected value: {x}"),
+ }
+ }
+
+ fn create_call_status() -> RustCallStatus {
+ RustCallStatus {
+ code: 0,
+ error_buf: MaybeUninit::new(RustBuffer::new()),
+ }
+ }
+
+ #[test]
+ fn test_call_with_output() {
+ let mut status = create_call_status();
+ let return_value = call_with_output(&mut status, || function(0));
+ assert_eq!(status.code, CALL_SUCCESS);
+ assert_eq!(return_value, 100);
+
+ call_with_output(&mut status, || function(1));
+ assert_eq!(status.code, CALL_PANIC);
+ unsafe {
+ assert_eq!(
+ String::try_lift(status.error_buf.assume_init()).unwrap(),
+ "Unexpected value: 1"
+ );
+ }
+ }
+
+ #[derive(Debug, PartialEq)]
+ struct TestError(String);
+
+ // Use RustBufferFfiConverter to simplify lifting TestError out of RustBuffer to check it
+ impl RustBufferFfiConverter for TestError {
+ type RustType = Self;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ <String as FfiConverter>::write(obj.0, buf);
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self> {
+ String::try_read(buf).map(TestError)
+ }
+ }
+
+ impl FfiError for TestError {}
+
+ fn function_with_result(a: u8) -> Result<i8, TestError> {
+ match a {
+ 0 => Ok(100),
+ 1 => Err(TestError("Error".to_owned())),
+ x => panic!("Unexpected value: {x}"),
+ }
+ }
+
+ #[test]
+ fn test_call_with_result() {
+ let mut status = create_call_status();
+ let return_value = call_with_result(&mut status, || {
+ function_with_result(0).map_err(TestError::lower)
+ });
+ assert_eq!(status.code, CALL_SUCCESS);
+ assert_eq!(return_value, 100);
+
+ call_with_result(&mut status, || {
+ function_with_result(1).map_err(TestError::lower)
+ });
+ assert_eq!(status.code, CALL_ERROR);
+ unsafe {
+ assert_eq!(
+ TestError::try_lift(status.error_buf.assume_init()).unwrap(),
+ TestError("Error".to_owned())
+ );
+ }
+
+ let mut status = create_call_status();
+ call_with_result(&mut status, || {
+ function_with_result(2).map_err(TestError::lower)
+ });
+ assert_eq!(status.code, CALL_PANIC);
+ unsafe {
+ assert_eq!(
+ String::try_lift(status.error_buf.assume_init()).unwrap(),
+ "Unexpected value: 2"
+ );
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/lib.rs b/third_party/rust/uniffi_core/src/lib.rs
new file mode 100644
index 0000000000..44271a7a43
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/lib.rs
@@ -0,0 +1,692 @@
+/* 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/. */
+
+//! # Runtime support code for uniffi
+//!
+//! This crate provides the small amount of runtime code that is required by the generated uniffi
+//! component scaffolding in order to transfer data back and forth across the C-style FFI layer,
+//! as well as some utilities for testing the generated bindings.
+//!
+//! The key concept here is the [`FfiConverter`] trait, which is responsible for converting between
+//! a Rust type and a low-level C-style type that can be passed across the FFI:
+//!
+//! * How to [represent](FfiConverter::FfiType) values of the Rust type in the low-level C-style type
+//! system of the FFI layer.
+//! * How to ["lower"](FfiConverter::lower) values of the Rust type into an appropriate low-level
+//! FFI value.
+//! * How to ["lift"](FfiConverter::try_lift) low-level FFI values back into values of the Rust
+//! type.
+//! * How to [write](FfiConverter::write) values of the Rust type into a buffer, for cases
+//! where they are part of a compound data structure that is serialized for transfer.
+//! * How to [read](FfiConverter::try_read) values of the Rust type from buffer, for cases
+//! where they are received as part of a compound data structure that was serialized for transfer.
+//!
+//! This logic encapsulates the Rust-side handling of data transfer. Each foreign-language binding
+//! must also implement a matching set of data-handling rules for each data type.
+//!
+//! In addition to the core `FfiConverter` trait, we provide a handful of struct definitions useful
+//! for passing core rust types over the FFI, such as [`RustBuffer`].
+
+#![warn(rust_2018_idioms, unused_qualifications)]
+
+use anyhow::bail;
+use bytes::buf::{Buf, BufMut};
+use paste::paste;
+use std::{
+ collections::HashMap,
+ convert::TryFrom,
+ time::{Duration, SystemTime},
+};
+
+// Make Result<> public to support external impls of FfiConverter
+pub use anyhow::Result;
+
+pub mod ffi;
+pub use ffi::*;
+
+// Re-export the libs that we use in the generated code,
+// so the consumer doesn't have to depend on them directly.
+pub mod deps {
+ pub use anyhow;
+ pub use bytes;
+ pub use log;
+ pub use static_assertions;
+}
+
+mod panichook;
+
+const PACKAGE_VERSION: &str = env!("CARGO_PKG_VERSION");
+
+// For the significance of this magic number 10 here, and the reason that
+// it can't be a named constant, see the `check_compatible_version` function.
+static_assertions::const_assert!(PACKAGE_VERSION.as_bytes().len() < 10);
+
+/// Check whether the uniffi runtime version is compatible a given uniffi_bindgen version.
+///
+/// The result of this check may be used to ensure that generated Rust scaffolding is
+/// using a compatible version of the uniffi runtime crate. It's a `const fn` so that it
+/// can be used to perform such a check at compile time.
+#[allow(clippy::len_zero)]
+pub const fn check_compatible_version(bindgen_version: &'static str) -> bool {
+ // While UniFFI is still under heavy development, we require that
+ // the runtime support crate be precisely the same version as the
+ // build-time bindgen crate.
+ //
+ // What we want to achieve here is checking two strings for equality.
+ // Unfortunately Rust doesn't yet support calling the `&str` equals method
+ // in a const context. We can hack around that by doing a byte-by-byte
+ // comparison of the underlying bytes.
+ let package_version = PACKAGE_VERSION.as_bytes();
+ let bindgen_version = bindgen_version.as_bytes();
+ // What we want to achieve here is a loop over the underlying bytes,
+ // something like:
+ // ```
+ // if package_version.len() != bindgen_version.len() {
+ // return false
+ // }
+ // for i in 0..package_version.len() {
+ // if package_version[i] != bindgen_version[i] {
+ // return false
+ // }
+ // }
+ // return true
+ // ```
+ // Unfortunately stable Rust doesn't allow `if` or `for` in const contexts,
+ // so code like the above would only work in nightly. We can hack around it by
+ // statically asserting that the string is shorter than a certain length
+ // (currently 10 bytes) and then manually unrolling that many iterations of the loop.
+ //
+ // Yes, I am aware that this is horrific, but the externally-visible
+ // behaviour is quite nice for consumers!
+ package_version.len() == bindgen_version.len()
+ && (package_version.len() == 0 || package_version[0] == bindgen_version[0])
+ && (package_version.len() <= 1 || package_version[1] == bindgen_version[1])
+ && (package_version.len() <= 2 || package_version[2] == bindgen_version[2])
+ && (package_version.len() <= 3 || package_version[3] == bindgen_version[3])
+ && (package_version.len() <= 4 || package_version[4] == bindgen_version[4])
+ && (package_version.len() <= 5 || package_version[5] == bindgen_version[5])
+ && (package_version.len() <= 6 || package_version[6] == bindgen_version[6])
+ && (package_version.len() <= 7 || package_version[7] == bindgen_version[7])
+ && (package_version.len() <= 8 || package_version[8] == bindgen_version[8])
+ && (package_version.len() <= 9 || package_version[9] == bindgen_version[9])
+ && package_version.len() < 10
+}
+
+/// Assert that the uniffi runtime version matches an expected value.
+///
+/// This is a helper hook for the generated Rust scaffolding, to produce a compile-time
+/// error if the version of `uniffi_bindgen` used to generate the scaffolding was
+/// incompatible with the version of `uniffi` being used at runtime.
+#[macro_export]
+macro_rules! assert_compatible_version {
+ ($v:expr $(,)?) => {
+ uniffi::deps::static_assertions::const_assert!(uniffi::check_compatible_version($v));
+ };
+}
+
+/// Trait defining how to transfer values via the FFI layer.
+///
+/// The `FfiConverter` trait defines how to pass values of a particular type back-and-forth over
+/// the uniffi generated FFI layer, both as standalone argument or return values, and as
+/// part of serialized compound data structures.
+///
+/// (This trait is like the `IntoFfi` trait from `ffi_support`, but local to this crate
+/// so that we can add some alternative implementations for different builtin types,
+/// and so that we can add support for receiving as well as returning).
+///
+/// ## Safety
+///
+/// This is an unsafe trait (implementing it requires `unsafe impl`) because we can't guarantee
+/// that it's safe to pass your type out to foreign-language code and back again. Buggy
+/// implementations of this trait might violate some assumptions made by the generated code,
+/// or might not match with the corresponding code in the generated foreign-language bindings.
+///
+/// In general, you should not need to implement this trait by hand, and should instead rely on
+/// implementations generated from your component UDL via the `uniffi-bindgen scaffolding` command.
+
+pub unsafe trait FfiConverter: Sized {
+ /// The type used in Rust code.
+ ///
+ /// For primitive / standard types, we implement `FfiConverter` on the type itself with `RustType=Self`.
+ /// For user-defined types we create a unit struct and implement it there. This sidesteps
+ /// Rust's orphan rules (ADR-0006).
+ type RustType;
+
+ /// The low-level type used for passing values of this type over the FFI.
+ ///
+ /// This must be a C-compatible type (e.g. a numeric primitive, a `#[repr(C)]` struct) into
+ /// which values of the target rust type can be converted.
+ ///
+ /// For complex data types, we currently recommend using `RustBuffer` and serializing
+ /// the data for transfer. In theory it could be possible to build a matching
+ /// `#[repr(C)]` struct for a complex data type and pass that instead, but explicit
+ /// serialization is simpler and safer as a starting point.
+ type FfiType;
+
+ /// Lower a rust value of the target type, into an FFI value of type Self::FfiType.
+ ///
+ /// This trait method is used for sending data from rust to the foreign language code,
+ /// by (hopefully cheaply!) converting it into something that can be passed over the FFI
+ /// and reconstructed on the other side.
+ ///
+ /// Note that this method takes an owned `Self::RustType`; this allows it to transfer ownership
+ /// in turn to the foreign language code, e.g. by boxing the value and passing a pointer.
+ fn lower(obj: Self::RustType) -> Self::FfiType;
+
+ /// Lift a rust value of the target type, from an FFI value of type Self::FfiType.
+ ///
+ /// This trait method is used for receiving data from the foreign language code in rust,
+ /// by (hopefully cheaply!) converting it from a low-level FFI value of type Self::FfiType
+ /// into a high-level rust value of the target type.
+ ///
+ /// Since we cannot statically guarantee that the foreign-language code will send valid
+ /// values of type Self::FfiType, this method is fallible.
+ fn try_lift(v: Self::FfiType) -> Result<Self::RustType>;
+
+ /// Write a rust value into a buffer, to send over the FFI in serialized form.
+ ///
+ /// This trait method can be used for sending data from rust to the foreign language code,
+ /// in cases where we're not able to use a special-purpose FFI type and must fall back to
+ /// sending serialized bytes.
+ ///
+ /// Note that this method takes an owned `Self::RustType` because it's transferring ownership
+ /// to the foreign language code via the RustBuffer.
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>);
+
+ /// Read a rust value from a buffer, received over the FFI in serialized form.
+ ///
+ /// This trait method can be used for receiving data from the foreign language code in rust,
+ /// in cases where we're not able to use a special-purpose FFI type and must fall back to
+ /// receiving serialized bytes.
+ ///
+ /// Since we cannot statically guarantee that the foreign-language code will send valid
+ /// serialized bytes for the target type, this method is fallible.
+ ///
+ /// Note the slightly unusual type here - we want a mutable reference to a slice of bytes,
+ /// because we want to be able to advance the start of the slice after reading an item
+ /// from it (but will not mutate the actual contents of the slice).
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType>;
+}
+
+/// Types that can be returned from exported functions.
+///
+/// Blanket-implemented by any FfiConverter with RustType = Self, but additionally implemented by
+/// the unit type.
+///
+/// This helper trait is currently only used to simplify the code generated by the export macro and
+/// is not part of the public interface of the library, hence its documentation is hidden.
+#[doc(hidden)]
+pub unsafe trait FfiReturn: Sized {
+ type FfiType;
+ fn lower(self) -> Self::FfiType;
+}
+
+unsafe impl<T> FfiReturn for T
+where
+ T: FfiConverter<RustType = T>,
+{
+ type FfiType = <Self as FfiConverter>::FfiType;
+
+ fn lower(self) -> Self::FfiType {
+ <Self as FfiConverter>::lower(self)
+ }
+}
+
+unsafe impl FfiReturn for () {
+ type FfiType = ();
+
+ fn lower(self) -> Self::FfiType {
+ self
+ }
+}
+
+/// A helper function to ensure we don't read past the end of a buffer.
+///
+/// Rust won't actually let us read past the end of a buffer, but the `Buf` trait does not support
+/// returning an explicit error in this case, and will instead panic. This is a look-before-you-leap
+/// helper function to instead return an explicit error, to help with debugging.
+pub fn check_remaining(buf: &[u8], num_bytes: usize) -> Result<()> {
+ if buf.remaining() < num_bytes {
+ bail!(
+ "not enough bytes remaining in buffer ({} < {num_bytes})",
+ buf.remaining(),
+ );
+ }
+ Ok(())
+}
+
+/// Blanket implementation of `FfiConverter` for numeric primitives.
+///
+/// Numeric primitives have a straightforward mapping into C-compatible numeric types,
+/// sice they are themselves a C-compatible numeric type!
+macro_rules! impl_via_ffi_for_num_primitive {
+ ($($T:ty,)+) => { impl_via_ffi_for_num_primitive!($($T),+); };
+ ($($T:ty),*) => {
+ $(
+ paste! {
+ unsafe impl FfiConverter for $T {
+ type RustType = Self;
+ type FfiType = Self;
+
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ obj
+ }
+
+ fn try_lift(v: Self::FfiType) -> Result<Self> {
+ Ok(v)
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ buf.[<put_ $T>](obj);
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self> {
+ check_remaining(buf, std::mem::size_of::<$T>())?;
+ Ok(buf.[<get_ $T>]())
+ }
+ }
+ }
+ )*
+ };
+}
+
+impl_via_ffi_for_num_primitive! {
+ i8, u8, i16, u16, i32, u32, i64, u64, f32, f64
+}
+
+/// Support for passing boolean values via the FFI.
+///
+/// Booleans are passed as an `i8` in order to avoid problems with handling
+/// C-compatible boolean values on JVM-based languages.
+unsafe impl FfiConverter for bool {
+ type RustType = Self;
+ type FfiType = i8;
+
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ i8::from(obj)
+ }
+
+ fn try_lift(v: Self::FfiType) -> Result<Self::RustType> {
+ Ok(match v {
+ 0 => false,
+ 1 => true,
+ _ => bail!("unexpected byte for Boolean"),
+ })
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ buf.put_i8(<bool as FfiConverter>::lower(obj));
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 1)?;
+ <bool as FfiConverter>::try_lift(buf.get_i8())
+ }
+}
+
+/// Support for passing Strings via the FFI.
+///
+/// Unlike many other implementations of `FfiConverter`, this passes a struct containing
+/// a raw pointer rather than copying the data from one side to the other. This is a
+/// safety hazard, but turns out to be pretty nice for useability. This struct
+/// *must* be a valid `RustBuffer` and it *must* contain valid utf-8 data (in other
+/// words, it *must* be a `Vec<u8>` suitable for use as an actual rust `String`).
+///
+/// When serialized in a buffer, strings are represented as a i32 byte length
+/// followed by utf8-encoded bytes. (It's a signed integer because unsigned types are
+/// currently experimental in Kotlin).
+unsafe impl FfiConverter for String {
+ type RustType = Self;
+ type FfiType = RustBuffer;
+
+ // This returns a struct with a raw pointer to the underlying bytes, so it's very
+ // important that it consume ownership of the String, which is relinquished to the
+ // foreign language code (and can be restored by it passing the pointer back).
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ RustBuffer::from_vec(obj.into_bytes())
+ }
+
+ // The argument here *must* be a uniquely-owned `RustBuffer` previously obtained
+ // from `lower` above, and hence must be the bytes of a valid rust string.
+ fn try_lift(v: Self::FfiType) -> Result<Self::RustType> {
+ let v = v.destroy_into_vec();
+ // This turns the buffer back into a `String` without copying the data
+ // and without re-checking it for validity of the utf8. If the `RustBuffer`
+ // came from a valid String then there's no point in re-checking the utf8,
+ // and if it didn't then bad things are probably going to happen regardless
+ // of whether we check for valid utf8 data or not.
+ Ok(unsafe { String::from_utf8_unchecked(v) })
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ // N.B. `len()` gives us the length in bytes, not in chars or graphemes.
+ // TODO: it would be nice not to panic here.
+ let len = i32::try_from(obj.len()).unwrap();
+ buf.put_i32(len); // We limit strings to u32::MAX bytes
+ buf.put(obj.as_bytes());
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 4)?;
+ let len = usize::try_from(buf.get_i32())?;
+ check_remaining(buf, len)?;
+ // N.B: In the general case `Buf::chunk()` may return partial data.
+ // But in the specific case of `<&[u8] as Buf>` it returns the full slice,
+ // so there is no risk of having less than `len` bytes available here.
+ let bytes = &buf.chunk()[..len];
+ let res = String::from_utf8(bytes.to_vec())?;
+ buf.advance(len);
+ Ok(res)
+ }
+}
+
+/// A helper trait to implement lowering/lifting using a `RustBuffer`
+///
+/// For complex types where it's too fiddly or too unsafe to convert them into a special-purpose
+/// C-compatible value, you can use this trait to implement `lower()` in terms of `write()` and
+/// `lift` in terms of `read()`.
+pub trait RustBufferFfiConverter: Sized {
+ type RustType;
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>);
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType>;
+}
+
+unsafe impl<T: RustBufferFfiConverter> FfiConverter for T {
+ type RustType = T::RustType;
+ type FfiType = RustBuffer;
+
+ fn lower(obj: Self::RustType) -> RustBuffer {
+ let mut buf = Vec::new();
+ <T as RustBufferFfiConverter>::write(obj, &mut buf);
+ RustBuffer::from_vec(buf)
+ }
+
+ fn try_lift(v: RustBuffer) -> Result<Self::RustType> {
+ let vec = v.destroy_into_vec();
+ let mut buf = vec.as_slice();
+ let value = T::try_read(&mut buf)?;
+ if buf.remaining() != 0 {
+ bail!("junk data left in buffer after lifting")
+ }
+ Ok(value)
+ }
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ T::write(obj, buf)
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ T::try_read(buf)
+ }
+}
+
+/// Support for passing timestamp values via the FFI.
+///
+/// Timestamps values are currently always passed by serializing to a buffer.
+///
+/// Timestamps are represented on the buffer by an i64 that indicates the
+/// direction and the magnitude in seconds of the offset from epoch, and a
+/// u32 that indicates the nanosecond portion of the offset magnitude. The
+/// nanosecond portion is expected to be between 0 and 999,999,999.
+///
+/// To build an epoch offset the absolute value of the seconds portion of the
+/// offset should be combined with the nanosecond portion. This is because
+/// the sign of the seconds portion represents the direction of the offset
+/// overall. The sign of the seconds portion can then be used to determine
+/// if the total offset should be added to or subtracted from the unix epoch.
+impl RustBufferFfiConverter for SystemTime {
+ type RustType = Self;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ let mut sign = 1;
+ let epoch_offset = obj
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap_or_else(|error| {
+ sign = -1;
+ error.duration()
+ });
+ // This panic should never happen as SystemTime typically stores seconds as i64
+ let seconds = sign
+ * i64::try_from(epoch_offset.as_secs())
+ .expect("SystemTime overflow, seconds greater than i64::MAX");
+
+ buf.put_i64(seconds);
+ buf.put_u32(epoch_offset.subsec_nanos());
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 12)?;
+ let seconds = buf.get_i64();
+ let nanos = buf.get_u32();
+ let epoch_offset = Duration::new(seconds.wrapping_abs() as u64, nanos);
+
+ if seconds >= 0 {
+ Ok(SystemTime::UNIX_EPOCH + epoch_offset)
+ } else {
+ Ok(SystemTime::UNIX_EPOCH - epoch_offset)
+ }
+ }
+}
+
+/// Support for passing duration values via the FFI.
+///
+/// Duration values are currently always passed by serializing to a buffer.
+///
+/// Durations are represented on the buffer by a u64 that indicates the
+/// magnitude in seconds, and a u32 that indicates the nanosecond portion
+/// of the magnitude. The nanosecond portion is expected to be between 0
+/// and 999,999,999.
+impl RustBufferFfiConverter for Duration {
+ type RustType = Self;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ buf.put_u64(obj.as_secs());
+ buf.put_u32(obj.subsec_nanos());
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 12)?;
+ Ok(Duration::new(buf.get_u64(), buf.get_u32()))
+ }
+}
+
+/// Support for passing optional values via the FFI.
+///
+/// Optional values are currently always passed by serializing to a buffer.
+/// We write either a zero byte for `None`, or a one byte followed by the containing
+/// item for `Some`.
+///
+/// In future we could do the same optimization as rust uses internally, where the
+/// `None` option is represented as a null pointer and the `Some` as a valid pointer,
+/// but that seems more fiddly and less safe in the short term, so it can wait.
+impl<T: FfiConverter> RustBufferFfiConverter for Option<T> {
+ type RustType = Option<T::RustType>;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ match obj {
+ None => buf.put_i8(0),
+ Some(v) => {
+ buf.put_i8(1);
+ <T as FfiConverter>::write(v, buf);
+ }
+ }
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 1)?;
+ Ok(match buf.get_i8() {
+ 0 => None,
+ 1 => Some(<T as FfiConverter>::try_read(buf)?),
+ _ => bail!("unexpected tag byte for Option"),
+ })
+ }
+}
+
+/// Support for passing vectors of values via the FFI.
+///
+/// Vectors are currently always passed by serializing to a buffer.
+/// We write a `i32` item count followed by each item in turn.
+/// (It's a signed type due to limits of the JVM).
+///
+/// Ideally we would pass `Vec<u8>` directly as a `RustBuffer` rather
+/// than serializing, and perhaps even pass other vector types using a
+/// similar struct. But that's for future work.
+impl<T: FfiConverter> RustBufferFfiConverter for Vec<T> {
+ type RustType = Vec<T::RustType>;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ // TODO: would be nice not to panic here :-/
+ let len = i32::try_from(obj.len()).unwrap();
+ buf.put_i32(len); // We limit arrays to i32::MAX items
+ for item in obj {
+ <T as FfiConverter>::write(item, buf);
+ }
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 4)?;
+ let len = usize::try_from(buf.get_i32())?;
+ let mut vec = Vec::with_capacity(len);
+ for _ in 0..len {
+ vec.push(<T as FfiConverter>::try_read(buf)?)
+ }
+ Ok(vec)
+ }
+}
+
+/// Support for associative arrays via the FFI.
+/// Note that because of webidl limitations,
+/// the key must always be of the String type.
+///
+/// HashMaps are currently always passed by serializing to a buffer.
+/// We write a `i32` entries count followed by each entry (string
+/// key followed by the value) in turn.
+/// (It's a signed type due to limits of the JVM).
+impl<K: FfiConverter, V: FfiConverter> RustBufferFfiConverter for HashMap<K, V>
+where
+ K::RustType: std::hash::Hash + Eq,
+{
+ type RustType = HashMap<K::RustType, V::RustType>;
+
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ // TODO: would be nice not to panic here :-/
+ let len = i32::try_from(obj.len()).unwrap();
+ buf.put_i32(len); // We limit HashMaps to i32::MAX entries
+ for (key, value) in obj {
+ <K as FfiConverter>::write(key, buf);
+ <V as FfiConverter>::write(value, buf);
+ }
+ }
+
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ check_remaining(buf, 4)?;
+ let len = usize::try_from(buf.get_i32())?;
+ let mut map = HashMap::with_capacity(len);
+ for _ in 0..len {
+ let key = <K as FfiConverter>::try_read(buf)?;
+ let value = <V as FfiConverter>::try_read(buf)?;
+ map.insert(key, value);
+ }
+ Ok(map)
+ }
+}
+
+/// Support for passing reference-counted shared objects via the FFI.
+///
+/// To avoid dealing with complex lifetime semantics over the FFI, any data passed
+/// by reference must be encapsulated in an `Arc`, and must be safe to share
+/// across threads.
+unsafe impl<T: Sync + Send> FfiConverter for std::sync::Arc<T> {
+ type RustType = Self;
+ // Don't use a pointer to <T> as that requires a `pub <T>`
+ type FfiType = *const std::os::raw::c_void;
+
+ /// When lowering, we have an owned `Arc<T>` and we transfer that ownership
+ /// to the foreign-language code, "leaking" it out of Rust's ownership system
+ /// as a raw pointer. This works safely because we have unique ownership of `self`.
+ /// The foreign-language code is responsible for freeing this by calling the
+ /// `ffi_object_free` FFI function provided by the corresponding UniFFI type.
+ ///
+ /// Safety: when freeing the resulting pointer, the foreign-language code must
+ /// call the destructor function specific to the type `T`. Calling the destructor
+ /// function for other types may lead to undefined behaviour.
+ fn lower(obj: Self::RustType) -> Self::FfiType {
+ std::sync::Arc::into_raw(obj) as Self::FfiType
+ }
+
+ /// When lifting, we receive a "borrow" of the `Arc<T>` that is owned by
+ /// the foreign-language code, and make a clone of it for our own use.
+ ///
+ /// Safety: the provided value must be a pointer previously obtained by calling
+ /// the `lower()` or `write()` method of this impl.
+ fn try_lift(v: Self::FfiType) -> Result<Self::RustType> {
+ let v = v as *const T;
+ // We musn't drop the `Arc<T>` that is owned by the foreign-language code.
+ let foreign_arc = std::mem::ManuallyDrop::new(unsafe { Self::from_raw(v) });
+ // Take a clone for our own use.
+ Ok(std::sync::Arc::clone(&*foreign_arc))
+ }
+
+ /// When writing as a field of a complex structure, make a clone and transfer ownership
+ /// of it to the foreign-language code by writing its pointer into the buffer.
+ /// The foreign-language code is responsible for freeing this by calling the
+ /// `ffi_object_free` FFI function provided by the corresponding UniFFI type.
+ ///
+ /// Safety: when freeing the resulting pointer, the foreign-language code must
+ /// call the destructor function specific to the type `T`. Calling the destructor
+ /// function for other types may lead to undefined behaviour.
+ fn write(obj: Self::RustType, buf: &mut Vec<u8>) {
+ static_assertions::const_assert!(std::mem::size_of::<*const std::ffi::c_void>() <= 8);
+ buf.put_u64(<Self as FfiConverter>::lower(obj) as u64);
+ }
+
+ /// When reading as a field of a complex structure, we receive a "borrow" of the `Arc<T>`
+ /// that is owned by the foreign-language code, and make a clone for our own use.
+ ///
+ /// Safety: the buffer must contain a pointer previously obtained by calling
+ /// the `lower()` or `write()` method of this impl.
+ fn try_read(buf: &mut &[u8]) -> Result<Self::RustType> {
+ static_assertions::const_assert!(std::mem::size_of::<*const std::ffi::c_void>() <= 8);
+ check_remaining(buf, 8)?;
+ Self::try_lift(buf.get_u64() as Self::FfiType)
+ }
+}
+
+pub fn lower_anyhow_error_or_panic<ErrConverter>(
+ err: anyhow::Error,
+ arg_name: &str,
+) -> ErrConverter::FfiType
+where
+ ErrConverter: FfiConverter,
+ ErrConverter::RustType: 'static + Sync + Send + std::fmt::Debug + std::fmt::Display,
+{
+ match err.downcast::<ErrConverter::RustType>() {
+ Ok(actual_error) => ErrConverter::lower(actual_error),
+ Err(ohno) => panic!("Failed to convert arg '{arg_name}': {ohno}"),
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::time::{Duration, SystemTime};
+
+ use super::FfiConverter as _;
+
+ #[test]
+ fn timestamp_roundtrip_post_epoch() {
+ let expected = SystemTime::UNIX_EPOCH + Duration::new(100, 100);
+ let result = SystemTime::try_lift(SystemTime::lower(expected)).expect("Failed to lift!");
+ assert_eq!(expected, result)
+ }
+
+ #[test]
+ fn timestamp_roundtrip_pre_epoch() {
+ let expected = SystemTime::UNIX_EPOCH - Duration::new(100, 100);
+ let result = SystemTime::try_lift(SystemTime::lower(expected)).expect("Failed to lift!");
+ assert_eq!(
+ expected, result,
+ "Expected results after lowering and lifting to be equal"
+ )
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/panichook.rs b/third_party/rust/uniffi_core/src/panichook.rs
new file mode 100644
index 0000000000..ef0ab86f1f
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/panichook.rs
@@ -0,0 +1,34 @@
+/// Initialize our panic handling hook to optionally log panics
+#[cfg(feature = "log_panics")]
+pub fn ensure_setup() {
+ use std::sync::Once;
+ static INIT_BACKTRACES: Once = Once::new();
+ INIT_BACKTRACES.call_once(move || {
+ #[cfg(all(feature = "log_backtraces", not(target_os = "android")))]
+ {
+ std::env::set_var("RUST_BACKTRACE", "1");
+ }
+ // Turn on a panic hook which logs both backtraces and the panic
+ // "Location" (file/line). We do both in case we've been stripped,
+ // ).
+ std::panic::set_hook(Box::new(move |panic_info| {
+ let (file, line) = if let Some(loc) = panic_info.location() {
+ (loc.file(), loc.line())
+ } else {
+ // Apparently this won't happen but rust has reserved the
+ // ability to start returning None from location in some cases
+ // in the future.
+ ("<unknown>", 0)
+ };
+ log::error!("### Rust `panic!` hit at file '{file}', line {line}");
+ #[cfg(all(feature = "log_backtraces", not(target_os = "android")))]
+ {
+ log::error!(" Complete stack trace:\n{:?}", backtrace::Backtrace::new());
+ }
+ }));
+ });
+}
+
+/// Initialize our panic handling hook to optionally log panics
+#[cfg(not(feature = "log_panics"))]
+pub fn ensure_setup() {}
diff --git a/third_party/rust/uniffi_macros/.cargo-checksum.json b/third_party/rust/uniffi_macros/.cargo-checksum.json
new file mode 100644
index 0000000000..8a84c40226
--- /dev/null
+++ b/third_party/rust/uniffi_macros/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"495a97879e9a66a87a410c742fa2ac36e2b85e5f16bee1cdde878db02090d7ee","src/enum_.rs":"0b2aa1bce2b8f1c476f2874e6bb9ae49b9610abd6963f3336db63794296494a8","src/error.rs":"b5fccfa3d4aa2d7454c5d27504a2d8d2e02e761843ea2da94b76ef8ffb0d0bf5","src/export.rs":"f95d31c9445142efe59617392ce9135acbefc4ac852654a482a2360c5c78ee75","src/export/metadata.rs":"af89a9942c7c0c4043a3cd57d1e6bd71cde19005e1f9f246efac761f47eff6be","src/export/metadata/convert.rs":"82c9602ddee0af2c93733bbe6cb77c50dccb0134c927b582f04f5bb9f8b5203c","src/export/metadata/function.rs":"e775f838025bee429b294f786579ac6598966b1b4f1f415dfb9012c70a0fea6c","src/export/metadata/impl_.rs":"1f55990f15293f2a3e30ebb0c132f68caa25a56113e0451c5307c9ed32cb8fb8","src/export/scaffolding.rs":"1cd5b8cf62195e35cd851ee9dd5aa787eaa8a1c293c1b3e899a1de8feb183b2d","src/lib.rs":"4357a13d0e9deac75239007431211188d7ef6270256f002d17d9e937ee3aba5a","src/object.rs":"955b596f344304013692042bdc1760bbb1192ec33950b0dd2932cb8de94ec297","src/record.rs":"f0c2adb794d5996ff8545c10469e63f24164509f0566e6c401a26c363c278003","src/test.rs":"069d91947685de083c9faa9042c328c235445b84bcf69f03117016d9c8464736","src/util.rs":"4834ece6808595ef5fbf735f9610dd1ef1631ea2e5919e6a1da9f290beb7091a"},"package":"fa03394de21e759e0022f1ea8d992d2e39290d735b9ed52b1f74b20a684f794e"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_macros/Cargo.toml b/third_party/rust/uniffi_macros/Cargo.toml
new file mode 100644
index 0000000000..dc5b9a9184
--- /dev/null
+++ b/third_party/rust/uniffi_macros/Cargo.toml
@@ -0,0 +1,69 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi_macros"
+version = "0.23.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+description = "a multi-language bindings generator for rust (convenience macros)"
+homepage = "https://mozilla.github.io/uniffi-rs"
+documentation = "https://mozilla.github.io/uniffi-rs"
+keywords = [
+ "ffi",
+ "bindgen",
+]
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/uniffi-rs"
+
+[lib]
+proc-macro = true
+
+[dependencies.bincode]
+version = "1.3"
+
+[dependencies.camino]
+version = "1.0.8"
+
+[dependencies.fs-err]
+version = "2.7.0"
+
+[dependencies.once_cell]
+version = "1.10.0"
+
+[dependencies.proc-macro2]
+version = "1.0"
+
+[dependencies.quote]
+version = "1.0"
+
+[dependencies.serde]
+version = "1.0.136"
+
+[dependencies.syn]
+version = "1.0"
+features = [
+ "full",
+ "visit-mut",
+]
+
+[dependencies.toml]
+version = "0.5.9"
+
+[dependencies.uniffi_build]
+version = "=0.23.0"
+
+[dependencies.uniffi_meta]
+version = "=0.23.0"
+
+[features]
+default = []
+nightly = []
diff --git a/third_party/rust/uniffi_macros/src/enum_.rs b/third_party/rust/uniffi_macros/src/enum_.rs
new file mode 100644
index 0000000000..c4e49beb8b
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/enum_.rs
@@ -0,0 +1,160 @@
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::quote;
+use syn::{punctuated::Punctuated, Data, DeriveInput, Field, Index, Token, Variant};
+use uniffi_meta::{EnumMetadata, FieldMetadata, VariantMetadata};
+
+use crate::{
+ export::metadata::convert::convert_type,
+ util::{assert_type_eq, create_metadata_static_var, try_read_field},
+};
+
+pub fn expand_enum(input: DeriveInput, module_path: Vec<String>) -> TokenStream {
+ let variants = match input.data {
+ Data::Enum(e) => Some(e.variants),
+ _ => None,
+ };
+
+ let ident = &input.ident;
+
+ let ffi_converter_impl = enum_ffi_converter_impl(variants.as_ref(), ident);
+
+ let meta_static_var = if let Some(variants) = variants {
+ match enum_metadata(ident, variants, module_path) {
+ Ok(metadata) => create_metadata_static_var(ident, metadata.into()),
+ Err(e) => e.into_compile_error(),
+ }
+ } else {
+ syn::Error::new(Span::call_site(), "This derive must only be used on enums")
+ .into_compile_error()
+ };
+
+ let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident });
+
+ quote! {
+ #ffi_converter_impl
+ #meta_static_var
+ #type_assertion
+ }
+}
+
+pub(crate) fn enum_ffi_converter_impl(
+ variants: Option<&Punctuated<Variant, Token![,]>>,
+ ident: &Ident,
+) -> TokenStream {
+ let (write_impl, try_read_impl) = match variants {
+ Some(variants) => {
+ let write_match_arms = variants.iter().enumerate().map(|(i, v)| {
+ let v_ident = &v.ident;
+ let fields = v.fields.iter().map(|f| &f.ident);
+ let idx = Index::from(i + 1);
+ let write_fields = v.fields.iter().map(write_field);
+
+ quote! {
+ Self::#v_ident { #(#fields),* } => {
+ ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
+ #(#write_fields)*
+ }
+ }
+ });
+ let write_impl = quote! {
+ match obj { #(#write_match_arms)* }
+ };
+
+ let try_read_match_arms = variants.iter().enumerate().map(|(i, v)| {
+ let idx = Index::from(i + 1);
+ let v_ident = &v.ident;
+ let try_read_fields = v.fields.iter().map(try_read_field);
+
+ quote! {
+ #idx => Self::#v_ident { #(#try_read_fields)* },
+ }
+ });
+ let error_format_string = format!("Invalid {ident} enum value: {{}}");
+ let try_read_impl = quote! {
+ ::uniffi::check_remaining(buf, 4)?;
+
+ Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) {
+ #(#try_read_match_arms)*
+ v => ::uniffi::deps::anyhow::bail!(#error_format_string, v),
+ })
+ };
+
+ (write_impl, try_read_impl)
+ }
+ None => {
+ let unimplemented = quote! { ::std::unimplemented!() };
+ (unimplemented.clone(), unimplemented)
+ }
+ };
+
+ quote! {
+ #[automatically_derived]
+ impl ::uniffi::RustBufferFfiConverter for #ident {
+ type RustType = Self;
+
+ fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) {
+ #write_impl
+ }
+
+ fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> {
+ #try_read_impl
+ }
+ }
+ }
+}
+
+fn enum_metadata(
+ ident: &Ident,
+ variants: Punctuated<Variant, Token![,]>,
+ module_path: Vec<String>,
+) -> syn::Result<EnumMetadata> {
+ let name = ident.to_string();
+ let variants = variants
+ .iter()
+ .map(variant_metadata)
+ .collect::<syn::Result<_>>()?;
+
+ Ok(EnumMetadata {
+ module_path,
+ name,
+ variants,
+ })
+}
+
+pub(crate) fn variant_metadata(v: &Variant) -> syn::Result<VariantMetadata> {
+ let name = v.ident.to_string();
+ let fields = v
+ .fields
+ .iter()
+ .map(|f| field_metadata(f, v))
+ .collect::<syn::Result<_>>()?;
+
+ Ok(VariantMetadata { name, fields })
+}
+
+fn field_metadata(f: &Field, v: &Variant) -> syn::Result<FieldMetadata> {
+ let name = f
+ .ident
+ .as_ref()
+ .ok_or_else(|| {
+ syn::Error::new_spanned(
+ v,
+ "UniFFI only supports enum variants with named fields (or no fields at all)",
+ )
+ })?
+ .to_string();
+
+ Ok(FieldMetadata {
+ name,
+ ty: convert_type(&f.ty)?,
+ })
+}
+
+fn write_field(f: &Field) -> TokenStream {
+ let ident = &f.ident;
+ let ty = &f.ty;
+
+ quote! {
+ <#ty as ::uniffi::FfiConverter>::write(#ident, buf);
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/error.rs b/third_party/rust/uniffi_macros/src/error.rs
new file mode 100644
index 0000000000..76c34cf78d
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/error.rs
@@ -0,0 +1,170 @@
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::quote;
+use syn::{
+ parse::{Parse, ParseStream},
+ punctuated::Punctuated,
+ Data, DeriveInput, Index, Token, Variant,
+};
+use uniffi_meta::{ErrorMetadata, VariantMetadata};
+
+use crate::{
+ enum_::{enum_ffi_converter_impl, variant_metadata},
+ util::{
+ assert_type_eq, chain, create_metadata_static_var, either_attribute_arg, AttributeSliceExt,
+ UniffiAttribute,
+ },
+};
+
+pub fn expand_error(input: DeriveInput, module_path: Vec<String>) -> TokenStream {
+ let variants = match input.data {
+ Data::Enum(e) => Ok(e.variants),
+ _ => Err(syn::Error::new(
+ Span::call_site(),
+ "This derive currently only supports enums",
+ )),
+ };
+
+ let ident = &input.ident;
+ let attr = input.attrs.parse_uniffi_attributes::<ErrorAttr>();
+ let ffi_converter_impl = match &attr {
+ Ok(a) if a.flat.is_some() => flat_error_ffi_converter_impl(variants.as_ref().ok(), ident),
+ _ => enum_ffi_converter_impl(variants.as_ref().ok(), ident),
+ };
+
+ let meta_static_var = match (&variants, &attr) {
+ (Ok(vs), Ok(a)) => Some(match error_metadata(ident, vs, module_path, a) {
+ Ok(metadata) => create_metadata_static_var(ident, metadata.into()),
+ Err(e) => e.into_compile_error(),
+ }),
+ _ => None,
+ };
+
+ let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident });
+ let variant_errors: TokenStream = match variants {
+ Ok(vs) => vs
+ .iter()
+ .flat_map(|variant| {
+ chain(
+ variant.attrs.attributes_not_allowed_here(),
+ variant
+ .fields
+ .iter()
+ .flat_map(|field| field.attrs.attributes_not_allowed_here()),
+ )
+ })
+ .map(syn::Error::into_compile_error)
+ .collect(),
+ Err(e) => e.into_compile_error(),
+ };
+ let attr_error = attr.err().map(syn::Error::into_compile_error);
+
+ quote! {
+ #ffi_converter_impl
+
+ #[automatically_derived]
+ impl ::uniffi::FfiError for #ident {}
+
+ #meta_static_var
+ #type_assertion
+ #variant_errors
+ #attr_error
+ }
+}
+
+pub(crate) fn flat_error_ffi_converter_impl(
+ variants: Option<&Punctuated<Variant, Token![,]>>,
+ ident: &Ident,
+) -> TokenStream {
+ let write_impl = match variants {
+ Some(variants) => {
+ let write_match_arms = variants.iter().enumerate().map(|(i, v)| {
+ let v_ident = &v.ident;
+ let idx = Index::from(i + 1);
+
+ quote! {
+ Self::#v_ident { .. } => {
+ ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
+ <::std::string::String as ::uniffi::FfiConverter>::write(error_msg, buf);
+ }
+ }
+ });
+ let write_impl = quote! {
+ let error_msg = ::std::string::ToString::to_string(&obj);
+ match obj { #(#write_match_arms)* }
+ };
+
+ write_impl
+ }
+ None => quote! { ::std::unimplemented!() },
+ };
+
+ quote! {
+ #[automatically_derived]
+ impl ::uniffi::RustBufferFfiConverter for #ident {
+ type RustType = Self;
+
+ fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) {
+ #write_impl
+ }
+
+ fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> {
+ ::std::panic!("try_read not supported for flat errors");
+ }
+ }
+ }
+}
+
+fn error_metadata(
+ ident: &Ident,
+ variants: &Punctuated<Variant, Token![,]>,
+ module_path: Vec<String>,
+ attr: &ErrorAttr,
+) -> syn::Result<ErrorMetadata> {
+ let name = ident.to_string();
+ let flat = attr.flat.is_some();
+ let variants = if flat {
+ variants
+ .iter()
+ .map(|v| VariantMetadata {
+ name: v.ident.to_string(),
+ fields: vec![],
+ })
+ .collect()
+ } else {
+ variants
+ .iter()
+ .map(variant_metadata)
+ .collect::<syn::Result<_>>()?
+ };
+
+ Ok(ErrorMetadata {
+ module_path,
+ name,
+ variants,
+ flat,
+ })
+}
+
+mod kw {
+ syn::custom_keyword!(flat_error);
+}
+
+#[derive(Default)]
+struct ErrorAttr {
+ flat: Option<kw::flat_error>,
+}
+
+impl Parse for ErrorAttr {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let flat = input.parse()?;
+ Ok(ErrorAttr { flat })
+ }
+}
+
+impl UniffiAttribute for ErrorAttr {
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ Ok(Self {
+ flat: either_attribute_arg(self.flat, other.flat)?,
+ })
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export.rs b/third_party/rust/uniffi_macros/src/export.rs
new file mode 100644
index 0000000000..22d469eec6
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export.rs
@@ -0,0 +1,210 @@
+/* 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 std::collections::BTreeMap;
+
+use proc_macro2::{Ident, TokenStream};
+use quote::{format_ident, quote, quote_spanned};
+use uniffi_meta::{checksum, FnMetadata, MethodMetadata, Type};
+
+pub(crate) mod metadata;
+mod scaffolding;
+
+pub use self::metadata::gen_metadata;
+use self::{
+ metadata::convert::{convert_type, try_split_result},
+ scaffolding::{gen_fn_scaffolding, gen_method_scaffolding},
+};
+use crate::util::{assert_type_eq, create_metadata_static_var};
+
+// TODO(jplatte): Ensure no generics, no async, …
+// TODO(jplatte): Aggregate errors instead of short-circuiting, wherever possible
+
+pub enum ExportItem {
+ Function {
+ sig: Signature,
+ metadata: FnMetadata,
+ },
+ Impl {
+ self_ident: Ident,
+ methods: Vec<syn::Result<Method>>,
+ },
+}
+
+pub struct Method {
+ sig: Signature,
+ metadata: MethodMetadata,
+}
+
+pub struct Signature {
+ ident: Ident,
+ inputs: Vec<syn::FnArg>,
+ output: Option<FunctionReturn>,
+}
+
+impl Signature {
+ fn new(item: syn::Signature) -> syn::Result<Self> {
+ let output = match item.output {
+ syn::ReturnType::Default => None,
+ syn::ReturnType::Type(_, ty) => Some(FunctionReturn::new(ty)?),
+ };
+
+ Ok(Self {
+ ident: item.ident,
+ inputs: item.inputs.into_iter().collect(),
+ output,
+ })
+ }
+}
+
+pub struct FunctionReturn {
+ ty: Box<syn::Type>,
+ throws: Option<Ident>,
+}
+
+impl FunctionReturn {
+ fn new(ty: Box<syn::Type>) -> syn::Result<Self> {
+ Ok(match try_split_result(&ty)? {
+ Some((ok_type, throws)) => FunctionReturn {
+ ty: Box::new(ok_type.to_owned()),
+ throws: Some(throws),
+ },
+ None => FunctionReturn { ty, throws: None },
+ })
+ }
+}
+
+pub fn expand_export(metadata: ExportItem, mod_path: &[String]) -> TokenStream {
+ match metadata {
+ ExportItem::Function { sig, metadata } => {
+ let checksum = checksum(&metadata);
+ let scaffolding = gen_fn_scaffolding(&sig, mod_path, checksum);
+ let type_assertions = fn_type_assertions(&sig);
+ let meta_static_var = create_metadata_static_var(&sig.ident, metadata.into());
+
+ quote! {
+ #scaffolding
+ #type_assertions
+ #meta_static_var
+ }
+ }
+ ExportItem::Impl {
+ methods,
+ self_ident,
+ } => {
+ let method_tokens: TokenStream = methods
+ .into_iter()
+ .map(|res| {
+ res.map_or_else(
+ syn::Error::into_compile_error,
+ |Method { sig, metadata }| {
+ let checksum = checksum(&metadata);
+ let scaffolding =
+ gen_method_scaffolding(&sig, mod_path, checksum, &self_ident);
+ let type_assertions = fn_type_assertions(&sig);
+ let meta_static_var = create_metadata_static_var(
+ &format_ident!("{}_{}", metadata.self_name, sig.ident),
+ metadata.into(),
+ );
+
+ quote! {
+ #scaffolding
+ #type_assertions
+ #meta_static_var
+ }
+ },
+ )
+ })
+ .collect();
+
+ quote_spanned! {self_ident.span()=>
+ ::uniffi::deps::static_assertions::assert_type_eq_all!(
+ #self_ident,
+ crate::uniffi_types::#self_ident
+ );
+
+ #method_tokens
+ }
+ }
+ }
+}
+
+fn fn_type_assertions(sig: &Signature) -> TokenStream {
+ // Convert uniffi_meta::Type back to a Rust type
+ fn convert_type_back(ty: &Type) -> TokenStream {
+ match &ty {
+ Type::U8 => quote! { ::std::primitive::u8 },
+ Type::U16 => quote! { ::std::primitive::u16 },
+ Type::U32 => quote! { ::std::primitive::u32 },
+ Type::U64 => quote! { ::std::primitive::u64 },
+ Type::I8 => quote! { ::std::primitive::i8 },
+ Type::I16 => quote! { ::std::primitive::i16 },
+ Type::I32 => quote! { ::std::primitive::i32 },
+ Type::I64 => quote! { ::std::primitive::i64 },
+ Type::F32 => quote! { ::std::primitive::f32 },
+ Type::F64 => quote! { ::std::primitive::f64 },
+ Type::Bool => quote! { ::std::primitive::bool },
+ Type::String => quote! { ::std::string::String },
+ Type::Option { inner_type } => {
+ let inner = convert_type_back(inner_type);
+ quote! { ::std::option::Option<#inner> }
+ }
+ Type::Vec { inner_type } => {
+ let inner = convert_type_back(inner_type);
+ quote! { ::std::vec::Vec<#inner> }
+ }
+ Type::HashMap {
+ key_type,
+ value_type,
+ } => {
+ let key = convert_type_back(key_type);
+ let value = convert_type_back(value_type);
+ quote! { ::std::collections::HashMap<#key, #value> }
+ }
+ Type::ArcObject { object_name } => {
+ let object_ident = format_ident!("{object_name}");
+ quote! { ::std::sync::Arc<crate::uniffi_types::#object_ident> }
+ }
+ Type::Unresolved { name } => {
+ let ident = format_ident!("{name}");
+ quote! { crate::uniffi_types::#ident }
+ }
+ }
+ }
+
+ let input_types = sig.inputs.iter().filter_map(|input| match input {
+ syn::FnArg::Receiver(_) => None,
+ syn::FnArg::Typed(pat_ty) => match &*pat_ty.pat {
+ // Self type is asserted separately for impl blocks
+ syn::Pat::Ident(i) if i.ident == "self" => None,
+ _ => Some(&pat_ty.ty),
+ },
+ });
+ let output_type = sig.output.as_ref().map(|s| &s.ty);
+
+ let type_assertions: BTreeMap<_, _> = input_types
+ .chain(output_type)
+ .filter_map(|ty| {
+ convert_type(ty).ok().map(|meta_ty| {
+ let expected_ty = convert_type_back(&meta_ty);
+ let assert = assert_type_eq(ty, expected_ty);
+ (meta_ty, assert)
+ })
+ })
+ .collect();
+ let input_output_type_assertions: TokenStream = type_assertions.into_values().collect();
+
+ let throws_type_assertion = sig.output.as_ref().and_then(|s| {
+ let ident = s.throws.as_ref()?;
+ Some(assert_type_eq(
+ ident,
+ quote! { crate::uniffi_types::#ident },
+ ))
+ });
+
+ quote! {
+ #input_output_type_assertions
+ #throws_type_assertion
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export/metadata.rs b/third_party/rust/uniffi_macros/src/export/metadata.rs
new file mode 100644
index 0000000000..2d0b284333
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata.rs
@@ -0,0 +1,26 @@
+/* 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 proc_macro2::Span;
+
+use super::ExportItem;
+
+pub(crate) mod convert;
+mod function;
+mod impl_;
+
+use self::{function::gen_fn_metadata, impl_::gen_impl_metadata};
+
+pub fn gen_metadata(item: syn::Item, mod_path: &[String]) -> syn::Result<ExportItem> {
+ match item {
+ syn::Item::Fn(item) => gen_fn_metadata(item.sig, mod_path),
+ syn::Item::Impl(item) => gen_impl_metadata(item, mod_path),
+ // FIXME: Support const / static?
+ _ => Err(syn::Error::new(
+ Span::call_site(),
+ "unsupported item: only functions and impl \
+ blocks may be annotated with this attribute",
+ )),
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export/metadata/convert.rs b/third_party/rust/uniffi_macros/src/export/metadata/convert.rs
new file mode 100644
index 0000000000..c19ae579c2
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata/convert.rs
@@ -0,0 +1,222 @@
+/* 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 proc_macro2::Ident;
+use quote::ToTokens;
+use uniffi_meta::{FnParamMetadata, Type};
+
+pub(super) fn fn_param_metadata(params: &[syn::FnArg]) -> syn::Result<Vec<FnParamMetadata>> {
+ params
+ .iter()
+ .filter_map(|a| {
+ let _is_method = false;
+ let (name, ty) = match a {
+ // methods currently have an implicit self parameter in uniffi_meta
+ syn::FnArg::Receiver(_) => return None,
+ syn::FnArg::Typed(pat_ty) => {
+ let name = match &*pat_ty.pat {
+ syn::Pat::Ident(pat_id) => pat_id.ident.to_string(),
+ _ => unimplemented!(),
+ };
+
+ // methods currently have an implicit self parameter in uniffi_meta
+ if name == "self" {
+ return None;
+ }
+
+ (name, &pat_ty.ty)
+ }
+ };
+
+ Some(convert_type(ty).map(|ty| FnParamMetadata { name, ty }))
+ })
+ .collect()
+}
+
+pub(crate) fn convert_return_type(ty: &syn::Type) -> syn::Result<Option<Type>> {
+ match ty {
+ syn::Type::Tuple(tup) if tup.elems.is_empty() => Ok(None),
+ _ => convert_type(ty).map(Some),
+ }
+}
+
+pub(crate) fn convert_type(ty: &syn::Type) -> syn::Result<Type> {
+ let type_path = type_as_type_path(ty)?;
+
+ if type_path.qself.is_some() {
+ return Err(syn::Error::new_spanned(
+ type_path,
+ "qualified self types are not currently supported by uniffi::export",
+ ));
+ }
+
+ if type_path.path.segments.len() > 1 {
+ return Err(syn::Error::new_spanned(
+ type_path,
+ "qualified paths in types are not currently supported by uniffi::export",
+ ));
+ }
+
+ match &type_path.path.segments.first() {
+ Some(seg) => match &seg.arguments {
+ syn::PathArguments::None => Ok(convert_bare_type_name(&seg.ident)),
+ syn::PathArguments::AngleBracketed(a) => convert_generic_type(&seg.ident, a),
+ syn::PathArguments::Parenthesized(_) => Err(type_not_supported(type_path)),
+ },
+ None => Err(syn::Error::new_spanned(
+ type_path,
+ "unreachable: TypePath must have non-empty segments",
+ )),
+ }
+}
+
+fn convert_generic_type(
+ ident: &Ident,
+ a: &syn::AngleBracketedGenericArguments,
+) -> syn::Result<Type> {
+ let mut it = a.args.iter();
+ match it.next() {
+ // `u8<>` is a valid way to write `u8` in the type namespace, so why not?
+ None => Ok(convert_bare_type_name(ident)),
+ Some(arg1) => match it.next() {
+ None => convert_generic_type1(ident, arg1),
+ Some(arg2) => match it.next() {
+ None => convert_generic_type2(ident, arg1, arg2),
+ Some(_) => Err(syn::Error::new_spanned(
+ ident,
+ "types with more than two generics are not currently
+ supported by uniffi::export",
+ )),
+ },
+ },
+ }
+}
+
+fn convert_bare_type_name(ident: &Ident) -> Type {
+ let name = ident.to_string();
+ match name.as_str() {
+ "u8" => Type::U8,
+ "u16" => Type::U16,
+ "u32" => Type::U32,
+ "u64" => Type::U64,
+ "i8" => Type::I8,
+ "i16" => Type::I16,
+ "i32" => Type::I32,
+ "i64" => Type::I64,
+ "f32" => Type::F32,
+ "f64" => Type::F64,
+ "bool" => Type::Bool,
+ "String" => Type::String,
+ _ => Type::Unresolved { name },
+ }
+}
+
+fn convert_generic_type1(ident: &Ident, arg: &syn::GenericArgument) -> syn::Result<Type> {
+ let arg = arg_as_type(arg)?;
+ match ident.to_string().as_str() {
+ "Arc" => Ok(Type::ArcObject {
+ object_name: type_as_type_name(arg)?.to_string(),
+ }),
+ "Option" => Ok(Type::Option {
+ inner_type: convert_type(arg)?.into(),
+ }),
+ "Vec" => Ok(Type::Vec {
+ inner_type: convert_type(arg)?.into(),
+ }),
+ _ => Err(type_not_supported(ident)),
+ }
+}
+
+fn convert_generic_type2(
+ ident: &Ident,
+ arg1: &syn::GenericArgument,
+ arg2: &syn::GenericArgument,
+) -> syn::Result<Type> {
+ let arg1 = arg_as_type(arg1)?;
+ let arg2 = arg_as_type(arg2)?;
+
+ match ident.to_string().as_str() {
+ "HashMap" => Ok(Type::HashMap {
+ key_type: convert_type(arg1)?.into(),
+ value_type: convert_type(arg2)?.into(),
+ }),
+ _ => Err(type_not_supported(ident)),
+ }
+}
+
+fn type_as_type_name(arg: &syn::Type) -> syn::Result<&Ident> {
+ type_as_type_path(arg)?
+ .path
+ .get_ident()
+ .ok_or_else(|| type_not_supported(arg))
+}
+
+pub(super) fn type_as_type_path(ty: &syn::Type) -> syn::Result<&syn::TypePath> {
+ match ty {
+ syn::Type::Group(g) => type_as_type_path(&g.elem),
+ syn::Type::Paren(p) => type_as_type_path(&p.elem),
+ syn::Type::Path(p) => Ok(p),
+ _ => Err(type_not_supported(ty)),
+ }
+}
+
+fn arg_as_type(arg: &syn::GenericArgument) -> syn::Result<&syn::Type> {
+ match arg {
+ syn::GenericArgument::Type(t) => Ok(t),
+ _ => Err(syn::Error::new_spanned(
+ arg,
+ "non-type generic parameters are not currently supported by uniffi::export",
+ )),
+ }
+}
+
+fn type_not_supported(ty: &impl ToTokens) -> syn::Error {
+ syn::Error::new_spanned(
+ ty,
+ "this type is not currently supported by uniffi::export in this position",
+ )
+}
+
+pub(crate) fn try_split_result(ty: &syn::Type) -> syn::Result<Option<(&syn::Type, Ident)>> {
+ let type_path = type_as_type_path(ty)?;
+
+ if type_path.qself.is_some() {
+ return Err(syn::Error::new_spanned(
+ type_path,
+ "qualified self types are not currently supported by uniffi::export",
+ ));
+ }
+
+ if type_path.path.segments.len() > 1 {
+ return Err(syn::Error::new_spanned(
+ type_path,
+ "qualified paths in types are not currently supported by uniffi::export",
+ ));
+ }
+
+ let (ident, a) = match &type_path.path.segments.first() {
+ Some(seg) => match &seg.arguments {
+ syn::PathArguments::AngleBracketed(a) => (&seg.ident, a),
+ syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => return Ok(None),
+ },
+ None => return Ok(None),
+ };
+
+ let mut it = a.args.iter();
+ if let Some(arg1) = it.next() {
+ if let Some(arg2) = it.next() {
+ if it.next().is_none() {
+ let arg1 = arg_as_type(arg1)?;
+ let arg2 = arg_as_type(arg2)?;
+
+ if let "Result" = ident.to_string().as_str() {
+ let throws = type_as_type_name(arg2)?.to_owned();
+ return Ok(Some((arg1, throws)));
+ }
+ }
+ }
+ }
+
+ Ok(None)
+}
diff --git a/third_party/rust/uniffi_macros/src/export/metadata/function.rs b/third_party/rust/uniffi_macros/src/export/metadata/function.rs
new file mode 100644
index 0000000000..a2cf6b7aa1
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata/function.rs
@@ -0,0 +1,32 @@
+/* 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 uniffi_meta::FnMetadata;
+
+use super::convert::{convert_return_type, fn_param_metadata};
+use crate::export::{ExportItem, Signature};
+
+pub(super) fn gen_fn_metadata(sig: syn::Signature, mod_path: &[String]) -> syn::Result<ExportItem> {
+ let sig = Signature::new(sig)?;
+ let metadata = fn_metadata(&sig, mod_path)?;
+ Ok(ExportItem::Function { sig, metadata })
+}
+
+fn fn_metadata(sig: &Signature, mod_path: &[String]) -> syn::Result<FnMetadata> {
+ let (return_type, throws) = match &sig.output {
+ Some(ret) => (
+ convert_return_type(&ret.ty)?,
+ ret.throws.as_ref().map(ToString::to_string),
+ ),
+ None => (None, None),
+ };
+
+ Ok(FnMetadata {
+ module_path: mod_path.to_owned(),
+ name: sig.ident.to_string(),
+ inputs: fn_param_metadata(&sig.inputs)?,
+ return_type,
+ throws,
+ })
+}
diff --git a/third_party/rust/uniffi_macros/src/export/metadata/impl_.rs b/third_party/rust/uniffi_macros/src/export/metadata/impl_.rs
new file mode 100644
index 0000000000..302f0bfa13
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata/impl_.rs
@@ -0,0 +1,93 @@
+/* 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 uniffi_meta::MethodMetadata;
+
+use super::convert::{convert_return_type, fn_param_metadata, type_as_type_path};
+use crate::export::{ExportItem, Method, Signature};
+
+pub(super) fn gen_impl_metadata(
+ item: syn::ItemImpl,
+ mod_path: &[String],
+) -> syn::Result<ExportItem> {
+ if !item.generics.params.is_empty() || item.generics.where_clause.is_some() {
+ return Err(syn::Error::new_spanned(
+ &item.generics,
+ "generic impls are not currently supported by uniffi::export",
+ ));
+ }
+
+ let type_path = type_as_type_path(&item.self_ty)?;
+
+ if type_path.qself.is_some() {
+ return Err(syn::Error::new_spanned(
+ type_path,
+ "qualified self types are not currently supported by uniffi::export",
+ ));
+ }
+
+ let self_ident = match type_path.path.get_ident() {
+ Some(id) => id,
+ None => {
+ return Err(syn::Error::new_spanned(
+ type_path,
+ "qualified paths in self-types are not currently supported by uniffi::export",
+ ));
+ }
+ };
+
+ let methods = item
+ .items
+ .into_iter()
+ .map(|it| gen_method_metadata(it, &self_ident.to_string(), mod_path))
+ .collect();
+
+ Ok(ExportItem::Impl {
+ methods,
+ self_ident: self_ident.to_owned(),
+ })
+}
+
+fn gen_method_metadata(
+ it: syn::ImplItem,
+ self_name: &str,
+ mod_path: &[String],
+) -> syn::Result<Method> {
+ let sig = match it {
+ syn::ImplItem::Method(m) => Signature::new(m.sig)?,
+ _ => {
+ return Err(syn::Error::new_spanned(
+ it,
+ "only methods are supported in impl blocks annotated with uniffi::export",
+ ));
+ }
+ };
+
+ let metadata = method_metadata(self_name, &sig, mod_path)?;
+
+ Ok(Method { sig, metadata })
+}
+
+fn method_metadata(
+ self_name: &str,
+ sig: &Signature,
+ mod_path: &[String],
+) -> syn::Result<MethodMetadata> {
+ let (return_type, throws) = match &sig.output {
+ Some(ret) => (
+ convert_return_type(&ret.ty)?,
+ ret.throws.as_ref().map(ToString::to_string),
+ ),
+ None => (None, None),
+ };
+
+ Ok(MethodMetadata {
+ module_path: mod_path.to_owned(),
+ self_name: self_name.to_owned(),
+ name: sig.ident.to_string(),
+ inputs: fn_param_metadata(&sig.inputs)?,
+ return_type,
+ throws,
+ })
+}
diff --git a/third_party/rust/uniffi_macros/src/export/scaffolding.rs b/third_party/rust/uniffi_macros/src/export/scaffolding.rs
new file mode 100644
index 0000000000..f66e8bf844
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/scaffolding.rs
@@ -0,0 +1,199 @@
+/* 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 proc_macro2::{Ident, Span, TokenStream};
+use quote::{format_ident, quote, ToTokens};
+use syn::{parse_quote, FnArg, Pat};
+
+use super::{FunctionReturn, Signature};
+
+pub(super) fn gen_fn_scaffolding(
+ sig: &Signature,
+ mod_path: &[String],
+ checksum: u16,
+) -> TokenStream {
+ let name = &sig.ident;
+ let name_s = name.to_string();
+
+ let ffi_ident = Ident::new(
+ &uniffi_meta::fn_ffi_symbol_name(mod_path, &name_s, checksum),
+ Span::call_site(),
+ );
+
+ const ERROR_MSG: &str =
+ "uniffi::export must be used on the impl block, not its containing fn's";
+ let (params, args): (Vec<_>, Vec<_>) = collect_params(&sig.inputs, ERROR_MSG).unzip();
+
+ let fn_call = quote! {
+ #name(#(#args),*)
+ };
+
+ gen_ffi_function(sig, ffi_ident, &params, fn_call)
+}
+
+pub(super) fn gen_method_scaffolding(
+ sig: &Signature,
+ mod_path: &[String],
+ checksum: u16,
+ self_ident: &Ident,
+) -> TokenStream {
+ let name = &sig.ident;
+ let name_s = name.to_string();
+
+ let ffi_name = format!("impl_{self_ident}_{name_s}");
+ let ffi_ident = Ident::new(
+ &uniffi_meta::fn_ffi_symbol_name(mod_path, &ffi_name, checksum),
+ Span::call_site(),
+ );
+
+ let mut params_args = (Vec::new(), Vec::new());
+
+ const RECEIVER_ERROR: &str = "unreachable: only first parameter can be method receiver";
+ let mut assoc_fn_error = None;
+ let fn_call_prefix = match sig.inputs.first() {
+ Some(arg) if is_receiver(arg) => {
+ let ffi_converter = quote! {
+ <::std::sync::Arc<#self_ident> as ::uniffi::FfiConverter>
+ };
+
+ params_args.0.push(quote! { this: #ffi_converter::FfiType });
+
+ let remaining_args = sig.inputs.iter().skip(1);
+ params_args.extend(collect_params(remaining_args, RECEIVER_ERROR));
+
+ quote! {
+ #ffi_converter::try_lift(this).unwrap_or_else(|err| {
+ ::std::panic!("Failed to convert arg 'self': {}", err)
+ }).
+ }
+ }
+ _ => {
+ assoc_fn_error = Some(
+ syn::Error::new_spanned(
+ &sig.ident,
+ "associated functions are not currently supported",
+ )
+ .into_compile_error(),
+ );
+ params_args.extend(collect_params(&sig.inputs, RECEIVER_ERROR));
+ quote! { #self_ident:: }
+ }
+ };
+
+ let (params, args) = params_args;
+
+ let fn_call = quote! {
+ #assoc_fn_error
+ #fn_call_prefix #name(#(#args),*)
+ };
+
+ gen_ffi_function(sig, ffi_ident, &params, fn_call)
+}
+
+fn is_receiver(fn_arg: &FnArg) -> bool {
+ match fn_arg {
+ FnArg::Receiver(_) => true,
+ FnArg::Typed(pat_ty) => matches!(&*pat_ty.pat, Pat::Ident(i) if i.ident == "self"),
+ }
+}
+
+fn collect_params<'a>(
+ inputs: impl IntoIterator<Item = &'a FnArg> + 'a,
+ receiver_error_msg: &'static str,
+) -> impl Iterator<Item = (TokenStream, TokenStream)> + 'a {
+ fn receiver_error(
+ receiver: impl ToTokens,
+ receiver_error_msg: &str,
+ ) -> (TokenStream, TokenStream) {
+ let param = quote! { &self };
+ let arg = syn::Error::new_spanned(receiver, receiver_error_msg).into_compile_error();
+ (param, arg)
+ }
+
+ inputs.into_iter().enumerate().map(|(i, arg)| {
+ let (ty, name) = match arg {
+ FnArg::Receiver(r) => {
+ return receiver_error(r, receiver_error_msg);
+ }
+ FnArg::Typed(pat_ty) => {
+ let name = match &*pat_ty.pat {
+ Pat::Ident(i) if i.ident == "self" => {
+ return receiver_error(i, receiver_error_msg);
+ }
+ Pat::Ident(i) => Some(i.ident.to_string()),
+ _ => None,
+ };
+
+ (&pat_ty.ty, name)
+ }
+ };
+
+ let arg_n = format_ident!("arg{i}");
+ let param = quote! { #arg_n: <#ty as ::uniffi::FfiConverter>::FfiType };
+
+ // FIXME: With UDL, fallible functions use uniffi::lower_anyhow_error_or_panic instead of
+ // panicking unconditionally. This seems cleaner though.
+ let panic_fmt = match name {
+ Some(name) => format!("Failed to convert arg '{name}': {{}}"),
+ None => format!("Failed to convert arg #{i}: {{}}"),
+ };
+ let arg = quote! {
+ <#ty as ::uniffi::FfiConverter>::try_lift(#arg_n).unwrap_or_else(|err| {
+ ::std::panic!(#panic_fmt, err)
+ })
+ };
+
+ (param, arg)
+ })
+}
+
+fn gen_ffi_function(
+ sig: &Signature,
+ ffi_ident: Ident,
+ params: &[TokenStream],
+ rust_fn_call: TokenStream,
+) -> TokenStream {
+ let name = &sig.ident;
+ let name_s = name.to_string();
+
+ let unit_slot;
+ let (ty, throws) = match &sig.output {
+ Some(FunctionReturn { ty, throws }) => (ty, throws),
+ None => {
+ unit_slot = parse_quote! { () };
+ (&unit_slot, &None)
+ }
+ };
+
+ let return_expr = if let Some(error_ident) = throws {
+ quote! {
+ ::uniffi::call_with_result(call_status, || {
+ let val = #rust_fn_call.map_err(|e| {
+ <#error_ident as ::uniffi::FfiConverter>::lower(
+ ::std::convert::Into::into(e),
+ )
+ })?;
+ Ok(<#ty as ::uniffi::FfiReturn>::lower(val))
+ })
+ }
+ } else {
+ quote! {
+ ::uniffi::call_with_output(call_status, || {
+ <#ty as ::uniffi::FfiReturn>::lower(#rust_fn_call)
+ })
+ }
+ };
+
+ quote! {
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern "C" fn #ffi_ident(
+ #(#params,)*
+ call_status: &mut ::uniffi::RustCallStatus,
+ ) -> <#ty as ::uniffi::FfiReturn>::FfiType {
+ ::uniffi::deps::log::debug!(#name_s);
+ #return_expr
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/lib.rs b/third_party/rust/uniffi_macros/src/lib.rs
new file mode 100644
index 0000000000..a38705e57d
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/lib.rs
@@ -0,0 +1,179 @@
+/* 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/. */
+#![cfg_attr(feature = "nightly", feature(proc_macro_expand))]
+
+//! Macros for `uniffi`.
+//!
+//! Currently this is just for easily generating integration tests, but maybe
+//! we'll put some other code-annotation helper macros in here at some point.
+
+use camino::Utf8Path;
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, LitStr};
+use util::rewrite_self_type;
+
+mod enum_;
+mod error;
+mod export;
+mod object;
+mod record;
+mod test;
+mod util;
+
+use self::{
+ enum_::expand_enum, error::expand_error, export::expand_export, object::expand_object,
+ record::expand_record,
+};
+
+/// A macro to build testcases for a component's generated bindings.
+///
+/// This macro provides some plumbing to write automated tests for the generated
+/// foreign language bindings of a component. As a component author, you can write
+/// script files in the target foreign language(s) that exercise you component API,
+/// and then call this macro to produce a `cargo test` testcase from each one.
+/// The generated code will execute your script file with appropriate configuration and
+/// environment to let it load the component bindings, and will pass iff the script
+/// exits successfully.
+///
+/// To use it, invoke the macro with the name of a fixture/example crate as the first argument,
+/// then one or more file paths relative to the crate root directory. It will produce one `#[test]`
+/// function per file, in a manner designed to play nicely with `cargo test` and its test filtering
+/// options.
+#[proc_macro]
+pub fn build_foreign_language_testcases(tokens: TokenStream) -> TokenStream {
+ test::build_foreign_language_testcases(tokens)
+}
+
+#[proc_macro_attribute]
+pub fn export(_attr: TokenStream, input: TokenStream) -> TokenStream {
+ let input2 = proc_macro2::TokenStream::from(input.clone());
+
+ let gen_output = || {
+ let mod_path = util::mod_path()?;
+ let mut item = syn::parse(input)?;
+
+ // If the input is an `impl` block, rewrite any uses of the `Self` type
+ // alias to the actual type, so we don't have to special-case it in the
+ // metadata collection or scaffolding code generation (which generates
+ // new functions outside of the `impl`).
+ rewrite_self_type(&mut item);
+
+ let metadata = export::gen_metadata(item, &mod_path)?;
+ Ok(expand_export(metadata, &mod_path))
+ };
+ let output = gen_output().unwrap_or_else(syn::Error::into_compile_error);
+
+ quote! {
+ #input2
+ #output
+ }
+ .into()
+}
+
+#[proc_macro_derive(Record)]
+pub fn derive_record(input: TokenStream) -> TokenStream {
+ let mod_path = match util::mod_path() {
+ Ok(p) => p,
+ Err(e) => return e.into_compile_error().into(),
+ };
+ let input = parse_macro_input!(input);
+
+ expand_record(input, mod_path).into()
+}
+
+#[proc_macro_derive(Enum)]
+pub fn derive_enum(input: TokenStream) -> TokenStream {
+ let mod_path = match util::mod_path() {
+ Ok(p) => p,
+ Err(e) => return e.into_compile_error().into(),
+ };
+ let input = parse_macro_input!(input);
+
+ expand_enum(input, mod_path).into()
+}
+
+#[proc_macro_derive(Object)]
+pub fn derive_object(input: TokenStream) -> TokenStream {
+ let mod_path = match util::mod_path() {
+ Ok(p) => p,
+ Err(e) => return e.into_compile_error().into(),
+ };
+ let input = parse_macro_input!(input);
+
+ expand_object(input, mod_path).into()
+}
+
+#[proc_macro_derive(Error, attributes(uniffi))]
+pub fn derive_error(input: TokenStream) -> TokenStream {
+ let mod_path = match util::mod_path() {
+ Ok(p) => p,
+ Err(e) => return e.into_compile_error().into(),
+ };
+ let input = parse_macro_input!(input);
+
+ expand_error(input, mod_path).into()
+}
+
+/// A helper macro to include generated component scaffolding.
+///
+/// This is a simple convenience macro to include the UniFFI component
+/// scaffolding as built by `uniffi_build::generate_scaffolding`.
+/// Use it like so:
+///
+/// ```rs
+/// uniffi_macros::include_scaffolding!("my_component_name");
+/// ```
+///
+/// This will expand to the appropriate `include!` invocation to include
+/// the generated `my_component_name.uniffi.rs` (which it assumes has
+/// been successfully built by your crate's `build.rs` script).
+///
+#[proc_macro]
+pub fn include_scaffolding(component_name: TokenStream) -> TokenStream {
+ let name = syn::parse_macro_input!(component_name as syn::LitStr);
+ if std::env::var("OUT_DIR").is_err() {
+ quote! {
+ compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present");
+ }
+ } else {
+ quote! {
+ include!(concat!(env!("OUT_DIR"), "/", #name, ".uniffi.rs"));
+ }
+ }.into()
+}
+
+/// A helper macro to generate and include component scaffolding.
+///
+/// This is a convenience macro designed for writing `trybuild`-style tests and
+/// probably shouldn't be used for production code. Given the path to a `.udl` file,
+/// if will run `uniffi-bindgen` to produce the corresponding Rust scaffolding and then
+/// include it directly into the calling file. Like so:
+///
+/// ```rs
+/// uniffi_macros::generate_and_include_scaffolding!("path/to/my/interface.udl");
+/// ```
+///
+#[proc_macro]
+pub fn generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream {
+ let udl_file = syn::parse_macro_input!(udl_file as syn::LitStr);
+ let udl_file_string = udl_file.value();
+ let udl_file_path = Utf8Path::new(&udl_file_string);
+ if std::env::var("OUT_DIR").is_err() {
+ quote! {
+ compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present");
+ }
+ } else if uniffi_build::generate_scaffolding(udl_file_path).is_err() {
+ quote! {
+ compile_error!(concat!("Failed to generate scaffolding from UDL file at ", #udl_file));
+ }
+ } else {
+ // We know the filename is good because `generate_scaffolding` succeeded,
+ // so this `unwrap` will never fail.
+ let name = LitStr::new(udl_file_path.file_stem().unwrap(), udl_file.span());
+ quote! {
+ uniffi_macros::include_scaffolding!(#name);
+ }
+ }.into()
+}
diff --git a/third_party/rust/uniffi_macros/src/object.rs b/third_party/rust/uniffi_macros/src/object.rs
new file mode 100644
index 0000000000..2f4988530c
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/object.rs
@@ -0,0 +1,35 @@
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::quote;
+use syn::DeriveInput;
+use uniffi_meta::ObjectMetadata;
+
+use crate::util::{assert_type_eq, create_metadata_static_var};
+
+pub fn expand_object(input: DeriveInput, module_path: Vec<String>) -> TokenStream {
+ let ident = &input.ident;
+ let name = ident.to_string();
+ let metadata = ObjectMetadata { module_path, name };
+ let free_fn_ident = Ident::new(&metadata.free_ffi_symbol_name(), Span::call_site());
+ let meta_static_var = create_metadata_static_var(ident, metadata.into());
+ let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident });
+
+ quote! {
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern "C" fn #free_fn_ident(
+ ptr: *const ::std::ffi::c_void,
+ call_status: &mut ::uniffi::RustCallStatus
+ ) {
+ uniffi::call_with_output(call_status, || {
+ assert!(!ptr.is_null());
+ let ptr = ptr.cast::<#ident>();
+ unsafe {
+ ::std::sync::Arc::decrement_strong_count(ptr);
+ }
+ });
+ }
+
+ #meta_static_var
+ #type_assertion
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/record.rs b/third_party/rust/uniffi_macros/src/record.rs
new file mode 100644
index 0000000000..60d0962503
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/record.rs
@@ -0,0 +1,108 @@
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::quote;
+use syn::{Data, DeriveInput, Field, Fields};
+use uniffi_meta::{FieldMetadata, RecordMetadata};
+
+use crate::{
+ export::metadata::convert::convert_type,
+ util::{assert_type_eq, create_metadata_static_var, try_read_field},
+};
+
+pub fn expand_record(input: DeriveInput, module_path: Vec<String>) -> TokenStream {
+ let fields = match input.data {
+ Data::Struct(s) => Some(s.fields),
+ _ => None,
+ };
+
+ let ident = &input.ident;
+
+ let (write_impl, try_read_fields) = match &fields {
+ Some(fields) => (
+ fields.iter().map(write_field).collect(),
+ fields.iter().map(try_read_field).collect(),
+ ),
+ None => {
+ let unimplemented = quote! { ::std::unimplemented!() };
+ (unimplemented.clone(), unimplemented)
+ }
+ };
+
+ let meta_static_var = if let Some(fields) = fields {
+ match record_metadata(ident, fields, module_path) {
+ Ok(metadata) => create_metadata_static_var(ident, metadata.into()),
+ Err(e) => e.into_compile_error(),
+ }
+ } else {
+ syn::Error::new(
+ Span::call_site(),
+ "This derive must only be used on structs",
+ )
+ .into_compile_error()
+ };
+
+ let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident });
+
+ quote! {
+ #[automatically_derived]
+ impl ::uniffi::RustBufferFfiConverter for #ident {
+ type RustType = Self;
+
+ fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) {
+ #write_impl
+ }
+
+ fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> {
+ Ok(Self { #try_read_fields })
+ }
+ }
+
+ #meta_static_var
+ #type_assertion
+ }
+}
+
+fn record_metadata(
+ ident: &Ident,
+ fields: Fields,
+ module_path: Vec<String>,
+) -> syn::Result<RecordMetadata> {
+ let name = ident.to_string();
+ let fields = match fields {
+ Fields::Named(fields) => fields.named,
+ _ => {
+ return Err(syn::Error::new(
+ Span::call_site(),
+ "UniFFI only supports structs with named fields",
+ ));
+ }
+ };
+
+ let fields = fields
+ .iter()
+ .map(field_metadata)
+ .collect::<syn::Result<_>>()?;
+
+ Ok(RecordMetadata {
+ module_path,
+ name,
+ fields,
+ })
+}
+
+fn field_metadata(f: &Field) -> syn::Result<FieldMetadata> {
+ let name = f.ident.as_ref().unwrap().to_string();
+
+ Ok(FieldMetadata {
+ name,
+ ty: convert_type(&f.ty)?,
+ })
+}
+
+fn write_field(f: &Field) -> TokenStream {
+ let ident = &f.ident;
+ let ty = &f.ty;
+
+ quote! {
+ <#ty as ::uniffi::FfiConverter>::write(obj.#ident, buf);
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/test.rs b/third_party/rust/uniffi_macros/src/test.rs
new file mode 100644
index 0000000000..ddcbf46000
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/test.rs
@@ -0,0 +1,90 @@
+/* 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 camino::{Utf8Path, Utf8PathBuf};
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use std::env;
+use syn::{parse_macro_input, punctuated::Punctuated, LitStr, Token};
+
+pub(crate) fn build_foreign_language_testcases(tokens: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(tokens as BuildForeignLanguageTestCaseInput);
+ // we resolve each path relative to the crate root directory.
+ let pkg_dir = env::var("CARGO_MANIFEST_DIR")
+ .expect("Missing $CARGO_MANIFEST_DIR, cannot build tests for generated bindings");
+
+ // For each test file found, generate a matching testcase.
+ let test_functions = input
+ .test_scripts
+ .iter()
+ .map(|file_path| {
+ let test_file_pathbuf: Utf8PathBuf = [&pkg_dir, file_path].iter().collect();
+ let test_file_path = test_file_pathbuf.to_string();
+ let test_file_name = test_file_pathbuf
+ .file_name()
+ .expect("Test file has no name, cannot build tests for generated bindings");
+ let test_name = format_ident!(
+ "uniffi_foreign_language_testcase_{}",
+ test_file_name.replace(|c: char| !c.is_alphanumeric(), "_")
+ );
+ let run_test = match test_file_pathbuf.extension() {
+ Some("kts") => quote! {
+ uniffi::kotlin_run_test
+ },
+ Some("swift") => quote! {
+ uniffi::swift_run_test
+ },
+ Some("py") => quote! {
+ uniffi::python_run_test
+ },
+ Some("rb") => quote! {
+ uniffi::ruby_run_test
+ },
+ _ => panic!("Unexpected extension for test script: {test_file_name}"),
+ };
+ let maybe_ignore = if should_skip_path(&test_file_pathbuf) {
+ quote! { #[ignore] }
+ } else {
+ quote! {}
+ };
+ quote! {
+ #maybe_ignore
+ #[test]
+ fn #test_name () -> uniffi::deps::anyhow::Result<()> {
+ #run_test(
+ std::env!("CARGO_TARGET_TMPDIR"),
+ std::env!("CARGO_PKG_NAME"),
+ #test_file_path)
+ }
+ }
+ })
+ .collect::<Vec<proc_macro2::TokenStream>>();
+ let test_module = quote! {
+ #(#test_functions)*
+ };
+ TokenStream::from(test_module)
+}
+
+// UNIFFI_TESTS_DISABLE_EXTENSIONS contains a comma-sep'd list of extensions (without leading `.`)
+fn should_skip_path(path: &Utf8Path) -> bool {
+ let ext = path.extension().expect("File has no extension!");
+ env::var("UNIFFI_TESTS_DISABLE_EXTENSIONS")
+ .map(|v| v.split(',').any(|look| look == ext))
+ .unwrap_or(false)
+}
+
+struct BuildForeignLanguageTestCaseInput {
+ test_scripts: Vec<String>,
+}
+
+impl syn::parse::Parse for BuildForeignLanguageTestCaseInput {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ let test_scripts = Punctuated::<LitStr, Token![,]>::parse_terminated(input)?
+ .iter()
+ .map(|s| s.value())
+ .collect();
+
+ Ok(Self { test_scripts })
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/util.rs b/third_party/rust/uniffi_macros/src/util.rs
new file mode 100644
index 0000000000..e8e03b3b5d
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/util.rs
@@ -0,0 +1,223 @@
+/* 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 proc_macro2::{Ident, Span, TokenStream};
+use quote::{format_ident, quote, quote_spanned, ToTokens};
+use syn::{
+ parse::{Parse, ParseStream},
+ punctuated::Punctuated,
+ spanned::Spanned,
+ visit_mut::VisitMut,
+ Attribute, Item, Token, Type,
+};
+use uniffi_meta::Metadata;
+
+#[cfg(not(feature = "nightly"))]
+pub fn mod_path() -> syn::Result<Vec<String>> {
+ // Without the nightly feature and TokenStream::expand_expr, just return the crate name
+
+ use std::path::Path;
+
+ use fs_err as fs;
+ use once_cell::sync::Lazy;
+ use serde::Deserialize;
+
+ #[derive(Deserialize)]
+ struct CargoToml {
+ package: Package,
+ #[serde(default)]
+ lib: Lib,
+ }
+
+ #[derive(Deserialize)]
+ struct Package {
+ name: String,
+ }
+
+ #[derive(Default, Deserialize)]
+ struct Lib {
+ name: Option<String>,
+ }
+
+ static LIB_CRATE_MOD_PATH: Lazy<Result<Vec<String>, String>> = Lazy::new(|| {
+ let manifest_dir =
+ std::env::var_os("CARGO_MANIFEST_DIR").ok_or("`CARGO_MANIFEST_DIR` is not set")?;
+
+ let cargo_toml_bytes =
+ fs::read(Path::new(&manifest_dir).join("Cargo.toml")).map_err(|e| e.to_string())?;
+ let cargo_toml = toml::from_slice::<CargoToml>(&cargo_toml_bytes)
+ .map_err(|e| format!("Failed to parse `Cargo.toml`: {e}"))?;
+
+ let lib_crate_name = cargo_toml
+ .lib
+ .name
+ .unwrap_or_else(|| cargo_toml.package.name.replace('-', "_"));
+
+ Ok(vec![lib_crate_name])
+ });
+
+ LIB_CRATE_MOD_PATH
+ .clone()
+ .map_err(|e| syn::Error::new(Span::call_site(), e))
+}
+
+#[cfg(feature = "nightly")]
+pub fn mod_path() -> syn::Result<Vec<String>> {
+ use proc_macro::TokenStream;
+
+ let module_path_invoc = TokenStream::from(quote! { ::core::module_path!() });
+ // We ask the compiler what `module_path!()` expands to here.
+ // This is a nightly feature, tracked at https://github.com/rust-lang/rust/issues/90765
+ let expanded_module_path = TokenStream::expand_expr(&module_path_invoc)
+ .map_err(|e| syn::Error::new(Span::call_site(), e))?;
+ Ok(syn::parse::<syn::LitStr>(expanded_module_path)?
+ .value()
+ .split("::")
+ .collect())
+}
+
+/// Rewrite Self type alias usage in an impl block to the type itself.
+///
+/// For example,
+///
+/// ```ignore
+/// impl some::module::Foo {
+/// fn method(
+/// self: Arc<Self>,
+/// arg: Option<Bar<(), Self>>,
+/// ) -> Result<Self, Error> {
+/// todo!()
+/// }
+/// }
+/// ```
+///
+/// will be rewritten to
+///
+/// ```ignore
+/// impl some::module::Foo {
+/// fn method(
+/// self: Arc<some::module::Foo>,
+/// arg: Option<Bar<(), some::module::Foo>>,
+/// ) -> Result<some::module::Foo, Error> {
+/// todo!()
+/// }
+/// }
+/// ```
+pub fn rewrite_self_type(item: &mut Item) {
+ let item = match item {
+ Item::Impl(i) => i,
+ _ => return,
+ };
+
+ struct RewriteSelfVisitor<'a>(&'a Type);
+
+ impl<'a> VisitMut for RewriteSelfVisitor<'a> {
+ fn visit_type_mut(&mut self, i: &mut Type) {
+ match i {
+ Type::Path(p) if p.qself.is_none() && p.path.is_ident("Self") => {
+ *i = self.0.clone();
+ }
+ _ => syn::visit_mut::visit_type_mut(self, i),
+ }
+ }
+ }
+
+ let mut visitor = RewriteSelfVisitor(&item.self_ty);
+ for item in &mut item.items {
+ visitor.visit_impl_item_mut(item);
+ }
+}
+
+pub fn try_read_field(f: &syn::Field) -> TokenStream {
+ let ident = &f.ident;
+ let ty = &f.ty;
+
+ quote! {
+ #ident: <#ty as ::uniffi::FfiConverter>::try_read(buf)?,
+ }
+}
+
+pub fn create_metadata_static_var(name: &Ident, val: Metadata) -> TokenStream {
+ let data: Vec<u8> = bincode::serialize(&val).expect("Error serializing metadata item");
+ let count = data.len();
+ let var_name = format_ident!("UNIFFI_META_{}", name);
+
+ quote! {
+ #[no_mangle]
+ #[doc(hidden)]
+ pub static #var_name: [u8; #count] = [#(#data),*];
+ }
+}
+
+pub fn assert_type_eq(a: impl ToTokens + Spanned, b: impl ToTokens) -> TokenStream {
+ quote_spanned! {a.span()=>
+ #[allow(unused_qualifications)]
+ const _: () = {
+ ::uniffi::deps::static_assertions::assert_type_eq_all!(#a, #b);
+ };
+ }
+}
+
+pub fn chain<T>(
+ a: impl IntoIterator<Item = T>,
+ b: impl IntoIterator<Item = T>,
+) -> impl Iterator<Item = T> {
+ a.into_iter().chain(b)
+}
+
+pub trait UniffiAttribute: Default + Parse {
+ fn merge(self, other: Self) -> syn::Result<Self>;
+}
+
+#[derive(Default)]
+struct AttributeNotAllowedHere;
+
+impl Parse for AttributeNotAllowedHere {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ Err(syn::Error::new(
+ input.span(),
+ "UniFFI attributes are not currently recognized in this position",
+ ))
+ }
+}
+
+impl UniffiAttribute for AttributeNotAllowedHere {
+ fn merge(self, _other: Self) -> syn::Result<Self> {
+ Ok(Self)
+ }
+}
+
+pub trait AttributeSliceExt {
+ fn parse_uniffi_attributes<T: UniffiAttribute>(&self) -> syn::Result<T>;
+ fn attributes_not_allowed_here(&self) -> Option<syn::Error>;
+}
+
+impl AttributeSliceExt for [Attribute] {
+ fn parse_uniffi_attributes<T: UniffiAttribute>(&self) -> syn::Result<T> {
+ self.iter()
+ .filter(|attr| attr.path.is_ident("uniffi"))
+ .try_fold(T::default(), |res, attr| {
+ let list: Punctuated<T, Token![,]> =
+ attr.parse_args_with(Punctuated::parse_terminated)?;
+ list.into_iter().try_fold(res, T::merge)
+ })
+ }
+
+ fn attributes_not_allowed_here(&self) -> Option<syn::Error> {
+ self.parse_uniffi_attributes::<AttributeNotAllowedHere>()
+ .err()
+ }
+}
+
+pub fn either_attribute_arg<T: ToTokens>(a: Option<T>, b: Option<T>) -> syn::Result<Option<T>> {
+ match (a, b) {
+ (None, None) => Ok(None),
+ (Some(val), None) | (None, Some(val)) => Ok(Some(val)),
+ (Some(a), Some(b)) => {
+ let mut error = syn::Error::new_spanned(a, "redundant attribute argument");
+ error.combine(syn::Error::new_spanned(b, "note: first one here"));
+ Err(error)
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_meta/.cargo-checksum.json b/third_party/rust/uniffi_meta/.cargo-checksum.json
new file mode 100644
index 0000000000..c7b53da275
--- /dev/null
+++ b/third_party/rust/uniffi_meta/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"034db422dda8022d11beeac51c91bb472c9fc8d523e95a01a348ed6c90383bc1","src/lib.rs":"fde4a760b126cacf7e8008a8a8ce4e97b3c91d1ec3db08df4945bee549dee16a"},"package":"66fdab2c436aed7a6391bec64204ec33948bfed9b11b303235740771f85c4ea6"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_meta/Cargo.toml b/third_party/rust/uniffi_meta/Cargo.toml
new file mode 100644
index 0000000000..ee71f4c121
--- /dev/null
+++ b/third_party/rust/uniffi_meta/Cargo.toml
@@ -0,0 +1,33 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi_meta"
+version = "0.23.0"
+description = "uniffi_meta"
+homepage = "https://mozilla.github.io/uniffi-rs"
+keywords = [
+ "ffi",
+ "bindgen",
+]
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/uniffi-rs"
+
+[dependencies.serde]
+version = "1.0.136"
+features = ["derive"]
+
+[dependencies.siphasher]
+version = "0.3"
+
+[dependencies.uniffi_checksum_derive]
+version = "0.23.0"
diff --git a/third_party/rust/uniffi_meta/src/lib.rs b/third_party/rust/uniffi_meta/src/lib.rs
new file mode 100644
index 0000000000..13af3b112d
--- /dev/null
+++ b/third_party/rust/uniffi_meta/src/lib.rs
@@ -0,0 +1,280 @@
+/* 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 std::{collections::BTreeMap, hash::Hasher};
+pub use uniffi_checksum_derive::Checksum;
+
+use serde::{Deserialize, Serialize};
+
+/// Similar to std::hash::Hash.
+///
+/// Implementations of this trait are expected to update the hasher state in
+/// the same way across platforms. #[derive(Checksum)] will do the right thing.
+pub trait Checksum {
+ fn checksum<H: Hasher>(&self, state: &mut H);
+}
+
+impl Checksum for bool {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ state.write_u8(*self as u8);
+ }
+}
+
+impl Checksum for u64 {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ state.write(&self.to_le_bytes());
+ }
+}
+
+impl Checksum for i64 {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ state.write(&self.to_le_bytes());
+ }
+}
+
+impl<T: Checksum> Checksum for Box<T> {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ (**self).checksum(state)
+ }
+}
+
+impl<T: Checksum> Checksum for [T] {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ state.write(&(self.len() as u64).to_le_bytes());
+ for item in self {
+ Checksum::checksum(item, state);
+ }
+ }
+}
+
+impl<T: Checksum> Checksum for Vec<T> {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ Checksum::checksum(&**self, state);
+ }
+}
+
+impl<K: Checksum, V: Checksum> Checksum for BTreeMap<K, V> {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ state.write(&(self.len() as u64).to_le_bytes());
+ for (key, value) in self {
+ Checksum::checksum(key, state);
+ Checksum::checksum(value, state);
+ }
+ }
+}
+
+impl<T: Checksum> Checksum for Option<T> {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ match self {
+ None => state.write(&0u64.to_le_bytes()),
+ Some(value) => {
+ state.write(&1u64.to_le_bytes());
+ Checksum::checksum(value, state)
+ }
+ }
+ }
+}
+
+impl Checksum for str {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ state.write(self.as_bytes());
+ state.write_u8(0xff);
+ }
+}
+
+impl Checksum for String {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ (**self).checksum(state)
+ }
+}
+
+impl Checksum for &str {
+ fn checksum<H: Hasher>(&self, state: &mut H) {
+ (**self).checksum(state)
+ }
+}
+
+#[derive(Clone, Debug, Checksum, Deserialize, Serialize)]
+pub struct FnMetadata {
+ pub module_path: Vec<String>,
+ pub name: String,
+ pub inputs: Vec<FnParamMetadata>,
+ pub return_type: Option<Type>,
+ pub throws: Option<String>,
+}
+
+impl FnMetadata {
+ pub fn ffi_symbol_name(&self) -> String {
+ fn_ffi_symbol_name(&self.module_path, &self.name, checksum(self))
+ }
+}
+
+#[derive(Clone, Debug, Checksum, Deserialize, Serialize)]
+pub struct MethodMetadata {
+ pub module_path: Vec<String>,
+ pub self_name: String,
+ pub name: String,
+ pub inputs: Vec<FnParamMetadata>,
+ pub return_type: Option<Type>,
+ pub throws: Option<String>,
+}
+
+impl MethodMetadata {
+ pub fn ffi_symbol_name(&self) -> String {
+ let full_name = format!("impl_{}_{}", self.self_name, self.name);
+ fn_ffi_symbol_name(&self.module_path, &full_name, checksum(self))
+ }
+}
+
+#[derive(Clone, Debug, Checksum, Deserialize, Serialize)]
+pub struct FnParamMetadata {
+ pub name: String,
+ #[serde(rename = "type")]
+ pub ty: Type,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Checksum, Deserialize, Serialize)]
+pub enum Type {
+ U8,
+ U16,
+ U32,
+ U64,
+ I8,
+ I16,
+ I32,
+ I64,
+ F32,
+ F64,
+ Bool,
+ String,
+ Option {
+ inner_type: Box<Type>,
+ },
+ Vec {
+ inner_type: Box<Type>,
+ },
+ HashMap {
+ key_type: Box<Type>,
+ value_type: Box<Type>,
+ },
+ ArcObject {
+ object_name: String,
+ },
+ Unresolved {
+ name: String,
+ },
+}
+
+#[derive(Clone, Debug, Checksum, Deserialize, Serialize)]
+pub struct RecordMetadata {
+ pub module_path: Vec<String>,
+ pub name: String,
+ pub fields: Vec<FieldMetadata>,
+}
+
+#[derive(Clone, Debug, Checksum, Deserialize, Serialize)]
+pub struct FieldMetadata {
+ pub name: String,
+ #[serde(rename = "type")]
+ pub ty: Type,
+}
+
+#[derive(Clone, Debug, Checksum, Deserialize, Serialize)]
+pub struct EnumMetadata {
+ pub module_path: Vec<String>,
+ pub name: String,
+ pub variants: Vec<VariantMetadata>,
+}
+
+#[derive(Clone, Debug, Checksum, Deserialize, Serialize)]
+pub struct VariantMetadata {
+ pub name: String,
+ pub fields: Vec<FieldMetadata>,
+}
+
+#[derive(Clone, Debug, Checksum, Deserialize, Serialize)]
+pub struct ObjectMetadata {
+ pub module_path: Vec<String>,
+ pub name: String,
+}
+
+impl ObjectMetadata {
+ /// FFI symbol name for the `free` function for this object.
+ ///
+ /// This function is used to free the memory used by this object.
+ pub fn free_ffi_symbol_name(&self) -> String {
+ let free_name = format!("object_free_{}", self.name);
+ fn_ffi_symbol_name(&self.module_path, &free_name, checksum(self))
+ }
+}
+
+#[derive(Clone, Debug, Checksum, Deserialize, Serialize)]
+pub struct ErrorMetadata {
+ pub module_path: Vec<String>,
+ pub name: String,
+ pub variants: Vec<VariantMetadata>,
+ pub flat: bool,
+}
+
+/// Returns the last 16 bits of the value's hash as computed with [`SipHasher13`].
+///
+/// To be used as a checksum of FFI symbols, as a safeguard against different UniFFI versions being
+/// used for scaffolding and bindings generation.
+pub fn checksum<T: Checksum>(val: &T) -> u16 {
+ let mut hasher = siphasher::sip::SipHasher13::new();
+ val.checksum(&mut hasher);
+ (hasher.finish() & 0x000000000000FFFF) as u16
+}
+
+pub fn fn_ffi_symbol_name(mod_path: &[String], name: &str, checksum: u16) -> String {
+ let mod_path = mod_path.join("__");
+ format!("_uniffi_{mod_path}_{name}_{checksum:x}")
+}
+
+/// Enum covering all the possible metadata types
+#[derive(Clone, Debug, Checksum, Deserialize, Serialize)]
+pub enum Metadata {
+ Func(FnMetadata),
+ Method(MethodMetadata),
+ Record(RecordMetadata),
+ Enum(EnumMetadata),
+ Object(ObjectMetadata),
+ Error(ErrorMetadata),
+}
+
+impl From<FnMetadata> for Metadata {
+ fn from(value: FnMetadata) -> Metadata {
+ Self::Func(value)
+ }
+}
+
+impl From<MethodMetadata> for Metadata {
+ fn from(m: MethodMetadata) -> Self {
+ Self::Method(m)
+ }
+}
+
+impl From<RecordMetadata> for Metadata {
+ fn from(r: RecordMetadata) -> Self {
+ Self::Record(r)
+ }
+}
+
+impl From<EnumMetadata> for Metadata {
+ fn from(e: EnumMetadata) -> Self {
+ Self::Enum(e)
+ }
+}
+
+impl From<ObjectMetadata> for Metadata {
+ fn from(v: ObjectMetadata) -> Self {
+ Self::Object(v)
+ }
+}
+
+impl From<ErrorMetadata> for Metadata {
+ fn from(v: ErrorMetadata) -> Self {
+ Self::Error(v)
+ }
+}
diff --git a/third_party/rust/uniffi_testing/.cargo-checksum.json b/third_party/rust/uniffi_testing/.cargo-checksum.json
new file mode 100644
index 0000000000..45c384e054
--- /dev/null
+++ b/third_party/rust/uniffi_testing/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"6077dda68909ce797d4b68f6a617bafb83a7a140ea43a90633b2bb78f37fab0a","README.md":"ec6aba24af9a011ef6647422aa22efabdee519cdee3da1a9f9033b07b7cbdb0d","src/lib.rs":"925b93068b9d3a9d890f3ee3e6d760fd032359ac083350fd919cbb71a2a6580c"},"package":"92b0570953ec41d97ce23e3b92161ac18231670a1f97523258a6d2ab76d7f76c"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_testing/Cargo.toml b/third_party/rust/uniffi_testing/Cargo.toml
new file mode 100644
index 0000000000..9869e861c3
--- /dev/null
+++ b/third_party/rust/uniffi_testing/Cargo.toml
@@ -0,0 +1,43 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi_testing"
+version = "0.23.0"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+description = "a multi-language bindings generator for rust (testing helpers)"
+homepage = "https://mozilla.github.io/uniffi-rs"
+documentation = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/uniffi-rs"
+
+[dependencies.anyhow]
+version = "1"
+
+[dependencies.camino]
+version = "1.0.8"
+
+[dependencies.cargo_metadata]
+version = "0.15"
+
+[dependencies.fs-err]
+version = "2.7.0"
+
+[dependencies.once_cell]
+version = "1.12"
+
+[dependencies.serde]
+version = "1"
+
+[dependencies.serde_json]
+version = "1"
diff --git a/third_party/rust/uniffi_testing/README.md b/third_party/rust/uniffi_testing/README.md
new file mode 100644
index 0000000000..c171b26373
--- /dev/null
+++ b/third_party/rust/uniffi_testing/README.md
@@ -0,0 +1,18 @@
+This crate contains helper code for testing bindings. Our general system is to
+generate bindings for the libraries from the examples and fixtures
+directories, then execute a script that tests the bindings.
+
+Each bindings crate can do this in a different way, but the typical system is:
+
+ - Construct a `UniFFITestHelper` struct to assist the process
+ - Call `UniFFITestHelper.create_out_dir()` to create a temp directory to
+ store testing files
+ - Call `UniFFITestHelper.copy_cdylibs_to_out_dir()` to copy the dylib
+ artifacts for the example/fixture library to the `out_dir`. This is needed
+ because the bindings code dynamically links to or loads from this library.
+ - Call `UniFFITestHelper.get_compile_sources()` to iterate over (`udl_path`,
+ `uniffi_config_path`) pairs and generate the bindings from them. This step
+ is specific to the bindings language, it may mean creating a .jar file,
+ compiling a binary, or just copying script files over.
+ - Execute the test script and check if it succeeds. This step is also
+ specific to the bindings language.
diff --git a/third_party/rust/uniffi_testing/src/lib.rs b/third_party/rust/uniffi_testing/src/lib.rs
new file mode 100644
index 0000000000..cc08895214
--- /dev/null
+++ b/third_party/rust/uniffi_testing/src/lib.rs
@@ -0,0 +1,275 @@
+/* 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 anyhow::{bail, Result};
+use camino::{Utf8Path, Utf8PathBuf};
+use cargo_metadata::{Message, Metadata, MetadataCommand, Package, Target};
+use fs_err as fs;
+use once_cell::sync::Lazy;
+use serde::Deserialize;
+use std::{
+ collections::hash_map::DefaultHasher,
+ env,
+ env::consts::DLL_EXTENSION,
+ hash::{Hash, Hasher},
+ process::{Command, Stdio},
+};
+
+#[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<Vec<String>>,
+}
+
+// A source to compile for a test
+#[derive(Debug)]
+pub struct CompileSource {
+ pub udl_path: Utf8PathBuf,
+ pub config_path: Option<Utf8PathBuf>,
+}
+
+// Store Cargo output to in a static to avoid calling it more than once.
+
+static CARGO_METADATA: Lazy<Metadata> = Lazy::new(get_cargo_metadata);
+static CARGO_BUILD_MESSAGES: Lazy<Vec<Message>> = Lazy::new(get_cargo_build_messages);
+
+/// Struct for running fixture and example tests for bindings generators
+///
+/// Expectations:
+/// - Used from a integration test (a `.rs` file in the tests/ directory)
+/// - The working directory is the project root for the bindings crate. This is the normal case
+/// for test code, just make sure you don't cd somewhere else.
+/// - The bindings crate has a dev-dependency on the fixture crate
+/// - The fixture crate produces a cdylib library
+/// - The fixture crate, and any external-crates, has 1 UDL file in it's src/ directory
+pub struct UniFFITestHelper {
+ name: String,
+ package: Package,
+ metadata: Option<UniFFITestingMetadata>,
+}
+
+impl UniFFITestHelper {
+ pub fn new(name: &str) -> Result<Self> {
+ let package = Self::find_package(name)?;
+ let metadata: Option<UniFFITestingMetadata> = package
+ .metadata
+ .pointer("/uniffi/testing")
+ .cloned()
+ .map(serde_json::from_value)
+ .transpose()?;
+ Ok(Self {
+ name: name.to_string(),
+ package,
+ metadata,
+ })
+ }
+
+ fn find_package(name: &str) -> Result<Package> {
+ let matching: Vec<&Package> = CARGO_METADATA
+ .packages
+ .iter()
+ .filter(|p| p.name == name)
+ .collect();
+ match matching.len() {
+ 1 => Ok(matching[0].clone()),
+ n => bail!("cargo metadata return {n} packages named {name}"),
+ }
+ }
+
+ fn find_packages_for_external_crates(&self) -> Result<Vec<Package>> {
+ // Add any external crates listed in `Cargo.toml`
+ match &self.metadata {
+ None => Ok(vec![]),
+ Some(metadata) => metadata
+ .external_crates
+ .iter()
+ .flatten()
+ .map(|name| Self::find_package(name))
+ .collect(),
+ }
+ }
+
+ fn find_cdylib_path(package: &Package) -> Result<Utf8PathBuf> {
+ let cdylib_targets: Vec<&Target> = package
+ .targets
+ .iter()
+ .filter(|t| t.crate_types.iter().any(|t| t == "cdylib"))
+ .collect();
+ let target = match cdylib_targets.len() {
+ 1 => cdylib_targets[0],
+ n => bail!("Found {n} cdylib targets for {}", package.name),
+ };
+
+ let artifacts = CARGO_BUILD_MESSAGES
+ .iter()
+ .filter_map(|message| match message {
+ Message::CompilerArtifact(artifact) => {
+ if artifact.target == *target {
+ Some(artifact.clone())
+ } else {
+ None
+ }
+ }
+ _ => None,
+ });
+ let cdylib_files: Vec<Utf8PathBuf> = artifacts
+ .into_iter()
+ .flat_map(|artifact| {
+ artifact
+ .filenames
+ .into_iter()
+ .filter(|nm| matches!(nm.extension(), Some(DLL_EXTENSION)))
+ .collect::<Vec<Utf8PathBuf>>()
+ })
+ .collect();
+
+ match cdylib_files.len() {
+ 1 => Ok(cdylib_files[0].to_owned()),
+ n => bail!("Found {n} cdylib files for {}", package.name),
+ }
+ }
+
+ /// Create at `out_dir` for testing
+ ///
+ /// This directory can be used for:
+ /// - Generated bindings files (usually via the `--out-dir` param)
+ /// - cdylib libraries that the bindings depend on
+ /// - Anything else that's useful for testing
+ ///
+ /// This directory typically created as a subdirectory of `CARGO_TARGET_TMPDIR` when running an
+ /// integration test.
+ ///
+ /// We use the script path to create a hash included in the outpuit directory. This avoids
+ /// path collutions when 2 scripts run against the same fixture.
+ pub fn create_out_dir(
+ &self,
+ temp_dir: impl AsRef<Utf8Path>,
+ script_path: impl AsRef<Utf8Path>,
+ ) -> Result<Utf8PathBuf> {
+ let dirname = format!("{}-{}", self.name, hash_path(script_path.as_ref()));
+ let out_dir = temp_dir.as_ref().join(dirname);
+ if out_dir.exists() {
+ // Clean out any files from previous runs
+ fs::remove_dir_all(&out_dir)?;
+ }
+ fs::create_dir(&out_dir)?;
+ Ok(out_dir)
+ }
+
+ /// Copy the `cdylib` for a fixture into the out_dir
+ ///
+ /// This is typically needed for the bindings to open it when running the tests
+ ///
+ /// Returns the path to the copied library
+ pub fn copy_cdylibs_to_out_dir(&self, out_dir: impl AsRef<Utf8Path>) -> Result<()> {
+ let cdylib_paths =
+ std::iter::once(self.cdylib_path()?).chain(self.external_cdylib_paths()?);
+
+ for path in cdylib_paths {
+ let dest = out_dir.as_ref().join(path.file_name().unwrap());
+ fs::copy(&path, dest)?;
+ }
+ Ok(())
+ }
+
+ /// Get the path to the cdylib file for this package
+ pub fn cdylib_path(&self) -> Result<Utf8PathBuf> {
+ Self::find_cdylib_path(&self.package)
+ }
+
+ /// Get the path to the cdylib file for external crates listed in `Cargo.toml`
+ pub fn external_cdylib_paths(&self) -> Result<Vec<Utf8PathBuf>> {
+ self.find_packages_for_external_crates()?
+ .into_iter()
+ .map(|p| Self::find_cdylib_path(&p))
+ .collect()
+ }
+
+ /// Get paths to the UDL and config files for a fixture
+ pub fn get_compile_sources(&self) -> Result<Vec<CompileSource>> {
+ Ok(std::iter::once(self.get_main_compile_source()?)
+ .chain(self.get_external_compile_sources()?)
+ .collect())
+ }
+
+ pub fn get_main_compile_source(&self) -> Result<CompileSource> {
+ self.find_compile_source(&self.package.clone())
+ }
+
+ pub fn get_external_compile_sources(&self) -> Result<Vec<CompileSource>> {
+ self.find_packages_for_external_crates()?
+ .into_iter()
+ .map(|p| self.find_compile_source(&p))
+ .collect()
+ }
+
+ fn find_compile_source(&self, package: &Package) -> Result<CompileSource> {
+ let crate_root = package.manifest_path.parent().unwrap();
+ let src_dir = crate_root.join("src");
+ let mut udl_paths = find_files(
+ &src_dir,
+ |path| matches!(path.extension(), Some(ext) if ext.to_ascii_lowercase() == "udl"),
+ )?;
+ let udl_path = match udl_paths.len() {
+ 1 => udl_paths.remove(0),
+ n => bail!("Found {n} UDL files in {src_dir}"),
+ };
+ let mut config_paths = find_files(
+ crate_root,
+ |path| matches!(path.file_name(), Some(name) if name == "uniffi.toml"),
+ )?;
+ let config_path = match config_paths.len() {
+ 0 => None,
+ 1 => Some(config_paths.remove(0)),
+ n => bail!("Found {n} UDL files in {crate_root}"),
+ };
+
+ Ok(CompileSource {
+ udl_path,
+ config_path,
+ })
+ }
+}
+
+fn find_files<F: Fn(&Utf8Path) -> bool>(dir: &Utf8Path, predicate: F) -> Result<Vec<Utf8PathBuf>> {
+ fs::read_dir(dir)?
+ .flatten()
+ .map(|entry| entry.path().try_into())
+ .try_fold(Vec::new(), |mut vec, path| {
+ let path: Utf8PathBuf = path?;
+ if predicate(&path) {
+ vec.push(path);
+ }
+ Ok(vec)
+ })
+}
+
+fn get_cargo_metadata() -> Metadata {
+ MetadataCommand::new()
+ .exec()
+ .expect("error running cargo metadata")
+}
+
+fn get_cargo_build_messages() -> Vec<Message> {
+ let mut child = Command::new(env!("CARGO"))
+ .arg("build")
+ .arg("--message-format=json")
+ .arg("--tests")
+ .stdout(Stdio::piped())
+ .spawn()
+ .expect("Error running cargo build");
+ let output = std::io::BufReader::new(child.stdout.take().unwrap());
+ Message::parse_stream(output)
+ .map(|m| m.expect("Error parsing cargo build messages"))
+ .collect()
+}
+
+fn hash_path(path: &Utf8Path) -> String {
+ let mut hasher = DefaultHasher::new();
+ path.hash(&mut hasher);
+ format!("{:x}", hasher.finish())
+}