summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Geisler <mgeisler@google.com>2024-04-09 20:16:58 +0200
committerMartin Geisler <mgeisler@google.com>2024-04-09 20:45:56 +0200
commitca0981fdc1617770ec72ba72e0d506b818881b2a (patch)
treeb6732ee4ddc38dfdb32981114f7f0935f02191bc
parent4225c0f2b73c04449446c90891d5ac82baa27e0b (diff)
downloaduniffi_meta-ca0981fdc1617770ec72ba72e0d506b818881b2a.tar.gz
Import 'uniffi_meta' crate
Request Document: go/android-rust-importing-crates For CL Reviewers: go/android3p#cl-review For Build Team: go/ab-third-party-imports Bug: http://b/330712398 Test: m libuniffi_meta Change-Id: Icba1e4b1bf99ff42ccee441835f4eea0204c8fd9
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Cargo.lock76
-rw-r--r--Cargo.toml36
-rw-r--r--LICENSE373
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_MPL0
-rw-r--r--OWNERS2
-rw-r--r--README.md79
-rw-r--r--cargo_embargo.json3
-rw-r--r--src/ffi_names.rs72
-rw-r--r--src/group.rs270
-rw-r--r--src/lib.rs546
-rw-r--r--src/metadata.rs88
-rw-r--r--src/reader.rs526
-rw-r--r--src/types.rs174
15 files changed, 2271 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..1c28374
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "d5332be35ef497255f7ce49debfd917f6a1009c7"
+ },
+ "path_in_vcs": "uniffi_meta"
+} \ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..32c931a
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,76 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
+
+[[package]]
+name = "syn"
+version = "2.0.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "uniffi_checksum_derive"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72775b3afa6adb30e0c92b3107858d2fcb0ff1a417ac242db1f648b0e2dd0ef2"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "uniffi_meta"
+version = "0.26.1"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "siphasher",
+ "uniffi_checksum_derive",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..4816c8e
--- /dev/null
+++ b/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_meta"
+version = "0.26.1"
+description = "uniffi_meta"
+homepage = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
+keywords = [
+ "ffi",
+ "bindgen",
+]
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/uniffi-rs"
+
+[dependencies.anyhow]
+version = "1"
+
+[dependencies.bytes]
+version = "1.3"
+
+[dependencies.siphasher]
+version = "0.3"
+
+[dependencies.uniffi_checksum_derive]
+version = "0.26.1"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a612ad9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ 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/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d2b56a1
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "uniffi_meta"
+description: "uniffi_meta"
+third_party {
+ identifier {
+ type: "crates.io"
+ value: "uniffi_meta"
+ }
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/uniffi_meta/uniffi_meta-0.26.1.crate"
+ primary_source: true
+ }
+ version: "0.26.1"
+ license_type: RECIPROCAL
+ last_upgrade_date {
+ year: 2024
+ month: 3
+ day: 21
+ }
+}
diff --git a/MODULE_LICENSE_MPL b/MODULE_LICENSE_MPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MPL
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..48bea6e
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 688011
+include platform/prebuilts/rust:main:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bb72360
--- /dev/null
+++ b/README.md
@@ -0,0 +1,79 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFII comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/cargo_embargo.json b/cargo_embargo.json
new file mode 100644
index 0000000..cb908d7
--- /dev/null
+++ b/cargo_embargo.json
@@ -0,0 +1,3 @@
+{
+ "run_cargo": false
+}
diff --git a/src/ffi_names.rs b/src/ffi_names.rs
new file mode 100644
index 0000000..a58977b
--- /dev/null
+++ b/src/ffi_names.rs
@@ -0,0 +1,72 @@
+/* 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/. */
+
+//! Functions to calculate names for FFI symbols
+//!
+//! All of these functions input a `namespace` parameter which is:
+//! - The UDL namespace for UDL-based generation
+//! - The "module path" of the item for proc-macro based generation. Right now this is actually just the
+//! crate name, but we eventually hope to make this the full module path.
+//!
+//! This could cause collisions in the case where you combine UDL and proc-macro generation and you
+//! set the UDL namespace to the name of another crate. This seems so pathological that it's not
+//! worth the code complexity to prevent it.
+
+/// FFI symbol name for a top-level function
+pub fn fn_symbol_name(namespace: &str, name: &str) -> String {
+ let name = name.to_ascii_lowercase();
+ format!("uniffi_{namespace}_fn_func_{name}")
+}
+
+/// FFI symbol name for an object constructor
+pub fn constructor_symbol_name(namespace: &str, object_name: &str, name: &str) -> String {
+ let object_name = object_name.to_ascii_lowercase();
+ let name = name.to_ascii_lowercase();
+ format!("uniffi_{namespace}_fn_constructor_{object_name}_{name}")
+}
+
+/// FFI symbol name for an object method
+pub fn method_symbol_name(namespace: &str, object_name: &str, name: &str) -> String {
+ let object_name = object_name.to_ascii_lowercase();
+ let name = name.to_ascii_lowercase();
+ format!("uniffi_{namespace}_fn_method_{object_name}_{name}")
+}
+
+/// FFI symbol name for the `clone` function for an object.
+pub fn clone_fn_symbol_name(namespace: &str, object_name: &str) -> String {
+ let object_name = object_name.to_ascii_lowercase();
+ format!("uniffi_{namespace}_fn_clone_{object_name}")
+}
+
+/// FFI symbol name for the `free` function for an object.
+pub fn free_fn_symbol_name(namespace: &str, object_name: &str) -> String {
+ let object_name = object_name.to_ascii_lowercase();
+ format!("uniffi_{namespace}_fn_free_{object_name}")
+}
+
+/// FFI symbol name for the `init_callback` function for a callback interface
+pub fn init_callback_fn_symbol_name(namespace: &str, callback_interface_name: &str) -> String {
+ let callback_interface_name = callback_interface_name.to_ascii_lowercase();
+ format!("uniffi_{namespace}_fn_init_callback_{callback_interface_name}")
+}
+
+/// FFI checksum symbol name for a top-level function
+pub fn fn_checksum_symbol_name(namespace: &str, name: &str) -> String {
+ let name = name.to_ascii_lowercase();
+ format!("uniffi_{namespace}_checksum_func_{name}")
+}
+
+/// FFI checksum symbol name for an object constructor
+pub fn constructor_checksum_symbol_name(namespace: &str, object_name: &str, name: &str) -> String {
+ let object_name = object_name.to_ascii_lowercase();
+ let name = name.to_ascii_lowercase();
+ format!("uniffi_{namespace}_checksum_constructor_{object_name}_{name}")
+}
+
+/// FFI checksum symbol name for an object method
+pub fn method_checksum_symbol_name(namespace: &str, object_name: &str, name: &str) -> String {
+ let object_name = object_name.to_ascii_lowercase();
+ let name = name.to_ascii_lowercase();
+ format!("uniffi_{namespace}_checksum_method_{object_name}_{name}")
+}
diff --git a/src/group.rs b/src/group.rs
new file mode 100644
index 0000000..a41776b
--- /dev/null
+++ b/src/group.rs
@@ -0,0 +1,270 @@
+/* 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::{BTreeSet, HashMap};
+
+use crate::*;
+use anyhow::{bail, Result};
+
+type MetadataGroupMap = HashMap<String, MetadataGroup>;
+
+// Create empty metadata groups based on the metadata items.
+pub fn create_metadata_groups(items: &[Metadata]) -> MetadataGroupMap {
+ // Map crate names to MetadataGroup instances
+ items
+ .iter()
+ .filter_map(|i| match i {
+ Metadata::Namespace(namespace) => {
+ let group = MetadataGroup {
+ namespace: namespace.clone(),
+ namespace_docstring: None,
+ items: BTreeSet::new(),
+ };
+ Some((namespace.crate_name.clone(), group))
+ }
+ Metadata::UdlFile(udl) => {
+ let namespace = NamespaceMetadata {
+ crate_name: udl.module_path.clone(),
+ name: udl.namespace.clone(),
+ };
+ let group = MetadataGroup {
+ namespace,
+ namespace_docstring: None,
+ items: BTreeSet::new(),
+ };
+ Some((udl.module_path.clone(), group))
+ }
+ _ => None,
+ })
+ .collect::<HashMap<_, _>>()
+}
+
+/// Consume the items into the previously created metadata groups.
+pub fn group_metadata(group_map: &mut MetadataGroupMap, items: Vec<Metadata>) -> Result<()> {
+ for item in items {
+ if matches!(&item, Metadata::Namespace(_)) {
+ continue;
+ }
+
+ let crate_name = calc_crate_name(item.module_path()).to_owned(); // XXX - kill clone?
+
+ let item = fixup_external_type(item, group_map);
+ let group = match group_map.get_mut(&crate_name) {
+ Some(ns) => ns,
+ None => bail!("Unknown namespace for {item:?} ({crate_name})"),
+ };
+ if group.items.contains(&item) {
+ bail!("Duplicate metadata item: {item:?}");
+ }
+ group.add_item(item);
+ }
+ Ok(())
+}
+
+#[derive(Debug)]
+pub struct MetadataGroup {
+ pub namespace: NamespaceMetadata,
+ pub namespace_docstring: Option<String>,
+ pub items: BTreeSet<Metadata>,
+}
+
+impl MetadataGroup {
+ pub fn add_item(&mut self, item: Metadata) {
+ self.items.insert(item);
+ }
+}
+
+pub fn fixup_external_type(item: Metadata, group_map: &MetadataGroupMap) -> Metadata {
+ let crate_name = calc_crate_name(item.module_path()).to_owned();
+ let converter = ExternalTypeConverter {
+ crate_name: &crate_name,
+ crate_to_namespace: group_map,
+ };
+ converter.convert_item(item)
+}
+
+/// Convert metadata items by replacing types from external crates with Type::External
+struct ExternalTypeConverter<'a> {
+ crate_name: &'a str,
+ crate_to_namespace: &'a MetadataGroupMap,
+}
+
+impl<'a> ExternalTypeConverter<'a> {
+ fn crate_to_namespace(&self, crate_name: &str) -> String {
+ self.crate_to_namespace
+ .get(crate_name)
+ .unwrap_or_else(|| panic!("Can't find namespace for module {crate_name}"))
+ .namespace
+ .name
+ .clone()
+ }
+
+ fn convert_item(&self, item: Metadata) -> Metadata {
+ match item {
+ Metadata::Func(meta) => Metadata::Func(FnMetadata {
+ inputs: self.convert_params(meta.inputs),
+ return_type: self.convert_optional(meta.return_type),
+ throws: self.convert_optional(meta.throws),
+ ..meta
+ }),
+ Metadata::Method(meta) => Metadata::Method(MethodMetadata {
+ inputs: self.convert_params(meta.inputs),
+ return_type: self.convert_optional(meta.return_type),
+ throws: self.convert_optional(meta.throws),
+ ..meta
+ }),
+ Metadata::TraitMethod(meta) => Metadata::TraitMethod(TraitMethodMetadata {
+ inputs: self.convert_params(meta.inputs),
+ return_type: self.convert_optional(meta.return_type),
+ throws: self.convert_optional(meta.throws),
+ ..meta
+ }),
+ Metadata::Constructor(meta) => Metadata::Constructor(ConstructorMetadata {
+ inputs: self.convert_params(meta.inputs),
+ throws: self.convert_optional(meta.throws),
+ ..meta
+ }),
+ Metadata::Record(meta) => Metadata::Record(RecordMetadata {
+ fields: self.convert_fields(meta.fields),
+ ..meta
+ }),
+ Metadata::Enum(meta) => Metadata::Enum(self.convert_enum(meta)),
+ _ => item,
+ }
+ }
+
+ fn convert_params(&self, params: Vec<FnParamMetadata>) -> Vec<FnParamMetadata> {
+ params
+ .into_iter()
+ .map(|param| FnParamMetadata {
+ ty: self.convert_type(param.ty),
+ ..param
+ })
+ .collect()
+ }
+
+ fn convert_fields(&self, fields: Vec<FieldMetadata>) -> Vec<FieldMetadata> {
+ fields
+ .into_iter()
+ .map(|field| FieldMetadata {
+ ty: self.convert_type(field.ty),
+ ..field
+ })
+ .collect()
+ }
+
+ fn convert_enum(&self, enum_: EnumMetadata) -> EnumMetadata {
+ EnumMetadata {
+ variants: enum_
+ .variants
+ .into_iter()
+ .map(|variant| VariantMetadata {
+ fields: self.convert_fields(variant.fields),
+ ..variant
+ })
+ .collect(),
+ ..enum_
+ }
+ }
+
+ fn convert_optional(&self, ty: Option<Type>) -> Option<Type> {
+ ty.map(|ty| self.convert_type(ty))
+ }
+
+ fn convert_type(&self, ty: Type) -> Type {
+ match ty {
+ // Convert `ty` if it's external
+ Type::Enum { module_path, name } | Type::Record { module_path, name }
+ if self.is_module_path_external(&module_path) =>
+ {
+ Type::External {
+ namespace: self.crate_to_namespace(&module_path),
+ module_path,
+ name,
+ kind: ExternalKind::DataClass,
+ tagged: false,
+ }
+ }
+ Type::Custom {
+ module_path, name, ..
+ } if self.is_module_path_external(&module_path) => {
+ // For now, it's safe to assume that all custom types are data classes.
+ // There's no reason to use a custom type with an interface.
+ Type::External {
+ namespace: self.crate_to_namespace(&module_path),
+ module_path,
+ name,
+ kind: ExternalKind::DataClass,
+ tagged: false,
+ }
+ }
+ Type::Object {
+ module_path, name, ..
+ } if self.is_module_path_external(&module_path) => Type::External {
+ namespace: self.crate_to_namespace(&module_path),
+ module_path,
+ name,
+ kind: ExternalKind::Interface,
+ tagged: false,
+ },
+ Type::CallbackInterface { module_path, name }
+ if self.is_module_path_external(&module_path) =>
+ {
+ panic!("External callback interfaces not supported ({name})")
+ }
+ // Convert child types
+ Type::Custom {
+ module_path,
+ name,
+ builtin,
+ ..
+ } => Type::Custom {
+ module_path,
+ name,
+ builtin: Box::new(self.convert_type(*builtin)),
+ },
+ Type::Optional { inner_type } => Type::Optional {
+ inner_type: Box::new(self.convert_type(*inner_type)),
+ },
+ Type::Sequence { inner_type } => Type::Sequence {
+ inner_type: Box::new(self.convert_type(*inner_type)),
+ },
+ Type::Map {
+ key_type,
+ value_type,
+ } => Type::Map {
+ key_type: Box::new(self.convert_type(*key_type)),
+ value_type: Box::new(self.convert_type(*value_type)),
+ },
+ // Existing External types probably need namespace fixed.
+ Type::External {
+ namespace,
+ module_path,
+ name,
+ kind,
+ tagged,
+ } => {
+ assert!(namespace.is_empty());
+ Type::External {
+ namespace: self.crate_to_namespace(&module_path),
+ module_path,
+ name,
+ kind,
+ tagged,
+ }
+ }
+
+ // Otherwise, just return the type unchanged
+ _ => ty,
+ }
+ }
+
+ fn is_module_path_external(&self, module_path: &str) -> bool {
+ calc_crate_name(module_path) != self.crate_name
+ }
+}
+
+fn calc_crate_name(module_path: &str) -> &str {
+ module_path.split("::").next().unwrap()
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..eb4cdca
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,546 @@
+/* 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;
+
+mod ffi_names;
+pub use ffi_names::*;
+
+mod group;
+pub use group::{create_metadata_groups, fixup_external_type, group_metadata, MetadataGroup};
+
+mod reader;
+pub use reader::{read_metadata, read_metadata_type};
+
+mod types;
+pub use types::{AsType, ExternalKind, ObjectImpl, Type, TypeIterator};
+
+mod metadata;
+
+// This needs to match the minor version of the `uniffi` crate. See
+// `docs/uniffi-versioning.md` for details.
+//
+// Once we get to 1.0, then we'll need to update the scheme to something like 100 + major_version
+pub const UNIFFI_CONTRACT_VERSION: u32 = 25;
+
+/// 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)
+ }
+}
+
+// The namespace of a Component interface.
+//
+// This is used to match up the macro metadata with the UDL items.
+#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub struct NamespaceMetadata {
+ pub crate_name: String,
+ pub name: String,
+}
+
+// UDL file included with `include_scaffolding!()`
+//
+// This is to find the UDL files in library mode generation
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct UdlFile {
+ // The module path specified when the UDL file was parsed.
+ pub module_path: String,
+ pub namespace: String,
+ // the base filename of the udl file - no path, no extension.
+ pub file_stub: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct FnMetadata {
+ pub module_path: String,
+ pub name: String,
+ pub is_async: bool,
+ pub inputs: Vec<FnParamMetadata>,
+ pub return_type: Option<Type>,
+ pub throws: Option<Type>,
+ pub checksum: Option<u16>,
+ pub docstring: Option<String>,
+}
+
+impl FnMetadata {
+ pub fn ffi_symbol_name(&self) -> String {
+ fn_symbol_name(&self.module_path, &self.name)
+ }
+
+ pub fn checksum_symbol_name(&self) -> String {
+ fn_checksum_symbol_name(&self.module_path, &self.name)
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ConstructorMetadata {
+ pub module_path: String,
+ pub self_name: String,
+ pub name: String,
+ pub inputs: Vec<FnParamMetadata>,
+ pub throws: Option<Type>,
+ pub checksum: Option<u16>,
+ pub docstring: Option<String>,
+}
+
+impl ConstructorMetadata {
+ pub fn ffi_symbol_name(&self) -> String {
+ constructor_symbol_name(&self.module_path, &self.self_name, &self.name)
+ }
+
+ pub fn checksum_symbol_name(&self) -> String {
+ constructor_checksum_symbol_name(&self.module_path, &self.self_name, &self.name)
+ }
+
+ pub fn is_primary(&self) -> bool {
+ self.name == "new"
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct MethodMetadata {
+ pub module_path: String,
+ pub self_name: String,
+ pub name: String,
+ pub is_async: bool,
+ pub inputs: Vec<FnParamMetadata>,
+ pub return_type: Option<Type>,
+ pub throws: Option<Type>,
+ pub takes_self_by_arc: bool, // unused except by rust udl bindgen.
+ pub checksum: Option<u16>,
+ pub docstring: Option<String>,
+}
+
+impl MethodMetadata {
+ pub fn ffi_symbol_name(&self) -> String {
+ method_symbol_name(&self.module_path, &self.self_name, &self.name)
+ }
+
+ pub fn checksum_symbol_name(&self) -> String {
+ method_checksum_symbol_name(&self.module_path, &self.self_name, &self.name)
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct TraitMethodMetadata {
+ pub module_path: String,
+ pub trait_name: String,
+ // Note: the position of `index` is important since it causes callback interface methods to be
+ // ordered correctly in MetadataGroup.items
+ pub index: u32,
+ pub name: String,
+ pub is_async: bool,
+ pub inputs: Vec<FnParamMetadata>,
+ pub return_type: Option<Type>,
+ pub throws: Option<Type>,
+ pub takes_self_by_arc: bool, // unused except by rust udl bindgen.
+ pub checksum: Option<u16>,
+ pub docstring: Option<String>,
+}
+
+impl TraitMethodMetadata {
+ pub fn ffi_symbol_name(&self) -> String {
+ method_symbol_name(&self.module_path, &self.trait_name, &self.name)
+ }
+
+ pub fn checksum_symbol_name(&self) -> String {
+ method_checksum_symbol_name(&self.module_path, &self.trait_name, &self.name)
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct FnParamMetadata {
+ pub name: String,
+ pub ty: Type,
+ pub by_ref: bool,
+ pub optional: bool,
+ pub default: Option<LiteralMetadata>,
+}
+
+impl FnParamMetadata {
+ pub fn simple(name: &str, ty: Type) -> Self {
+ Self {
+ name: name.to_string(),
+ ty,
+ by_ref: false,
+ optional: false,
+ default: None,
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Checksum)]
+pub enum LiteralMetadata {
+ 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,
+}
+
+impl LiteralMetadata {
+ pub fn new_uint(v: u64) -> Self {
+ LiteralMetadata::UInt(v, Radix::Decimal, Type::UInt64)
+ }
+}
+
+// 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,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct RecordMetadata {
+ pub module_path: String,
+ pub name: String,
+ pub fields: Vec<FieldMetadata>,
+ pub docstring: Option<String>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct FieldMetadata {
+ pub name: String,
+ pub ty: Type,
+ pub default: Option<LiteralMetadata>,
+ pub docstring: Option<String>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct EnumMetadata {
+ pub module_path: String,
+ pub name: String,
+ pub forced_flatness: Option<bool>,
+ pub variants: Vec<VariantMetadata>,
+ pub discr_type: Option<Type>,
+ pub non_exhaustive: bool,
+ pub docstring: Option<String>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct VariantMetadata {
+ pub name: String,
+ pub discr: Option<LiteralMetadata>,
+ pub fields: Vec<FieldMetadata>,
+ pub docstring: Option<String>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ObjectMetadata {
+ pub module_path: String,
+ pub name: String,
+ pub imp: types::ObjectImpl,
+ pub docstring: Option<String>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CallbackInterfaceMetadata {
+ pub module_path: String,
+ pub name: String,
+ pub docstring: Option<String>,
+}
+
+impl ObjectMetadata {
+ /// FFI symbol name for the `clone` function for this object.
+ ///
+ /// This function is used to increment the reference count before lowering an object to pass
+ /// back to Rust.
+ pub fn clone_ffi_symbol_name(&self) -> String {
+ clone_fn_symbol_name(&self.module_path, &self.name)
+ }
+
+ /// 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 {
+ free_fn_symbol_name(&self.module_path, &self.name)
+ }
+}
+
+/// The list of traits we support generating helper methods for.
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum UniffiTraitMetadata {
+ Debug {
+ fmt: MethodMetadata,
+ },
+ Display {
+ fmt: MethodMetadata,
+ },
+ Eq {
+ eq: MethodMetadata,
+ ne: MethodMetadata,
+ },
+ Hash {
+ hash: MethodMetadata,
+ },
+}
+
+impl UniffiTraitMetadata {
+ fn module_path(&self) -> &String {
+ &match self {
+ UniffiTraitMetadata::Debug { fmt } => fmt,
+ UniffiTraitMetadata::Display { fmt } => fmt,
+ UniffiTraitMetadata::Eq { eq, .. } => eq,
+ UniffiTraitMetadata::Hash { hash } => hash,
+ }
+ .module_path
+ }
+
+ pub fn self_name(&self) -> &String {
+ &match self {
+ UniffiTraitMetadata::Debug { fmt } => fmt,
+ UniffiTraitMetadata::Display { fmt } => fmt,
+ UniffiTraitMetadata::Eq { eq, .. } => eq,
+ UniffiTraitMetadata::Hash { hash } => hash,
+ }
+ .self_name
+ }
+}
+
+#[repr(u8)]
+#[derive(Eq, PartialEq, Hash)]
+pub enum UniffiTraitDiscriminants {
+ Debug,
+ Display,
+ Eq,
+ Hash,
+}
+
+impl UniffiTraitDiscriminants {
+ pub fn from(v: u8) -> anyhow::Result<Self> {
+ Ok(match v {
+ 0 => UniffiTraitDiscriminants::Debug,
+ 1 => UniffiTraitDiscriminants::Display,
+ 2 => UniffiTraitDiscriminants::Eq,
+ 3 => UniffiTraitDiscriminants::Hash,
+ _ => anyhow::bail!("invalid trait discriminant {v}"),
+ })
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CustomTypeMetadata {
+ pub module_path: String,
+ pub name: String,
+ pub builtin: Type,
+}
+
+/// Returns the last 16 bits of the value's hash as computed with [`SipHasher13`].
+///
+/// This is used 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
+}
+
+/// Enum covering all the possible metadata types
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum Metadata {
+ Namespace(NamespaceMetadata),
+ UdlFile(UdlFile),
+ Func(FnMetadata),
+ Object(ObjectMetadata),
+ CallbackInterface(CallbackInterfaceMetadata),
+ Record(RecordMetadata),
+ Enum(EnumMetadata),
+ Constructor(ConstructorMetadata),
+ Method(MethodMetadata),
+ TraitMethod(TraitMethodMetadata),
+ CustomType(CustomTypeMetadata),
+ UniffiTrait(UniffiTraitMetadata),
+}
+
+impl Metadata {
+ pub fn read(data: &[u8]) -> anyhow::Result<Self> {
+ read_metadata(data)
+ }
+
+ pub(crate) fn module_path(&self) -> &String {
+ match self {
+ Metadata::Namespace(meta) => &meta.crate_name,
+ Metadata::UdlFile(meta) => &meta.module_path,
+ Metadata::Func(meta) => &meta.module_path,
+ Metadata::Constructor(meta) => &meta.module_path,
+ Metadata::Method(meta) => &meta.module_path,
+ Metadata::Record(meta) => &meta.module_path,
+ Metadata::Enum(meta) => &meta.module_path,
+ Metadata::Object(meta) => &meta.module_path,
+ Metadata::CallbackInterface(meta) => &meta.module_path,
+ Metadata::TraitMethod(meta) => &meta.module_path,
+ Metadata::CustomType(meta) => &meta.module_path,
+ Metadata::UniffiTrait(meta) => meta.module_path(),
+ }
+ }
+}
+
+impl From<NamespaceMetadata> for Metadata {
+ fn from(value: NamespaceMetadata) -> Metadata {
+ Self::Namespace(value)
+ }
+}
+
+impl From<UdlFile> for Metadata {
+ fn from(value: UdlFile) -> Metadata {
+ Self::UdlFile(value)
+ }
+}
+
+impl From<FnMetadata> for Metadata {
+ fn from(value: FnMetadata) -> Metadata {
+ Self::Func(value)
+ }
+}
+
+impl From<ConstructorMetadata> for Metadata {
+ fn from(c: ConstructorMetadata) -> Self {
+ Self::Constructor(c)
+ }
+}
+
+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<CallbackInterfaceMetadata> for Metadata {
+ fn from(v: CallbackInterfaceMetadata) -> Self {
+ Self::CallbackInterface(v)
+ }
+}
+
+impl From<TraitMethodMetadata> for Metadata {
+ fn from(v: TraitMethodMetadata) -> Self {
+ Self::TraitMethod(v)
+ }
+}
+
+impl From<CustomTypeMetadata> for Metadata {
+ fn from(v: CustomTypeMetadata) -> Self {
+ Self::CustomType(v)
+ }
+}
+
+impl From<UniffiTraitMetadata> for Metadata {
+ fn from(v: UniffiTraitMetadata) -> Self {
+ Self::UniffiTrait(v)
+ }
+}
diff --git a/src/metadata.rs b/src/metadata.rs
new file mode 100644
index 0000000..66c2c63
--- /dev/null
+++ b/src/metadata.rs
@@ -0,0 +1,88 @@
+/* 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/. */
+
+// Copied from uniffi_core/src/metadata.rs
+// Due to a [Rust bug](https://github.com/rust-lang/rust/issues/113104) we don't want to pull in
+// `uniffi_core`.
+// This is the easy way out of that issue and is a temporary hacky solution.
+
+/// Metadata constants, make sure to keep this in sync with copy in `uniffi_core::metadata`
+pub mod codes {
+ // Top-level metadata item codes
+ pub const FUNC: u8 = 0;
+ pub const METHOD: u8 = 1;
+ pub const RECORD: u8 = 2;
+ pub const ENUM: u8 = 3;
+ pub const INTERFACE: u8 = 4;
+ pub const NAMESPACE: u8 = 6;
+ pub const CONSTRUCTOR: u8 = 7;
+ pub const UDL_FILE: u8 = 8;
+ pub const CALLBACK_INTERFACE: u8 = 9;
+ pub const TRAIT_METHOD: u8 = 10;
+ pub const UNIFFI_TRAIT: u8 = 11;
+ pub const TRAIT_INTERFACE: u8 = 12;
+ pub const CALLBACK_TRAIT_INTERFACE: u8 = 13;
+ //pub const UNKNOWN: u8 = 255;
+
+ // Type codes
+ pub const TYPE_U8: u8 = 0;
+ pub const TYPE_U16: u8 = 1;
+ pub const TYPE_U32: u8 = 2;
+ pub const TYPE_U64: u8 = 3;
+ pub const TYPE_I8: u8 = 4;
+ pub const TYPE_I16: u8 = 5;
+ pub const TYPE_I32: u8 = 6;
+ pub const TYPE_I64: u8 = 7;
+ pub const TYPE_F32: u8 = 8;
+ pub const TYPE_F64: u8 = 9;
+ pub const TYPE_BOOL: u8 = 10;
+ pub const TYPE_STRING: u8 = 11;
+ pub const TYPE_OPTION: u8 = 12;
+ pub const TYPE_RECORD: u8 = 13;
+ pub const TYPE_ENUM: u8 = 14;
+ // 15 no longer used.
+ pub const TYPE_INTERFACE: u8 = 16;
+ pub const TYPE_VEC: u8 = 17;
+ pub const TYPE_HASH_MAP: u8 = 18;
+ pub const TYPE_SYSTEM_TIME: u8 = 19;
+ pub const TYPE_DURATION: u8 = 20;
+ pub const TYPE_CALLBACK_INTERFACE: u8 = 21;
+ pub const TYPE_CUSTOM: u8 = 22;
+ pub const TYPE_RESULT: u8 = 23;
+ pub const TYPE_TRAIT_INTERFACE: u8 = 24;
+ pub const TYPE_CALLBACK_TRAIT_INTERFACE: u8 = 25;
+ pub const TYPE_UNIT: u8 = 255;
+
+ // Literal codes
+ pub const LIT_STR: u8 = 0;
+ pub const LIT_INT: u8 = 1;
+ pub const LIT_FLOAT: u8 = 2;
+ pub const LIT_BOOL: u8 = 3;
+ pub const LIT_NULL: u8 = 4;
+}
+
+// Create a checksum for a MetadataBuffer
+//
+// This is used by the bindings code to verify that the library they link to is the same one
+// that the bindings were generated from.
+pub const fn checksum_metadata(buf: &[u8]) -> u16 {
+ calc_checksum(buf, buf.len())
+}
+
+const fn calc_checksum(bytes: &[u8], size: usize) -> u16 {
+ // Taken from the fnv_hash() function from the FNV crate (https://github.com/servo/rust-fnv/blob/master/lib.rs).
+ // fnv_hash() hasn't been released in a version yet.
+ const INITIAL_STATE: u64 = 0xcbf29ce484222325;
+ const PRIME: u64 = 0x100000001b3;
+
+ let mut hash = INITIAL_STATE;
+ let mut i = 0;
+ while i < size {
+ hash ^= bytes[i] as u64;
+ hash = hash.wrapping_mul(PRIME);
+ i += 1;
+ }
+ // Convert the 64-bit hash to a 16-bit hash by XORing everything together
+ (hash ^ (hash >> 16) ^ (hash >> 32) ^ (hash >> 48)) as u16
+}
diff --git a/src/reader.rs b/src/reader.rs
new file mode 100644
index 0000000..5a09d9d
--- /dev/null
+++ b/src/reader.rs
@@ -0,0 +1,526 @@
+/* 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::metadata::{checksum_metadata, codes};
+use crate::*;
+use anyhow::{bail, ensure, Context, Result};
+
+pub fn read_metadata(data: &[u8]) -> Result<Metadata> {
+ MetadataReader::new(data).read_metadata()
+}
+
+// Read a metadata type, this is pub so that we can test it in the metadata fixture
+pub fn read_metadata_type(data: &[u8]) -> Result<Type> {
+ MetadataReader::new(data).read_type()
+}
+
+/// Helper struct for read_metadata()
+struct MetadataReader<'a> {
+ // This points to the initial data we were passed in
+ initial_data: &'a [u8],
+ // This points to the remaining data to be read
+ buf: &'a [u8],
+}
+
+impl<'a> MetadataReader<'a> {
+ fn new(data: &'a [u8]) -> Self {
+ Self {
+ initial_data: data,
+ buf: data,
+ }
+ }
+
+ // Read a top-level metadata item
+ //
+ // This consumes self because MetadataReader is only intended to read a single item.
+ fn read_metadata(mut self) -> Result<Metadata> {
+ let value = self.read_u8()?;
+ Ok(match value {
+ codes::NAMESPACE => NamespaceMetadata {
+ crate_name: self.read_string()?,
+ name: self.read_string()?,
+ }
+ .into(),
+ codes::UDL_FILE => UdlFile {
+ module_path: self.read_string()?,
+ namespace: self.read_string()?,
+ file_stub: self.read_string()?,
+ }
+ .into(),
+ codes::FUNC => self.read_func()?.into(),
+ codes::CONSTRUCTOR => self.read_constructor()?.into(),
+ codes::METHOD => self.read_method()?.into(),
+ codes::RECORD => self.read_record()?.into(),
+ codes::ENUM => self.read_enum()?.into(),
+ codes::INTERFACE => self.read_object(ObjectImpl::Struct)?.into(),
+ codes::TRAIT_INTERFACE => self.read_object(ObjectImpl::Trait)?.into(),
+ codes::CALLBACK_TRAIT_INTERFACE => self.read_object(ObjectImpl::CallbackTrait)?.into(),
+ codes::CALLBACK_INTERFACE => self.read_callback_interface()?.into(),
+ codes::TRAIT_METHOD => self.read_trait_method()?.into(),
+ codes::UNIFFI_TRAIT => self.read_uniffi_trait()?.into(),
+ _ => bail!("Unexpected metadata code: {value:?}"),
+ })
+ }
+
+ fn read_u8(&mut self) -> Result<u8> {
+ if !self.buf.is_empty() {
+ let value = self.buf[0];
+ self.buf = &self.buf[1..];
+ Ok(value)
+ } else {
+ bail!("Buffer is empty")
+ }
+ }
+
+ fn peek_u8(&mut self) -> Result<u8> {
+ if !self.buf.is_empty() {
+ Ok(self.buf[0])
+ } else {
+ bail!("Buffer is empty")
+ }
+ }
+
+ fn read_u16(&mut self) -> Result<u16> {
+ if self.buf.len() >= 2 {
+ // read the value as little-endian
+ let value = u16::from_le_bytes([self.buf[0], self.buf[1]]);
+ self.buf = &self.buf[2..];
+ Ok(value)
+ } else {
+ bail!("Not enough data left in buffer to read a u16 value");
+ }
+ }
+
+ fn read_u32(&mut self) -> Result<u32> {
+ if self.buf.len() >= 4 {
+ // read the value as little-endian
+ let value = self.buf[0] as u32
+ + ((self.buf[1] as u32) << 8)
+ + ((self.buf[2] as u32) << 16)
+ + ((self.buf[3] as u32) << 24);
+ self.buf = &self.buf[4..];
+ Ok(value)
+ } else {
+ bail!("Not enough data left in buffer to read a u32 value");
+ }
+ }
+
+ fn read_bool(&mut self) -> Result<bool> {
+ Ok(self.read_u8()? == 1)
+ }
+
+ fn read_string(&mut self) -> Result<String> {
+ let size = self.read_u8()? as usize;
+ let slice;
+ (slice, self.buf) = self.buf.split_at(size);
+ String::from_utf8(slice.into()).context("Invalid string data")
+ }
+
+ fn read_long_string(&mut self) -> Result<String> {
+ let size = self.read_u16()? as usize;
+ let slice;
+ (slice, self.buf) = self.buf.split_at(size);
+ String::from_utf8(slice.into()).context("Invalid string data")
+ }
+
+ fn read_optional_long_string(&mut self) -> Result<Option<String>> {
+ Ok(Some(self.read_long_string()?).filter(|str| !str.is_empty()))
+ }
+
+ fn read_type(&mut self) -> Result<Type> {
+ let value = self.read_u8()?;
+ Ok(match value {
+ codes::TYPE_U8 => Type::UInt8,
+ codes::TYPE_I8 => Type::Int8,
+ codes::TYPE_U16 => Type::UInt16,
+ codes::TYPE_I16 => Type::Int16,
+ codes::TYPE_U32 => Type::UInt32,
+ codes::TYPE_I32 => Type::Int32,
+ codes::TYPE_U64 => Type::UInt64,
+ codes::TYPE_I64 => Type::Int64,
+ codes::TYPE_F32 => Type::Float32,
+ codes::TYPE_F64 => Type::Float64,
+ codes::TYPE_BOOL => Type::Boolean,
+ codes::TYPE_STRING => Type::String,
+ codes::TYPE_DURATION => Type::Duration,
+ codes::TYPE_SYSTEM_TIME => Type::Timestamp,
+ codes::TYPE_RECORD => Type::Record {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ },
+ codes::TYPE_ENUM => Type::Enum {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ },
+ codes::TYPE_INTERFACE => Type::Object {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ imp: ObjectImpl::Struct,
+ },
+ codes::TYPE_TRAIT_INTERFACE => Type::Object {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ imp: ObjectImpl::Trait,
+ },
+ codes::TYPE_CALLBACK_TRAIT_INTERFACE => Type::Object {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ imp: ObjectImpl::CallbackTrait,
+ },
+ codes::TYPE_CALLBACK_INTERFACE => Type::CallbackInterface {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ },
+ codes::TYPE_CUSTOM => Type::Custom {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ builtin: Box::new(self.read_type()?),
+ },
+ codes::TYPE_OPTION => Type::Optional {
+ inner_type: Box::new(self.read_type()?),
+ },
+ codes::TYPE_VEC => {
+ let inner_type = self.read_type()?;
+ if inner_type == Type::UInt8 {
+ Type::Bytes
+ } else {
+ Type::Sequence {
+ inner_type: Box::new(inner_type),
+ }
+ }
+ }
+ codes::TYPE_HASH_MAP => Type::Map {
+ key_type: Box::new(self.read_type()?),
+ value_type: Box::new(self.read_type()?),
+ },
+ codes::TYPE_UNIT => bail!("Unexpected TYPE_UNIT"),
+ codes::TYPE_RESULT => bail!("Unexpected TYPE_RESULT"),
+ _ => bail!("Unexpected metadata type code: {value:?}"),
+ })
+ }
+
+ fn read_optional_type(&mut self) -> Result<Option<Type>> {
+ Ok(match self.peek_u8()? {
+ codes::TYPE_UNIT => {
+ _ = self.read_u8();
+ None
+ }
+ _ => Some(self.read_type()?),
+ })
+ }
+
+ fn read_return_type(&mut self) -> Result<(Option<Type>, Option<Type>)> {
+ Ok(match self.peek_u8()? {
+ codes::TYPE_UNIT => {
+ _ = self.read_u8();
+ (None, None)
+ }
+ codes::TYPE_RESULT => {
+ _ = self.read_u8();
+ (self.read_optional_type()?, self.read_optional_type()?)
+ }
+ _ => (Some(self.read_type()?), None),
+ })
+ }
+
+ fn read_func(&mut self) -> Result<FnMetadata> {
+ let module_path = self.read_string()?;
+ let name = self.read_string()?;
+ let is_async = self.read_bool()?;
+ let inputs = self.read_inputs()?;
+ let (return_type, throws) = self.read_return_type()?;
+ let docstring = self.read_optional_long_string()?;
+ Ok(FnMetadata {
+ module_path,
+ name,
+ is_async,
+ inputs,
+ return_type,
+ throws,
+ docstring,
+ checksum: self.calc_checksum(),
+ })
+ }
+
+ fn read_constructor(&mut self) -> Result<ConstructorMetadata> {
+ let module_path = self.read_string()?;
+ let self_name = self.read_string()?;
+ let name = self.read_string()?;
+ let inputs = self.read_inputs()?;
+ let (return_type, throws) = self.read_return_type()?;
+ let docstring = self.read_optional_long_string()?;
+
+ return_type
+ .filter(|t| {
+ matches!(
+ t,
+ Type::Object { name, imp: ObjectImpl::Struct, .. } if name == &self_name
+ )
+ })
+ .context("Constructor return type must be Arc<Self>")?;
+
+ Ok(ConstructorMetadata {
+ module_path,
+ self_name,
+ name,
+ inputs,
+ throws,
+ checksum: self.calc_checksum(),
+ docstring,
+ })
+ }
+
+ fn read_method(&mut self) -> Result<MethodMetadata> {
+ let module_path = self.read_string()?;
+ let self_name = self.read_string()?;
+ let name = self.read_string()?;
+ let is_async = self.read_bool()?;
+ let inputs = self.read_inputs()?;
+ let (return_type, throws) = self.read_return_type()?;
+ let docstring = self.read_optional_long_string()?;
+ Ok(MethodMetadata {
+ module_path,
+ self_name,
+ name,
+ is_async,
+ inputs,
+ return_type,
+ throws,
+ takes_self_by_arc: false, // not emitted by macros
+ checksum: self.calc_checksum(),
+ docstring,
+ })
+ }
+
+ fn read_record(&mut self) -> Result<RecordMetadata> {
+ Ok(RecordMetadata {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ fields: self.read_fields()?,
+ docstring: self.read_optional_long_string()?,
+ })
+ }
+
+ fn read_enum(&mut self) -> Result<EnumMetadata> {
+ let module_path = self.read_string()?;
+ let name = self.read_string()?;
+ let forced_flatness = match self.read_u8()? {
+ 0 => None,
+ 1 => Some(false),
+ 2 => Some(true),
+ _ => unreachable!("invalid flatness"),
+ };
+ let discr_type = if self.read_bool()? {
+ Some(self.read_type()?)
+ } else {
+ None
+ };
+ let variants = if forced_flatness == Some(true) {
+ self.read_flat_variants()?
+ } else {
+ self.read_variants()?
+ };
+
+ Ok(EnumMetadata {
+ module_path,
+ name,
+ forced_flatness,
+ discr_type,
+ variants,
+ non_exhaustive: self.read_bool()?,
+ docstring: self.read_optional_long_string()?,
+ })
+ }
+
+ fn read_object(&mut self, imp: ObjectImpl) -> Result<ObjectMetadata> {
+ Ok(ObjectMetadata {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ imp,
+ docstring: self.read_optional_long_string()?,
+ })
+ }
+
+ fn read_uniffi_trait(&mut self) -> Result<UniffiTraitMetadata> {
+ let code = self.read_u8()?;
+ let mut read_metadata_method = || -> Result<MethodMetadata> {
+ let code = self.read_u8()?;
+ ensure!(code == codes::METHOD, "expected METHOD but read {code}");
+ self.read_method()
+ };
+
+ Ok(match UniffiTraitDiscriminants::from(code)? {
+ UniffiTraitDiscriminants::Debug => UniffiTraitMetadata::Debug {
+ fmt: read_metadata_method()?,
+ },
+ UniffiTraitDiscriminants::Display => UniffiTraitMetadata::Display {
+ fmt: read_metadata_method()?,
+ },
+ UniffiTraitDiscriminants::Eq => UniffiTraitMetadata::Eq {
+ eq: read_metadata_method()?,
+ ne: read_metadata_method()?,
+ },
+ UniffiTraitDiscriminants::Hash => UniffiTraitMetadata::Hash {
+ hash: read_metadata_method()?,
+ },
+ })
+ }
+
+ fn read_callback_interface(&mut self) -> Result<CallbackInterfaceMetadata> {
+ Ok(CallbackInterfaceMetadata {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ docstring: self.read_optional_long_string()?,
+ })
+ }
+
+ fn read_trait_method(&mut self) -> Result<TraitMethodMetadata> {
+ let module_path = self.read_string()?;
+ let trait_name = self.read_string()?;
+ let index = self.read_u32()?;
+ let name = self.read_string()?;
+ let is_async = self.read_bool()?;
+ let inputs = self.read_inputs()?;
+ let (return_type, throws) = self.read_return_type()?;
+ let docstring = self.read_optional_long_string()?;
+ Ok(TraitMethodMetadata {
+ module_path,
+ trait_name,
+ index,
+ name,
+ is_async,
+ inputs,
+ return_type,
+ throws,
+ takes_self_by_arc: false, // not emitted by macros
+ checksum: self.calc_checksum(),
+ docstring,
+ })
+ }
+
+ fn read_fields(&mut self) -> Result<Vec<FieldMetadata>> {
+ let len = self.read_u8()?;
+ (0..len)
+ .map(|_| {
+ let name = self.read_string()?;
+ let ty = self.read_type()?;
+ let default = self.read_default(&name, &ty)?;
+ Ok(FieldMetadata {
+ name,
+ ty,
+ default,
+ docstring: self.read_optional_long_string()?,
+ })
+ })
+ .collect()
+ }
+
+ fn read_variants(&mut self) -> Result<Vec<VariantMetadata>> {
+ let len = self.read_u8()?;
+ (0..len)
+ .map(|_| {
+ Ok(VariantMetadata {
+ name: self.read_string()?,
+ discr: self.read_default("<variant-value>", &Type::UInt64)?,
+ fields: self.read_fields()?,
+ docstring: self.read_optional_long_string()?,
+ })
+ })
+ .collect()
+ }
+
+ fn read_flat_variants(&mut self) -> Result<Vec<VariantMetadata>> {
+ let len = self.read_u8()?;
+ (0..len)
+ .map(|_| {
+ Ok(VariantMetadata {
+ name: self.read_string()?,
+ discr: None,
+ fields: vec![],
+ docstring: self.read_optional_long_string()?,
+ })
+ })
+ .collect()
+ }
+
+ fn read_inputs(&mut self) -> Result<Vec<FnParamMetadata>> {
+ let len = self.read_u8()?;
+ (0..len)
+ .map(|_| {
+ Ok(FnParamMetadata {
+ name: self.read_string()?,
+ ty: self.read_type()?,
+ // not emitted by macros
+ by_ref: false,
+ optional: false,
+ default: None,
+ })
+ })
+ .collect()
+ }
+
+ fn calc_checksum(&self) -> Option<u16> {
+ let bytes_read = self.initial_data.len() - self.buf.len();
+ let metadata_buf = &self.initial_data[..bytes_read];
+ Some(checksum_metadata(metadata_buf))
+ }
+
+ fn read_default(&mut self, name: &str, ty: &Type) -> Result<Option<LiteralMetadata>> {
+ let has_default = self.read_bool()?;
+ if !has_default {
+ return Ok(None);
+ }
+
+ let literal_kind = self.read_u8()?;
+ Ok(Some(match literal_kind {
+ codes::LIT_STR => {
+ ensure!(
+ matches!(ty, Type::String),
+ "field {name} of type {ty:?} can't have a default value of type string"
+ );
+ LiteralMetadata::String(self.read_string()?)
+ }
+ codes::LIT_INT => {
+ let base10_digits = self.read_string()?;
+ macro_rules! parse_int {
+ ($ty:ident, $variant:ident) => {
+ LiteralMetadata::$variant(
+ base10_digits
+ .parse::<$ty>()
+ .with_context(|| format!("parsing default for field {name}"))?
+ .into(),
+ Radix::Decimal,
+ ty.to_owned(),
+ )
+ };
+ }
+
+ match ty {
+ Type::UInt8 => parse_int!(u8, UInt),
+ Type::Int8 => parse_int!(i8, Int),
+ Type::UInt16 => parse_int!(u16, UInt),
+ Type::Int16 => parse_int!(i16, Int),
+ Type::UInt32 => parse_int!(u32, UInt),
+ Type::Int32 => parse_int!(i32, Int),
+ Type::UInt64 => parse_int!(u64, UInt),
+ Type::Int64 => parse_int!(i64, Int),
+ _ => {
+ bail!("field {name} of type {ty:?} can't have a default value of type integer");
+ }
+ }
+ }
+ codes::LIT_FLOAT => match ty {
+ Type::Float32 | Type::Float64 => {
+ LiteralMetadata::Float(self.read_string()?, ty.to_owned())
+ }
+ _ => {
+ bail!("field {name} of type {ty:?} can't have a default value of type float");
+ }
+ },
+ codes::LIT_BOOL => LiteralMetadata::Boolean(self.read_bool()?),
+ codes::LIT_NULL => LiteralMetadata::Null,
+ _ => bail!("Unexpected literal kind code: {literal_kind:?}"),
+ }))
+ }
+}
diff --git a/src/types.rs b/src/types.rs
new file mode 100644
index 0000000..51bf156
--- /dev/null
+++ b/src/types.rs
@@ -0,0 +1,174 @@
+/* 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.
+//!
+//! 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 crate::Checksum;
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Checksum, Ord, PartialOrd)]
+pub enum ObjectImpl {
+ // A single Rust type
+ Struct,
+ // A trait that's can be implemented by Rust types
+ Trait,
+ // A trait + a callback interface -- can be implemented by both Rust and foreign types.
+ CallbackTrait,
+}
+
+impl ObjectImpl {
+ /// Return the fully qualified name which should be used by Rust code for
+ /// an object with the given name.
+ /// Includes `r#`, traits get a leading `dyn`. If we ever supported associated types, then
+ /// this would also include them.
+ pub fn rust_name_for(&self, name: &str) -> String {
+ if self.is_trait_interface() {
+ format!("dyn r#{name}")
+ } else {
+ format!("r#{name}")
+ }
+ }
+
+ pub fn is_trait_interface(&self) -> bool {
+ matches!(self, Self::Trait | Self::CallbackTrait)
+ }
+
+ pub fn has_callback_interface(&self) -> bool {
+ matches!(self, Self::CallbackTrait)
+ }
+}
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Checksum, Ord, PartialOrd)]
+pub enum ExternalKind {
+ Interface,
+ Trait,
+ // Either a record or enum
+ DataClass,
+}
+
+/// 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,
+ Bytes,
+ Timestamp,
+ Duration,
+ Object {
+ // The module path to the object
+ module_path: String,
+ // The name in the "type universe"
+ name: String,
+ // How the object is implemented.
+ imp: ObjectImpl,
+ },
+ // Types defined in the component API, each of which has a string name.
+ Record {
+ module_path: String,
+ name: String,
+ },
+ Enum {
+ module_path: String,
+ name: String,
+ },
+ CallbackInterface {
+ module_path: String,
+ name: String,
+ },
+ // Structurally recursive types.
+ Optional {
+ inner_type: Box<Type>,
+ },
+ Sequence {
+ inner_type: Box<Type>,
+ },
+ Map {
+ key_type: Box<Type>,
+ value_type: Box<Type>,
+ },
+ // An FfiConverter we `use` from an external crate
+ External {
+ module_path: String,
+ name: String,
+ #[checksum_ignore] // The namespace is not known generating scaffolding.
+ namespace: String,
+ kind: ExternalKind,
+ tagged: bool, // does its FfiConverter use <UniFFITag>?
+ },
+ // Custom type on the scaffolding side
+ Custom {
+ module_path: String,
+ name: String,
+ builtin: Box<Type>,
+ },
+}
+
+impl Type {
+ pub fn iter_types(&self) -> TypeIterator<'_> {
+ let nested_types = match self {
+ Type::Optional { inner_type } | Type::Sequence { inner_type } => {
+ inner_type.iter_types()
+ }
+ Type::Map {
+ key_type,
+ value_type,
+ } => Box::new(key_type.iter_types().chain(value_type.iter_types())),
+ _ => Box::new(std::iter::empty()),
+ };
+ Box::new(std::iter::once(self).chain(nested_types))
+ }
+}
+
+// A trait so various things can turn into a type.
+pub trait AsType: core::fmt::Debug {
+ fn as_type(&self) -> Type;
+}
+
+impl AsType for Type {
+ fn as_type(&self) -> Type {
+ self.clone()
+ }
+}
+
+// Needed to handle &&Type and &&&Type values, which we sometimes end up with in the template code
+impl<T, C> AsType for T
+where
+ T: std::ops::Deref<Target = C> + std::fmt::Debug,
+ C: AsType,
+{
+ fn as_type(&self) -> Type {
+ self.deref().as_type()
+ }
+}
+
+/// 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>;