Import cxxbridge-cmd 1.0.62 upstream upstream/1.0.62
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 28 Apr 2025 06:05:31 +0000 (15:05 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 28 Apr 2025 06:05:31 +0000 (15:05 +0900)
54 files changed:
.cargo_vcs_info.json [new file with mode: 0644]
Cargo.lock [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
Cargo.toml.orig [new file with mode: 0644]
LICENSE-APACHE [new file with mode: 0644]
LICENSE-MIT [new file with mode: 0644]
src/app.rs [new file with mode: 0644]
src/gen/block.rs [new file with mode: 0644]
src/gen/builtin.rs [new file with mode: 0644]
src/gen/check.rs [new file with mode: 0644]
src/gen/error.rs [new file with mode: 0644]
src/gen/file.rs [new file with mode: 0644]
src/gen/fs.rs [new file with mode: 0644]
src/gen/ifndef.rs [new file with mode: 0644]
src/gen/include.rs [new file with mode: 0644]
src/gen/include/cxx.h [new file with mode: 0644]
src/gen/mod.rs [new file with mode: 0644]
src/gen/namespace.rs [new file with mode: 0644]
src/gen/nested.rs [new file with mode: 0644]
src/gen/out.rs [new file with mode: 0644]
src/gen/write.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/main.rs [new file with mode: 0644]
src/output.rs [new file with mode: 0644]
src/syntax/atom.rs [new file with mode: 0644]
src/syntax/attrs.rs [new file with mode: 0644]
src/syntax/check.rs [new file with mode: 0644]
src/syntax/derive.rs [new file with mode: 0644]
src/syntax/discriminant.rs [new file with mode: 0644]
src/syntax/doc.rs [new file with mode: 0644]
src/syntax/error.rs [new file with mode: 0644]
src/syntax/file.rs [new file with mode: 0644]
src/syntax/ident.rs [new file with mode: 0644]
src/syntax/impls.rs [new file with mode: 0644]
src/syntax/improper.rs [new file with mode: 0644]
src/syntax/instantiate.rs [new file with mode: 0644]
src/syntax/mangle.rs [new file with mode: 0644]
src/syntax/map.rs [new file with mode: 0644]
src/syntax/mod.rs [new file with mode: 0644]
src/syntax/names.rs [new file with mode: 0644]
src/syntax/namespace.rs [new file with mode: 0644]
src/syntax/parse.rs [new file with mode: 0644]
src/syntax/pod.rs [new file with mode: 0644]
src/syntax/qualified.rs [new file with mode: 0644]
src/syntax/report.rs [new file with mode: 0644]
src/syntax/resolve.rs [new file with mode: 0644]
src/syntax/set.rs [new file with mode: 0644]
src/syntax/symbol.rs [new file with mode: 0644]
src/syntax/tokens.rs [new file with mode: 0644]
src/syntax/toposort.rs [new file with mode: 0644]
src/syntax/trivial.rs [new file with mode: 0644]
src/syntax/types.rs [new file with mode: 0644]
src/syntax/visit.rs [new file with mode: 0644]
src/test.rs [new file with mode: 0644]

diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644 (file)
index 0000000..75aeacf
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "6d7299e7b59b3ea6b2f64acb619c23a4507172e7"
+  },
+  "path_in_vcs": "gen/cmd"
+}
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644 (file)
index 0000000..8df1320
--- /dev/null
@@ -0,0 +1,173 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "clap"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d17bf219fcd37199b9a29e00ba65dfb8cd5b2688b7297ec14ff829c40ac50ca9"
+dependencies = [
+ "bitflags",
+ "indexmap",
+ "os_str_bytes",
+ "strsim",
+ "textwrap",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "cxxbridge-cmd"
+version = "1.0.62"
+dependencies = [
+ "clap",
+ "codespan-reporting",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "indexmap"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "os_str_bytes"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..479eda0
--- /dev/null
@@ -0,0 +1,51 @@
+# 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 = "2018"
+rust-version = "1.54"
+name = "cxxbridge-cmd"
+version = "1.0.62"
+authors = ["David Tolnay <dtolnay@gmail.com>"]
+exclude = ["build.rs"]
+description = "C++ code generator for integrating `cxx` crate into a non-Cargo build."
+homepage = "https://cxx.rs"
+keywords = ["ffi"]
+categories = ["development-tools::ffi"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/dtolnay/cxx"
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[[bin]]
+name = "cxxbridge"
+path = "src/main.rs"
+[dependencies.clap]
+version = "3.0"
+features = ["std", "suggestions"]
+default-features = false
+
+[dependencies.codespan-reporting]
+version = "0.11"
+
+[dependencies.proc-macro2]
+version = "1.0.26"
+features = ["span-locations"]
+default-features = false
+
+[dependencies.quote]
+version = "1.0"
+default-features = false
+
+[dependencies.syn]
+version = "1.0.70"
+features = ["parsing", "printing", "clone-impls", "full"]
+default-features = false
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644 (file)
index 0000000..459cf23
--- /dev/null
@@ -0,0 +1,27 @@
+[package]
+name = "cxxbridge-cmd"
+version = "1.0.62"
+authors = ["David Tolnay <dtolnay@gmail.com>"]
+edition = "2018"
+rust-version = "1.54"
+license = "MIT OR Apache-2.0"
+description = "C++ code generator for integrating `cxx` crate into a non-Cargo build."
+repository = "https://github.com/dtolnay/cxx"
+homepage = "https://cxx.rs"
+exclude = ["build.rs"]
+keywords = ["ffi"]
+categories = ["development-tools::ffi"]
+
+[[bin]]
+name = "cxxbridge"
+path = "src/main.rs"
+
+[dependencies]
+clap = { version = "3.0", default-features = false, features = ["std", "suggestions"] }
+codespan-reporting = "0.11"
+proc-macro2 = { version = "1.0.26", default-features = false, features = ["span-locations"] }
+quote = { version = "1.0", default-features = false }
+syn = { version = "1.0.70", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] }
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644 (file)
index 0000000..16fe87b
--- /dev/null
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644 (file)
index 0000000..31aa793
--- /dev/null
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/src/app.rs b/src/app.rs
new file mode 100644 (file)
index 0000000..f71e841
--- /dev/null
@@ -0,0 +1,168 @@
+#[cfg(test)]
+#[path = "test.rs"]
+mod test;
+
+use super::{Opt, Output};
+use crate::gen::include::Include;
+use crate::syntax::IncludeKind;
+use clap::{App, AppSettings, Arg};
+use std::ffi::OsStr;
+use std::path::PathBuf;
+
+const USAGE: &str = "\
+    cxxbridge <input>.rs              Emit .cc file for bridge to stdout
+    cxxbridge <input>.rs --header     Emit .h file for bridge to stdout
+    cxxbridge --header                Emit \"rust/cxx.h\" header to stdout\
+";
+
+const TEMPLATE: &str = "\
+{bin} {version}
+David Tolnay <dtolnay@gmail.com>
+https://github.com/dtolnay/cxx
+
+USAGE:
+    {usage}
+
+ARGS:
+{positionals}
+
+OPTIONS:
+{options}\
+";
+
+fn app() -> App<'static> {
+    let mut app = App::new("cxxbridge")
+        .override_usage(USAGE)
+        .help_template(TEMPLATE)
+        .setting(AppSettings::NextLineHelp)
+        .arg(arg_input())
+        .arg(arg_cxx_impl_annotations())
+        .arg(arg_header())
+        .arg(arg_include())
+        .arg(arg_output())
+        .mut_arg("help", |a| a.help("Print help information."));
+    if let Some(version) = option_env!("CARGO_PKG_VERSION") {
+        app = app
+            .version(version)
+            .mut_arg("version", |a| a.help("Print version information."));
+    }
+    app
+}
+
+const INPUT: &str = "input";
+const CXX_IMPL_ANNOTATIONS: &str = "cxx-impl-annotations";
+const HEADER: &str = "header";
+const INCLUDE: &str = "include";
+const OUTPUT: &str = "output";
+
+pub(super) fn from_args() -> Opt {
+    let matches = app().get_matches();
+
+    let input = matches.value_of_os(INPUT).map(PathBuf::from);
+    let cxx_impl_annotations = matches.value_of(CXX_IMPL_ANNOTATIONS).map(str::to_owned);
+    let header = matches.is_present(HEADER);
+    let include = matches
+        .values_of(INCLUDE)
+        .unwrap_or_default()
+        .map(|include| {
+            if include.starts_with('<') && include.ends_with('>') {
+                Include {
+                    path: include[1..include.len() - 1].to_owned(),
+                    kind: IncludeKind::Bracketed,
+                }
+            } else {
+                Include {
+                    path: include.to_owned(),
+                    kind: IncludeKind::Quoted,
+                }
+            }
+        })
+        .collect();
+
+    let mut outputs = Vec::new();
+    for path in matches.values_of_os(OUTPUT).unwrap_or_default() {
+        outputs.push(if path == "-" {
+            Output::Stdout
+        } else {
+            Output::File(PathBuf::from(path))
+        });
+    }
+    if outputs.is_empty() {
+        outputs.push(Output::Stdout);
+    }
+
+    Opt {
+        input,
+        header,
+        cxx_impl_annotations,
+        include,
+        outputs,
+    }
+}
+
+fn validate_utf8(arg: &OsStr) -> Result<(), &'static str> {
+    if arg.to_str().is_some() {
+        Ok(())
+    } else {
+        Err("invalid utf-8 sequence")
+    }
+}
+
+fn arg_input() -> Arg<'static> {
+    Arg::new(INPUT)
+        .help("Input Rust source file containing #[cxx::bridge].")
+        .required_unless_present(HEADER)
+        .allow_invalid_utf8(true)
+}
+
+fn arg_cxx_impl_annotations() -> Arg<'static> {
+    const HELP: &str = "\
+Optional annotation for implementations of C++ function wrappers
+that may be exposed to Rust. You may for example need to provide
+__declspec(dllexport) or __attribute__((visibility(\"default\")))
+if Rust code from one shared object or executable depends on
+these C++ functions in another.";
+    Arg::new(CXX_IMPL_ANNOTATIONS)
+        .long(CXX_IMPL_ANNOTATIONS)
+        .takes_value(true)
+        .value_name("annotation")
+        .allow_invalid_utf8(true)
+        .validator_os(validate_utf8)
+        .help(HELP)
+}
+
+fn arg_header() -> Arg<'static> {
+    const HELP: &str = "\
+Emit header with declarations only. Optional if using `-o` with
+a path ending in `.h`.";
+    Arg::new(HEADER).long(HEADER).help(HELP)
+}
+
+fn arg_include() -> Arg<'static> {
+    const HELP: &str = "\
+Any additional headers to #include. The cxxbridge tool does not
+parse or even require the given paths to exist; they simply go
+into the generated C++ code as #include lines.";
+    Arg::new(INCLUDE)
+        .long(INCLUDE)
+        .short('i')
+        .takes_value(true)
+        .multiple_occurrences(true)
+        .allow_invalid_utf8(true)
+        .validator_os(validate_utf8)
+        .help(HELP)
+}
+
+fn arg_output() -> Arg<'static> {
+    const HELP: &str = "\
+Path of file to write as output. Output goes to stdout if -o is
+not specified.";
+    Arg::new(OUTPUT)
+        .long(OUTPUT)
+        .short('o')
+        .takes_value(true)
+        .multiple_occurrences(true)
+        .allow_invalid_utf8(true)
+        .validator_os(validate_utf8)
+        .help(HELP)
+}
diff --git a/src/gen/block.rs b/src/gen/block.rs
new file mode 100644 (file)
index 0000000..96a9a6e
--- /dev/null
@@ -0,0 +1,45 @@
+use proc_macro2::Ident;
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum Block<'a> {
+    AnonymousNamespace,
+    Namespace(&'static str),
+    UserDefinedNamespace(&'a Ident),
+    InlineNamespace(&'static str),
+    ExternC,
+}
+
+impl<'a> Block<'a> {
+    pub fn write_begin(self, out: &mut String) {
+        if let Block::InlineNamespace(_) = self {
+            out.push_str("inline ");
+        }
+        self.write_common(out);
+        out.push_str(" {\n");
+    }
+
+    pub fn write_end(self, out: &mut String) {
+        out.push_str("} // ");
+        self.write_common(out);
+        out.push('\n');
+    }
+
+    fn write_common(self, out: &mut String) {
+        match self {
+            Block::AnonymousNamespace => out.push_str("namespace"),
+            Block::Namespace(name) => {
+                out.push_str("namespace ");
+                out.push_str(name);
+            }
+            Block::UserDefinedNamespace(name) => {
+                out.push_str("namespace ");
+                out.push_str(&name.to_string());
+            }
+            Block::InlineNamespace(name) => {
+                out.push_str("namespace ");
+                out.push_str(name);
+            }
+            Block::ExternC => out.push_str("extern \"C\""),
+        }
+    }
+}
diff --git a/src/gen/builtin.rs b/src/gen/builtin.rs
new file mode 100644 (file)
index 0000000..7ac9209
--- /dev/null
@@ -0,0 +1,397 @@
+use crate::gen::block::Block;
+use crate::gen::ifndef;
+use crate::gen::out::{Content, OutFile};
+
+#[derive(Default, PartialEq)]
+pub struct Builtins<'a> {
+    pub panic: bool,
+    pub rust_string: bool,
+    pub rust_str: bool,
+    pub rust_slice: bool,
+    pub rust_box: bool,
+    pub rust_vec: bool,
+    pub rust_fn: bool,
+    pub rust_isize: bool,
+    pub opaque: bool,
+    pub layout: bool,
+    pub unsafe_bitcopy: bool,
+    pub unsafe_bitcopy_t: bool,
+    pub rust_error: bool,
+    pub manually_drop: bool,
+    pub maybe_uninit: bool,
+    pub trycatch: bool,
+    pub ptr_len: bool,
+    pub repr_fat: bool,
+    pub rust_str_new_unchecked: bool,
+    pub rust_str_repr: bool,
+    pub rust_slice_new: bool,
+    pub rust_slice_repr: bool,
+    pub exception: bool,
+    pub relocatable: bool,
+    pub friend_impl: bool,
+    pub is_complete: bool,
+    pub destroy: bool,
+    pub deleter_if: bool,
+    pub content: Content<'a>,
+}
+
+impl<'a> Builtins<'a> {
+    pub fn new() -> Self {
+        Builtins::default()
+    }
+}
+
+pub(super) fn write(out: &mut OutFile) {
+    if out.builtin == Default::default() {
+        return;
+    }
+
+    let include = &mut out.include;
+    let builtin = &mut out.builtin;
+    let out = &mut builtin.content;
+
+    if builtin.rust_string {
+        include.array = true;
+        include.cstdint = true;
+        include.string = true;
+    }
+
+    if builtin.rust_str {
+        include.array = true;
+        include.cstdint = true;
+        include.string = true;
+        builtin.friend_impl = true;
+    }
+
+    if builtin.rust_vec {
+        include.algorithm = true;
+        include.array = true;
+        include.cassert = true;
+        include.cstddef = true;
+        include.cstdint = true;
+        include.initializer_list = true;
+        include.iterator = true;
+        include.new = true;
+        include.stdexcept = true;
+        include.type_traits = true;
+        include.utility = true;
+        builtin.panic = true;
+        builtin.rust_slice = true;
+        builtin.unsafe_bitcopy_t = true;
+    }
+
+    if builtin.rust_slice {
+        include.array = true;
+        include.cassert = true;
+        include.cstddef = true;
+        include.cstdint = true;
+        include.iterator = true;
+        include.stdexcept = true;
+        include.type_traits = true;
+        builtin.friend_impl = true;
+        builtin.layout = true;
+        builtin.panic = true;
+    }
+
+    if builtin.rust_box {
+        include.new = true;
+        include.type_traits = true;
+        include.utility = true;
+    }
+
+    if builtin.rust_fn {
+        include.utility = true;
+    }
+
+    if builtin.rust_error {
+        include.exception = true;
+        builtin.friend_impl = true;
+    }
+
+    if builtin.rust_isize {
+        include.basetsd = true;
+        include.sys_types = true;
+    }
+
+    if builtin.relocatable {
+        include.type_traits = true;
+    }
+
+    if builtin.layout {
+        include.type_traits = true;
+        include.cstddef = true;
+        builtin.is_complete = true;
+    }
+
+    if builtin.is_complete {
+        include.cstddef = true;
+        include.type_traits = true;
+    }
+
+    if builtin.unsafe_bitcopy {
+        builtin.unsafe_bitcopy_t = true;
+    }
+
+    out.begin_block(Block::Namespace("rust"));
+    out.begin_block(Block::InlineNamespace("cxxbridge1"));
+
+    let cxx_header = include.has_cxx_header();
+    if !cxx_header {
+        writeln!(out, "// #include \"rust/cxx.h\"");
+
+        ifndef::write(out, builtin.panic, "CXXBRIDGE1_PANIC");
+
+        if builtin.rust_string {
+            out.next_section();
+            writeln!(out, "struct unsafe_bitcopy_t;");
+        }
+
+        if builtin.friend_impl {
+            out.begin_block(Block::AnonymousNamespace);
+            writeln!(out, "template <typename T>");
+            writeln!(out, "class impl;");
+            out.end_block(Block::AnonymousNamespace);
+        }
+
+        out.next_section();
+        if builtin.rust_str && !builtin.rust_string {
+            writeln!(out, "class String;");
+        }
+        if builtin.layout && !builtin.opaque {
+            writeln!(out, "class Opaque;");
+        }
+
+        if builtin.rust_slice {
+            out.next_section();
+            writeln!(out, "template <typename T>");
+            writeln!(out, "::std::size_t size_of();");
+            writeln!(out, "template <typename T>");
+            writeln!(out, "::std::size_t align_of();");
+        }
+
+        ifndef::write(out, builtin.rust_string, "CXXBRIDGE1_RUST_STRING");
+        ifndef::write(out, builtin.rust_str, "CXXBRIDGE1_RUST_STR");
+        ifndef::write(out, builtin.rust_slice, "CXXBRIDGE1_RUST_SLICE");
+        ifndef::write(out, builtin.rust_box, "CXXBRIDGE1_RUST_BOX");
+        ifndef::write(out, builtin.unsafe_bitcopy_t, "CXXBRIDGE1_RUST_BITCOPY_T");
+        ifndef::write(out, builtin.unsafe_bitcopy, "CXXBRIDGE1_RUST_BITCOPY");
+        ifndef::write(out, builtin.rust_vec, "CXXBRIDGE1_RUST_VEC");
+        ifndef::write(out, builtin.rust_fn, "CXXBRIDGE1_RUST_FN");
+        ifndef::write(out, builtin.rust_error, "CXXBRIDGE1_RUST_ERROR");
+        ifndef::write(out, builtin.rust_isize, "CXXBRIDGE1_RUST_ISIZE");
+        ifndef::write(out, builtin.opaque, "CXXBRIDGE1_RUST_OPAQUE");
+        ifndef::write(out, builtin.is_complete, "CXXBRIDGE1_IS_COMPLETE");
+        ifndef::write(out, builtin.layout, "CXXBRIDGE1_LAYOUT");
+        ifndef::write(out, builtin.relocatable, "CXXBRIDGE1_RELOCATABLE");
+    }
+
+    if builtin.rust_str_new_unchecked {
+        out.next_section();
+        writeln!(out, "class Str::uninit {{}};");
+        writeln!(out, "inline Str::Str(uninit) noexcept {{}}");
+    }
+
+    if builtin.rust_slice_new {
+        out.next_section();
+        writeln!(out, "template <typename T>");
+        writeln!(out, "class Slice<T>::uninit {{}};");
+        writeln!(out, "template <typename T>");
+        writeln!(out, "inline Slice<T>::Slice(uninit) noexcept {{}}");
+    }
+
+    out.begin_block(Block::Namespace("detail"));
+
+    if builtin.maybe_uninit {
+        include.cstddef = true;
+        include.new = true;
+        out.next_section();
+        writeln!(out, "template <typename T, typename = void *>");
+        writeln!(out, "struct operator_new {{");
+        writeln!(
+            out,
+            "  void *operator()(::std::size_t sz) {{ return ::operator new(sz); }}",
+        );
+        writeln!(out, "}};");
+        out.next_section();
+        writeln!(out, "template <typename T>");
+        writeln!(
+            out,
+            "struct operator_new<T, decltype(T::operator new(sizeof(T)))> {{",
+        );
+        writeln!(
+            out,
+            "  void *operator()(::std::size_t sz) {{ return T::operator new(sz); }}",
+        );
+        writeln!(out, "}};");
+    }
+
+    out.end_block(Block::Namespace("detail"));
+
+    if builtin.manually_drop {
+        out.next_section();
+        include.utility = true;
+        writeln!(out, "template <typename T>");
+        writeln!(out, "union ManuallyDrop {{");
+        writeln!(out, "  T value;");
+        writeln!(
+            out,
+            "  ManuallyDrop(T &&value) : value(::std::move(value)) {{}}",
+        );
+        writeln!(out, "  ~ManuallyDrop() {{}}");
+        writeln!(out, "}};");
+    }
+
+    if builtin.maybe_uninit {
+        include.cstddef = true;
+        out.next_section();
+        writeln!(out, "template <typename T>");
+        writeln!(out, "union MaybeUninit {{");
+        writeln!(out, "  T value;");
+        writeln!(
+            out,
+            "  void *operator new(::std::size_t sz) {{ return detail::operator_new<T>{{}}(sz); }}",
+        );
+        writeln!(out, "  MaybeUninit() {{}}");
+        writeln!(out, "  ~MaybeUninit() {{}}");
+        writeln!(out, "}};");
+    }
+
+    out.begin_block(Block::AnonymousNamespace);
+
+    if builtin.repr_fat {
+        include.array = true;
+        include.cstdint = true;
+        out.next_section();
+        out.begin_block(Block::Namespace("repr"));
+        writeln!(out, "using Fat = ::std::array<::std::uintptr_t, 2>;");
+        out.end_block(Block::Namespace("repr"));
+    }
+
+    if builtin.ptr_len {
+        include.cstddef = true;
+        out.next_section();
+        out.begin_block(Block::Namespace("repr"));
+        writeln!(out, "struct PtrLen final {{");
+        writeln!(out, "  void *ptr;");
+        writeln!(out, "  ::std::size_t len;");
+        writeln!(out, "}};");
+        out.end_block(Block::Namespace("repr"));
+    }
+
+    if builtin.rust_str_new_unchecked || builtin.rust_str_repr {
+        out.next_section();
+        writeln!(out, "template <>");
+        writeln!(out, "class impl<Str> final {{");
+        writeln!(out, "public:");
+        if builtin.rust_str_new_unchecked {
+            writeln!(
+                out,
+                "  static Str new_unchecked(repr::Fat repr) noexcept {{",
+            );
+            writeln!(out, "    Str str = Str::uninit{{}};");
+            writeln!(out, "    str.repr = repr;");
+            writeln!(out, "    return str;");
+            writeln!(out, "  }}");
+        }
+        if builtin.rust_str_repr {
+            writeln!(out, "  static repr::Fat repr(Str str) noexcept {{");
+            writeln!(out, "    return str.repr;");
+            writeln!(out, "  }}");
+        }
+        writeln!(out, "}};");
+    }
+
+    if builtin.rust_slice_new || builtin.rust_slice_repr {
+        out.next_section();
+        writeln!(out, "template <typename T>");
+        writeln!(out, "class impl<Slice<T>> final {{");
+        writeln!(out, "public:");
+        if builtin.rust_slice_new {
+            writeln!(out, "  static Slice<T> slice(repr::Fat repr) noexcept {{");
+            writeln!(out, "    Slice<T> slice = typename Slice<T>::uninit{{}};");
+            writeln!(out, "    slice.repr = repr;");
+            writeln!(out, "    return slice;");
+            writeln!(out, "  }}");
+        }
+        if builtin.rust_slice_repr {
+            writeln!(out, "  static repr::Fat repr(Slice<T> slice) noexcept {{");
+            writeln!(out, "    return slice.repr;");
+            writeln!(out, "  }}");
+        }
+        writeln!(out, "}};");
+    }
+
+    if builtin.rust_error {
+        out.next_section();
+        writeln!(out, "template <>");
+        writeln!(out, "class impl<Error> final {{");
+        writeln!(out, "public:");
+        writeln!(out, "  static Error error(repr::PtrLen repr) noexcept {{");
+        writeln!(out, "    Error error;");
+        writeln!(out, "    error.msg = static_cast<const char *>(repr.ptr);");
+        writeln!(out, "    error.len = repr.len;");
+        writeln!(out, "    return error;");
+        writeln!(out, "  }}");
+        writeln!(out, "}};");
+    }
+
+    if builtin.destroy {
+        out.next_section();
+        writeln!(out, "template <typename T>");
+        writeln!(out, "void destroy(T *ptr) {{");
+        writeln!(out, "  ptr->~T();");
+        writeln!(out, "}}");
+    }
+
+    if builtin.deleter_if {
+        out.next_section();
+        writeln!(out, "template <bool> struct deleter_if {{");
+        writeln!(out, "  template <typename T> void operator()(T *) {{}}");
+        writeln!(out, "}};");
+        out.next_section();
+        writeln!(out, "template <> struct deleter_if<true> {{");
+        writeln!(
+            out,
+            "  template <typename T> void operator()(T *ptr) {{ ptr->~T(); }}",
+        );
+        writeln!(out, "}};");
+    }
+
+    out.end_block(Block::AnonymousNamespace);
+    out.end_block(Block::InlineNamespace("cxxbridge1"));
+
+    if builtin.trycatch {
+        out.begin_block(Block::Namespace("behavior"));
+        include.exception = true;
+        include.type_traits = true;
+        include.utility = true;
+        writeln!(out, "class missing {{}};");
+        writeln!(out, "missing trycatch(...);");
+        writeln!(out);
+        writeln!(out, "template <typename Try, typename Fail>");
+        writeln!(out, "static typename ::std::enable_if<");
+        writeln!(
+            out,
+            "    ::std::is_same<decltype(trycatch(::std::declval<Try>(), ::std::declval<Fail>())),",
+        );
+        writeln!(out, "                 missing>::value>::type");
+        writeln!(out, "trycatch(Try &&func, Fail &&fail) noexcept try {{");
+        writeln!(out, "  func();");
+        writeln!(out, "}} catch (const ::std::exception &e) {{");
+        writeln!(out, "  fail(e.what());");
+        writeln!(out, "}}");
+        out.end_block(Block::Namespace("behavior"));
+    }
+
+    out.end_block(Block::Namespace("rust"));
+
+    if builtin.exception {
+        include.cstddef = true;
+        out.begin_block(Block::ExternC);
+        writeln!(
+            out,
+            "const char *cxxbridge1$exception(const char *, ::std::size_t);",
+        );
+        out.end_block(Block::ExternC);
+    }
+}
diff --git a/src/gen/check.rs b/src/gen/check.rs
new file mode 100644 (file)
index 0000000..15add20
--- /dev/null
@@ -0,0 +1,27 @@
+use crate::gen::Opt;
+use crate::syntax::report::Errors;
+use crate::syntax::{error, Api};
+use quote::{quote, quote_spanned};
+use std::path::{Component, Path};
+
+pub(super) use crate::syntax::check::{typecheck, Generator};
+
+pub(super) fn precheck(cx: &mut Errors, apis: &[Api], opt: &Opt) {
+    if !opt.allow_dot_includes {
+        check_dot_includes(cx, apis);
+    }
+}
+
+fn check_dot_includes(cx: &mut Errors, apis: &[Api]) {
+    for api in apis {
+        if let Api::Include(include) = api {
+            let first_component = Path::new(&include.path).components().next();
+            if let Some(Component::CurDir) | Some(Component::ParentDir) = first_component {
+                let begin = quote_spanned!(include.begin_span=> .);
+                let end = quote_spanned!(include.end_span=> .);
+                let span = quote!(#begin #end);
+                cx.error(span, error::DOT_INCLUDE.msg);
+            }
+        }
+    }
+}
diff --git a/src/gen/error.rs b/src/gen/error.rs
new file mode 100644 (file)
index 0000000..3672e26
--- /dev/null
@@ -0,0 +1,175 @@
+use crate::gen::fs;
+use crate::syntax;
+use codespan_reporting::diagnostic::{Diagnostic, Label};
+use codespan_reporting::files::SimpleFiles;
+use codespan_reporting::term::termcolor::{ColorChoice, StandardStream, WriteColor};
+use codespan_reporting::term::{self, Config};
+use std::borrow::Cow;
+use std::error::Error as StdError;
+use std::fmt::{self, Display};
+use std::io::{self, Write};
+use std::ops::Range;
+use std::path::{Path, PathBuf};
+use std::process;
+use std::str::Utf8Error;
+
+pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
+
+#[derive(Debug)]
+pub(crate) enum Error {
+    NoBridgeMod,
+    Fs(fs::Error),
+    Utf8(PathBuf, Utf8Error),
+    Syn(syn::Error),
+}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Error::NoBridgeMod => write!(f, "no #[cxx::bridge] module found"),
+            Error::Fs(err) => err.fmt(f),
+            Error::Utf8(path, _) => write!(f, "Failed to read file `{}`", path.display()),
+            Error::Syn(err) => err.fmt(f),
+        }
+    }
+}
+
+impl StdError for Error {
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {
+        match self {
+            Error::Fs(err) => err.source(),
+            Error::Utf8(_, err) => Some(err),
+            Error::Syn(err) => err.source(),
+            _ => None,
+        }
+    }
+}
+
+impl From<fs::Error> for Error {
+    fn from(err: fs::Error) -> Self {
+        Error::Fs(err)
+    }
+}
+
+impl From<syn::Error> for Error {
+    fn from(err: syn::Error) -> Self {
+        Error::Syn(err)
+    }
+}
+
+pub(super) fn format_err(path: &Path, source: &str, error: Error) -> ! {
+    match error {
+        Error::Syn(syn_error) => {
+            let syn_error = sort_syn_errors(syn_error);
+            let writer = StandardStream::stderr(ColorChoice::Auto);
+            let ref mut stderr = writer.lock();
+            for error in syn_error {
+                let _ = writeln!(stderr);
+                display_syn_error(stderr, path, source, error);
+            }
+        }
+        Error::NoBridgeMod => {
+            let _ = writeln!(
+                io::stderr(),
+                "cxxbridge: no #[cxx::bridge] module found in {}",
+                path.display(),
+            );
+        }
+        _ => {
+            let _ = writeln!(io::stderr(), "cxxbridge: {}", report(error));
+        }
+    }
+    process::exit(1);
+}
+
+pub(crate) fn report(error: impl StdError) -> impl Display {
+    struct Report<E>(E);
+
+    impl<E: StdError> Display for Report<E> {
+        fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+            write!(formatter, "{}", self.0)?;
+            let mut error: &dyn StdError = &self.0;
+
+            while let Some(cause) = error.source() {
+                write!(formatter, "\n\nCaused by:\n    {}", cause)?;
+                error = cause;
+            }
+
+            Ok(())
+        }
+    }
+
+    Report(error)
+}
+
+fn sort_syn_errors(error: syn::Error) -> Vec<syn::Error> {
+    let mut errors: Vec<_> = error.into_iter().collect();
+    errors.sort_by_key(|e| {
+        let start = e.span().start();
+        (start.line, start.column)
+    });
+    errors
+}
+
+fn display_syn_error(stderr: &mut dyn WriteColor, path: &Path, source: &str, error: syn::Error) {
+    let span = error.span();
+    let start = span.start();
+    let end = span.end();
+
+    let mut start_offset = 0;
+    for _ in 1..start.line {
+        start_offset += source[start_offset..].find('\n').unwrap() + 1;
+    }
+    let start_column = source[start_offset..]
+        .chars()
+        .take(start.column)
+        .map(char::len_utf8)
+        .sum::<usize>();
+    start_offset += start_column;
+
+    let mut end_offset = start_offset;
+    if start.line == end.line {
+        end_offset -= start_column;
+    } else {
+        for _ in 0..end.line - start.line {
+            end_offset += source[end_offset..].find('\n').unwrap() + 1;
+        }
+    }
+    end_offset += source[end_offset..]
+        .chars()
+        .take(end.column)
+        .map(char::len_utf8)
+        .sum::<usize>();
+
+    let mut path = path.to_string_lossy();
+    if path == "-" {
+        path = Cow::Borrowed(if cfg!(unix) { "/dev/stdin" } else { "stdin" });
+    }
+
+    let mut files = SimpleFiles::new();
+    let file = files.add(path, source);
+
+    let diagnostic = diagnose(file, start_offset..end_offset, error);
+
+    let config = Config::default();
+    let _ = term::emit(stderr, &config, &files, &diagnostic);
+}
+
+fn diagnose(file: usize, range: Range<usize>, error: syn::Error) -> Diagnostic<usize> {
+    let message = error.to_string();
+    let info = syntax::error::ERRORS
+        .iter()
+        .find(|e| message.contains(e.msg));
+    let mut diagnostic = Diagnostic::error().with_message(&message);
+    let mut label = Label::primary(file, range);
+    if let Some(info) = info {
+        label.message = info.label.map_or(message, str::to_owned);
+        diagnostic.labels.push(label);
+        diagnostic.notes.extend(info.note.map(str::to_owned));
+    } else {
+        label.message = message;
+        diagnostic.labels.push(label);
+    }
+    diagnostic.code = Some("cxxbridge".to_owned());
+    diagnostic
+}
diff --git a/src/gen/file.rs b/src/gen/file.rs
new file mode 100644 (file)
index 0000000..46616fb
--- /dev/null
@@ -0,0 +1,72 @@
+use crate::syntax::file::Module;
+use crate::syntax::namespace::Namespace;
+use syn::parse::discouraged::Speculative;
+use syn::parse::{Error, Parse, ParseStream, Result};
+use syn::{braced, Attribute, Ident, Item, Token, Visibility};
+
+pub struct File {
+    pub modules: Vec<Module>,
+}
+
+impl Parse for File {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let mut modules = Vec::new();
+        input.call(Attribute::parse_inner)?;
+        parse(input, &mut modules)?;
+        Ok(File { modules })
+    }
+}
+
+fn parse(input: ParseStream, modules: &mut Vec<Module>) -> Result<()> {
+    while !input.is_empty() {
+        let mut cxx_bridge = false;
+        let mut namespace = Namespace::ROOT;
+        let mut attrs = input.call(Attribute::parse_outer)?;
+        for attr in &attrs {
+            let path = &attr.path.segments;
+            if path.len() == 2 && path[0].ident == "cxx" && path[1].ident == "bridge" {
+                cxx_bridge = true;
+                namespace = parse_args(attr)?;
+                break;
+            }
+        }
+
+        let ahead = input.fork();
+        ahead.parse::<Visibility>()?;
+        ahead.parse::<Option<Token![unsafe]>>()?;
+        if !ahead.peek(Token![mod]) {
+            let item: Item = input.parse()?;
+            if cxx_bridge {
+                return Err(Error::new_spanned(item, "expected a module"));
+            }
+            continue;
+        }
+
+        if cxx_bridge {
+            let mut module: Module = input.parse()?;
+            module.namespace = namespace;
+            attrs.extend(module.attrs);
+            module.attrs = attrs;
+            modules.push(module);
+        } else {
+            input.advance_to(&ahead);
+            input.parse::<Token![mod]>()?;
+            input.parse::<Ident>()?;
+            let semi: Option<Token![;]> = input.parse()?;
+            if semi.is_none() {
+                let content;
+                braced!(content in input);
+                parse(&content, modules)?;
+            }
+        }
+    }
+    Ok(())
+}
+
+fn parse_args(attr: &Attribute) -> Result<Namespace> {
+    if attr.tokens.is_empty() {
+        Ok(Namespace::ROOT)
+    } else {
+        attr.parse_args_with(Namespace::parse_bridge_attr_namespace)
+    }
+}
diff --git a/src/gen/fs.rs b/src/gen/fs.rs
new file mode 100644 (file)
index 0000000..bfda582
--- /dev/null
@@ -0,0 +1,165 @@
+#![allow(dead_code)]
+
+use std::error::Error as StdError;
+use std::fmt::{self, Display};
+use std::io::{self, Read};
+use std::path::{Path, PathBuf};
+
+pub(crate) type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug)]
+pub(crate) struct Error {
+    source: Option<io::Error>,
+    message: String,
+}
+
+impl Error {
+    pub fn kind(&self) -> io::ErrorKind {
+        match &self.source {
+            Some(io_error) => io_error.kind(),
+            None => io::ErrorKind::Other,
+        }
+    }
+}
+
+impl Display for Error {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str(&self.message)
+    }
+}
+
+impl StdError for Error {
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {
+        let source = self.source.as_ref()?;
+        Some(source)
+    }
+}
+
+macro_rules! err {
+    ($io_error:expr, $fmt:expr $(, $path:expr)* $(,)?) => {
+        Err(Error {
+            source: Option::from($io_error),
+            message: format!($fmt $(, $path.display())*),
+        })
+    }
+}
+
+pub(crate) fn copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<u64> {
+    let from = from.as_ref();
+    let to = to.as_ref();
+    match std::fs::copy(from, to) {
+        Ok(n) => Ok(n),
+        Err(e) => err!(e, "Failed to copy `{}` -> `{}`", from, to),
+    }
+}
+
+pub(crate) fn create_dir_all(path: impl AsRef<Path>) -> Result<()> {
+    let path = path.as_ref();
+    match std::fs::create_dir_all(path) {
+        Ok(()) => Ok(()),
+        Err(e) => err!(e, "Failed to create directory `{}`", path),
+    }
+}
+
+pub(crate) fn current_dir() -> Result<PathBuf> {
+    match std::env::current_dir() {
+        Ok(dir) => Ok(dir),
+        Err(e) => err!(e, "Failed to determine current directory"),
+    }
+}
+
+pub(crate) fn read(path: impl AsRef<Path>) -> Result<Vec<u8>> {
+    let path = path.as_ref();
+    match std::fs::read(path) {
+        Ok(string) => Ok(string),
+        Err(e) => err!(e, "Failed to read file `{}`", path),
+    }
+}
+
+pub(crate) fn read_stdin() -> Result<Vec<u8>> {
+    let mut bytes = Vec::new();
+    match io::stdin().read_to_end(&mut bytes) {
+        Ok(_len) => Ok(bytes),
+        Err(e) => err!(e, "Failed to read input from stdin"),
+    }
+}
+
+pub(crate) fn remove_file(path: impl AsRef<Path>) -> Result<()> {
+    let path = path.as_ref();
+    match std::fs::remove_file(path) {
+        Ok(()) => Ok(()),
+        Err(e) => err!(e, "Failed to remove file `{}`", path),
+    }
+}
+
+pub(crate) fn remove_dir(path: impl AsRef<Path>) -> Result<()> {
+    let path = path.as_ref();
+    match std::fs::remove_dir(path) {
+        Ok(()) => Ok(()),
+        Err(e) => err!(e, "Failed to remove directory `{}`", path),
+    }
+}
+
+fn symlink<'a>(
+    original: &'a Path,
+    link: &'a Path,
+    fun: fn(&'a Path, &'a Path) -> io::Result<()>,
+) -> Result<()> {
+    match fun(original, link) {
+        Ok(()) => Ok(()),
+        Err(e) => err!(
+            e,
+            "Failed to create symlink `{}` pointing to `{}`",
+            link,
+            original,
+        ),
+    }
+}
+
+pub(crate) fn symlink_fail(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> {
+    err!(
+        None,
+        "Failed to create symlink `{}` pointing to `{}`",
+        link.as_ref(),
+        original.as_ref(),
+    )
+}
+
+#[cfg(unix)]
+#[allow(unused_imports)]
+pub(crate) use self::symlink_file as symlink_dir;
+
+#[cfg(not(any(unix, windows)))]
+#[allow(unused_imports)]
+pub(crate) use self::symlink_fail as symlink_dir;
+
+#[cfg(unix)]
+pub(crate) fn symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> {
+    symlink(original.as_ref(), link.as_ref(), std::os::unix::fs::symlink)
+}
+
+#[cfg(windows)]
+pub(crate) fn symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> {
+    symlink(
+        original.as_ref(),
+        link.as_ref(),
+        std::os::windows::fs::symlink_file,
+    )
+}
+
+#[cfg(windows)]
+pub(crate) fn symlink_dir(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> {
+    symlink(
+        original.as_ref(),
+        link.as_ref(),
+        std::os::windows::fs::symlink_dir,
+    )
+}
+
+pub(crate) fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
+    let path = path.as_ref();
+    match std::fs::write(path, contents) {
+        Ok(()) => Ok(()),
+        Err(e) => err!(e, "Failed to write file `{}`", path),
+    }
+}
diff --git a/src/gen/ifndef.rs b/src/gen/ifndef.rs
new file mode 100644 (file)
index 0000000..b436266
--- /dev/null
@@ -0,0 +1,46 @@
+use crate::gen::include::HEADER;
+use crate::gen::out::Content;
+
+pub(super) fn write(out: &mut Content, needed: bool, guard: &str) {
+    let ifndef = format!("#ifndef {}", guard);
+    let define = format!("#define {}", guard);
+    let endif = format!("#endif // {}", guard);
+
+    let mut offset = 0;
+    loop {
+        let begin = find_line(offset, &ifndef);
+        let end = find_line(offset, &endif);
+        if let (Some(begin), Some(end)) = (begin, end) {
+            if !needed {
+                return;
+            }
+            out.next_section();
+            if offset == 0 {
+                writeln!(out, "{}", ifndef);
+                writeln!(out, "{}", define);
+            }
+            for line in HEADER[begin + ifndef.len()..end].trim().lines() {
+                if line != define && !line.trim_start().starts_with("//") {
+                    writeln!(out, "{}", line);
+                }
+            }
+            offset = end + endif.len();
+        } else if offset == 0 {
+            panic!("not found in cxx.h header: {}", guard)
+        } else {
+            writeln!(out, "{}", endif);
+            return;
+        }
+    }
+}
+
+fn find_line(mut offset: usize, line: &str) -> Option<usize> {
+    loop {
+        offset += HEADER[offset..].find(line)?;
+        let rest = &HEADER[offset + line.len()..];
+        if rest.starts_with('\n') || rest.starts_with('\r') {
+            return Some(offset);
+        }
+        offset += line.len();
+    }
+}
diff --git a/src/gen/include.rs b/src/gen/include.rs
new file mode 100644 (file)
index 0000000..62c9232
--- /dev/null
@@ -0,0 +1,204 @@
+use crate::gen::out::{Content, OutFile};
+use crate::syntax::{self, IncludeKind};
+use std::ops::{Deref, DerefMut};
+
+/// The complete contents of the "rust/cxx.h" header.
+pub static HEADER: &str = include_str!("include/cxx.h");
+
+/// A header to #include.
+///
+/// The cxxbridge tool does not parse or even require the given paths to exist;
+/// they simply go into the generated C++ code as #include lines.
+#[derive(Clone, PartialEq, Debug)]
+pub struct Include {
+    /// The header's path, not including the enclosing quotation marks or angle
+    /// brackets.
+    pub path: String,
+    /// Whether to emit `#include "path"` or `#include <path>`.
+    pub kind: IncludeKind,
+}
+
+#[derive(Default, PartialEq)]
+pub struct Includes<'a> {
+    pub custom: Vec<Include>,
+    pub algorithm: bool,
+    pub array: bool,
+    pub cassert: bool,
+    pub cstddef: bool,
+    pub cstdint: bool,
+    pub cstring: bool,
+    pub exception: bool,
+    pub functional: bool,
+    pub initializer_list: bool,
+    pub iterator: bool,
+    pub memory: bool,
+    pub new: bool,
+    pub stdexcept: bool,
+    pub string: bool,
+    pub type_traits: bool,
+    pub utility: bool,
+    pub vector: bool,
+    pub basetsd: bool,
+    pub sys_types: bool,
+    pub content: Content<'a>,
+}
+
+impl<'a> Includes<'a> {
+    pub fn new() -> Self {
+        Includes::default()
+    }
+
+    pub fn insert(&mut self, include: impl Into<Include>) {
+        self.custom.push(include.into());
+    }
+
+    pub fn has_cxx_header(&self) -> bool {
+        self.custom
+            .iter()
+            .any(|header| header.path == "rust/cxx.h" || header.path == "rust\\cxx.h")
+    }
+}
+
+pub(super) fn write(out: &mut OutFile) {
+    let header = out.header;
+    let include = &mut out.include;
+    let cxx_header = include.has_cxx_header();
+    let out = &mut include.content;
+
+    if header {
+        writeln!(out, "#pragma once");
+    }
+
+    for include in &include.custom {
+        match include.kind {
+            IncludeKind::Quoted => {
+                writeln!(out, "#include \"{}\"", include.path.escape_default());
+            }
+            IncludeKind::Bracketed => {
+                writeln!(out, "#include <{}>", include.path);
+            }
+        }
+    }
+
+    let Includes {
+        custom: _,
+        algorithm,
+        array,
+        cassert,
+        cstddef,
+        cstdint,
+        cstring,
+        exception,
+        functional,
+        initializer_list,
+        iterator,
+        memory,
+        new,
+        stdexcept,
+        string,
+        type_traits,
+        utility,
+        vector,
+        basetsd,
+        sys_types,
+        content: _,
+    } = *include;
+
+    if algorithm && !cxx_header {
+        writeln!(out, "#include <algorithm>");
+    }
+    if array && !cxx_header {
+        writeln!(out, "#include <array>");
+    }
+    if cassert && !cxx_header {
+        writeln!(out, "#include <cassert>");
+    }
+    if cstddef && !cxx_header {
+        writeln!(out, "#include <cstddef>");
+    }
+    if cstdint && !cxx_header {
+        writeln!(out, "#include <cstdint>");
+    }
+    if cstring {
+        writeln!(out, "#include <cstring>");
+    }
+    if exception && !cxx_header {
+        writeln!(out, "#include <exception>");
+    }
+    if functional {
+        writeln!(out, "#include <functional>");
+    }
+    if initializer_list && !cxx_header {
+        writeln!(out, "#include <initializer_list>");
+    }
+    if iterator && !cxx_header {
+        writeln!(out, "#include <iterator>");
+    }
+    if memory {
+        writeln!(out, "#include <memory>");
+    }
+    if new && !cxx_header {
+        writeln!(out, "#include <new>");
+    }
+    if stdexcept && !cxx_header {
+        writeln!(out, "#include <stdexcept>");
+    }
+    if string && !cxx_header {
+        writeln!(out, "#include <string>");
+    }
+    if type_traits && !cxx_header {
+        writeln!(out, "#include <type_traits>");
+    }
+    if utility && !cxx_header {
+        writeln!(out, "#include <utility>");
+    }
+    if vector && !cxx_header {
+        writeln!(out, "#include <vector>");
+    }
+    if basetsd && !cxx_header {
+        writeln!(out, "#if defined(_WIN32)");
+        writeln!(out, "#include <basetsd.h>");
+    }
+    if sys_types && !cxx_header {
+        if basetsd {
+            writeln!(out, "#else");
+        } else {
+            writeln!(out, "#if not defined(_WIN32)");
+        }
+    }
+    if sys_types && !cxx_header {
+        writeln!(out, "#include <sys/types.h>");
+    }
+    if (basetsd || sys_types) && !cxx_header {
+        writeln!(out, "#endif");
+    }
+}
+
+impl<'i, 'a> Extend<&'i Include> for Includes<'a> {
+    fn extend<I: IntoIterator<Item = &'i Include>>(&mut self, iter: I) {
+        self.custom.extend(iter.into_iter().cloned());
+    }
+}
+
+impl<'i> From<&'i syntax::Include> for Include {
+    fn from(include: &syntax::Include) -> Self {
+        Include {
+            path: include.path.clone(),
+            kind: include.kind,
+        }
+    }
+}
+
+impl<'a> Deref for Includes<'a> {
+    type Target = Content<'a>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.content
+    }
+}
+
+impl<'a> DerefMut for Includes<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.content
+    }
+}
diff --git a/src/gen/include/cxx.h b/src/gen/include/cxx.h
new file mode 100644 (file)
index 0000000..907ee82
--- /dev/null
@@ -0,0 +1,1111 @@
+#pragma once
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <exception>
+#include <initializer_list>
+#include <iosfwd>
+#include <iterator>
+#include <new>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#if defined(_WIN32)
+#include <basetsd.h>
+#else
+#include <sys/types.h>
+#endif
+
+namespace rust {
+inline namespace cxxbridge1 {
+
+struct unsafe_bitcopy_t;
+
+namespace {
+template <typename T>
+class impl;
+}
+
+#ifndef CXXBRIDGE1_RUST_STRING
+#define CXXBRIDGE1_RUST_STRING
+// https://cxx.rs/binding/string.html
+class String final {
+public:
+  String() noexcept;
+  String(const String &) noexcept;
+  String(String &&) noexcept;
+  ~String() noexcept;
+
+  String(const std::string &);
+  String(const char *);
+  String(const char *, std::size_t);
+  String(const char16_t *);
+  String(const char16_t *, std::size_t);
+
+  // Replace invalid Unicode data with the replacement character (U+FFFD).
+  static String lossy(const std::string &) noexcept;
+  static String lossy(const char *) noexcept;
+  static String lossy(const char *, std::size_t) noexcept;
+  static String lossy(const char16_t *) noexcept;
+  static String lossy(const char16_t *, std::size_t) noexcept;
+
+  String &operator=(const String &) &noexcept;
+  String &operator=(String &&) &noexcept;
+
+  explicit operator std::string() const;
+
+  // Note: no null terminator.
+  const char *data() const noexcept;
+  std::size_t size() const noexcept;
+  std::size_t length() const noexcept;
+  bool empty() const noexcept;
+
+  const char *c_str() noexcept;
+
+  std::size_t capacity() const noexcept;
+  void reserve(size_t new_cap) noexcept;
+
+  using iterator = char *;
+  iterator begin() noexcept;
+  iterator end() noexcept;
+
+  using const_iterator = const char *;
+  const_iterator begin() const noexcept;
+  const_iterator end() const noexcept;
+  const_iterator cbegin() const noexcept;
+  const_iterator cend() const noexcept;
+
+  bool operator==(const String &) const noexcept;
+  bool operator!=(const String &) const noexcept;
+  bool operator<(const String &) const noexcept;
+  bool operator<=(const String &) const noexcept;
+  bool operator>(const String &) const noexcept;
+  bool operator>=(const String &) const noexcept;
+
+  void swap(String &) noexcept;
+
+  // Internal API only intended for the cxxbridge code generator.
+  String(unsafe_bitcopy_t, const String &) noexcept;
+
+private:
+  struct lossy_t;
+  String(lossy_t, const char *, std::size_t) noexcept;
+  String(lossy_t, const char16_t *, std::size_t) noexcept;
+  friend void swap(String &lhs, String &rhs) noexcept { lhs.swap(rhs); }
+
+  // Size and alignment statically verified by rust_string.rs.
+  std::array<std::uintptr_t, 3> repr;
+};
+#endif // CXXBRIDGE1_RUST_STRING
+
+#ifndef CXXBRIDGE1_RUST_STR
+#define CXXBRIDGE1_RUST_STR
+// https://cxx.rs/binding/str.html
+class Str final {
+public:
+  Str() noexcept;
+  Str(const String &) noexcept;
+  Str(const std::string &);
+  Str(const char *);
+  Str(const char *, std::size_t);
+
+  Str &operator=(const Str &) &noexcept = default;
+
+  explicit operator std::string() const;
+
+  // Note: no null terminator.
+  const char *data() const noexcept;
+  std::size_t size() const noexcept;
+  std::size_t length() const noexcept;
+  bool empty() const noexcept;
+
+  // Important in order for System V ABI to pass in registers.
+  Str(const Str &) noexcept = default;
+  ~Str() noexcept = default;
+
+  using iterator = const char *;
+  using const_iterator = const char *;
+  const_iterator begin() const noexcept;
+  const_iterator end() const noexcept;
+  const_iterator cbegin() const noexcept;
+  const_iterator cend() const noexcept;
+
+  bool operator==(const Str &) const noexcept;
+  bool operator!=(const Str &) const noexcept;
+  bool operator<(const Str &) const noexcept;
+  bool operator<=(const Str &) const noexcept;
+  bool operator>(const Str &) const noexcept;
+  bool operator>=(const Str &) const noexcept;
+
+  void swap(Str &) noexcept;
+
+private:
+  class uninit;
+  Str(uninit) noexcept;
+  friend impl<Str>;
+
+  std::array<std::uintptr_t, 2> repr;
+};
+#endif // CXXBRIDGE1_RUST_STR
+
+#ifndef CXXBRIDGE1_RUST_SLICE
+namespace detail {
+template <bool>
+struct copy_assignable_if {};
+
+template <>
+struct copy_assignable_if<false> {
+  copy_assignable_if() noexcept = default;
+  copy_assignable_if(const copy_assignable_if &) noexcept = default;
+  copy_assignable_if &operator=(const copy_assignable_if &) &noexcept = delete;
+  copy_assignable_if &operator=(copy_assignable_if &&) &noexcept = default;
+};
+} // namespace detail
+
+// https://cxx.rs/binding/slice.html
+template <typename T>
+class Slice final
+    : private detail::copy_assignable_if<std::is_const<T>::value> {
+public:
+  using value_type = T;
+
+  Slice() noexcept;
+  Slice(T *, std::size_t count) noexcept;
+
+  Slice &operator=(const Slice<T> &) &noexcept = default;
+  Slice &operator=(Slice<T> &&) &noexcept = default;
+
+  T *data() const noexcept;
+  std::size_t size() const noexcept;
+  std::size_t length() const noexcept;
+  bool empty() const noexcept;
+
+  T &operator[](std::size_t n) const noexcept;
+  T &at(std::size_t n) const;
+  T &front() const noexcept;
+  T &back() const noexcept;
+
+  // Important in order for System V ABI to pass in registers.
+  Slice(const Slice<T> &) noexcept = default;
+  ~Slice() noexcept = default;
+
+  class iterator;
+  iterator begin() const noexcept;
+  iterator end() const noexcept;
+
+  void swap(Slice &) noexcept;
+
+private:
+  class uninit;
+  Slice(uninit) noexcept;
+  friend impl<Slice>;
+  friend void sliceInit(void *, const void *, std::size_t) noexcept;
+  friend void *slicePtr(const void *) noexcept;
+  friend std::size_t sliceLen(const void *) noexcept;
+
+  std::array<std::uintptr_t, 2> repr;
+};
+
+template <typename T>
+class Slice<T>::iterator final {
+public:
+  using iterator_category = std::random_access_iterator_tag;
+  using value_type = T;
+  using difference_type = std::ptrdiff_t;
+  using pointer = typename std::add_pointer<T>::type;
+  using reference = typename std::add_lvalue_reference<T>::type;
+
+  reference operator*() const noexcept;
+  pointer operator->() const noexcept;
+  reference operator[](difference_type) const noexcept;
+
+  iterator &operator++() noexcept;
+  iterator operator++(int) noexcept;
+  iterator &operator--() noexcept;
+  iterator operator--(int) noexcept;
+
+  iterator &operator+=(difference_type) noexcept;
+  iterator &operator-=(difference_type) noexcept;
+  iterator operator+(difference_type) const noexcept;
+  iterator operator-(difference_type) const noexcept;
+  difference_type operator-(const iterator &) const noexcept;
+
+  bool operator==(const iterator &) const noexcept;
+  bool operator!=(const iterator &) const noexcept;
+  bool operator<(const iterator &) const noexcept;
+  bool operator<=(const iterator &) const noexcept;
+  bool operator>(const iterator &) const noexcept;
+  bool operator>=(const iterator &) const noexcept;
+
+private:
+  friend class Slice;
+  void *pos;
+  std::size_t stride;
+};
+#endif // CXXBRIDGE1_RUST_SLICE
+
+#ifndef CXXBRIDGE1_RUST_BOX
+// https://cxx.rs/binding/box.html
+template <typename T>
+class Box final {
+public:
+  using element_type = T;
+  using const_pointer =
+      typename std::add_pointer<typename std::add_const<T>::type>::type;
+  using pointer = typename std::add_pointer<T>::type;
+
+  Box() = delete;
+  Box(Box &&) noexcept;
+  ~Box() noexcept;
+
+  explicit Box(const T &);
+  explicit Box(T &&);
+
+  Box &operator=(Box &&) &noexcept;
+
+  const T *operator->() const noexcept;
+  const T &operator*() const noexcept;
+  T *operator->() noexcept;
+  T &operator*() noexcept;
+
+  template <typename... Fields>
+  static Box in_place(Fields &&...);
+
+  void swap(Box &) noexcept;
+
+  // Important: requires that `raw` came from an into_raw call. Do not pass a
+  // pointer from `new` or any other source.
+  static Box from_raw(T *) noexcept;
+
+  T *into_raw() noexcept;
+
+  /* Deprecated */ using value_type = element_type;
+
+private:
+  class uninit;
+  class allocation;
+  Box(uninit) noexcept;
+  void drop() noexcept;
+
+  friend void swap(Box &lhs, Box &rhs) noexcept { lhs.swap(rhs); }
+
+  T *ptr;
+};
+#endif // CXXBRIDGE1_RUST_BOX
+
+#ifndef CXXBRIDGE1_RUST_VEC
+// https://cxx.rs/binding/vec.html
+template <typename T>
+class Vec final {
+public:
+  using value_type = T;
+
+  Vec() noexcept;
+  Vec(std::initializer_list<T>);
+  Vec(const Vec &);
+  Vec(Vec &&) noexcept;
+  ~Vec() noexcept;
+
+  Vec &operator=(Vec &&) &noexcept;
+  Vec &operator=(const Vec &) &;
+
+  std::size_t size() const noexcept;
+  bool empty() const noexcept;
+  const T *data() const noexcept;
+  T *data() noexcept;
+  std::size_t capacity() const noexcept;
+
+  const T &operator[](std::size_t n) const noexcept;
+  const T &at(std::size_t n) const;
+  const T &front() const noexcept;
+  const T &back() const noexcept;
+
+  T &operator[](std::size_t n) noexcept;
+  T &at(std::size_t n);
+  T &front() noexcept;
+  T &back() noexcept;
+
+  void reserve(std::size_t new_cap);
+  void push_back(const T &value);
+  void push_back(T &&value);
+  template <typename... Args>
+  void emplace_back(Args &&...args);
+  void truncate(std::size_t len);
+  void clear();
+
+  using iterator = typename Slice<T>::iterator;
+  iterator begin() noexcept;
+  iterator end() noexcept;
+
+  using const_iterator = typename Slice<const T>::iterator;
+  const_iterator begin() const noexcept;
+  const_iterator end() const noexcept;
+  const_iterator cbegin() const noexcept;
+  const_iterator cend() const noexcept;
+
+  void swap(Vec &) noexcept;
+
+  // Internal API only intended for the cxxbridge code generator.
+  Vec(unsafe_bitcopy_t, const Vec &) noexcept;
+
+private:
+  void reserve_total(std::size_t new_cap) noexcept;
+  void set_len(std::size_t len) noexcept;
+  void drop() noexcept;
+
+  friend void swap(Vec &lhs, Vec &rhs) noexcept { lhs.swap(rhs); }
+
+  // Size and alignment statically verified by rust_vec.rs.
+  std::array<std::uintptr_t, 3> repr;
+};
+#endif // CXXBRIDGE1_RUST_VEC
+
+#ifndef CXXBRIDGE1_RUST_FN
+// https://cxx.rs/binding/fn.html
+template <typename Signature>
+class Fn;
+
+template <typename Ret, typename... Args>
+class Fn<Ret(Args...)> final {
+public:
+  Ret operator()(Args... args) const noexcept;
+  Fn operator*() const noexcept;
+
+private:
+  Ret (*trampoline)(Args..., void *fn) noexcept;
+  void *fn;
+};
+#endif // CXXBRIDGE1_RUST_FN
+
+#ifndef CXXBRIDGE1_RUST_ERROR
+#define CXXBRIDGE1_RUST_ERROR
+// https://cxx.rs/binding/result.html
+class Error final : public std::exception {
+public:
+  Error(const Error &);
+  Error(Error &&) noexcept;
+  ~Error() noexcept override;
+
+  Error &operator=(const Error &) &;
+  Error &operator=(Error &&) &noexcept;
+
+  const char *what() const noexcept override;
+
+private:
+  Error() noexcept = default;
+  friend impl<Error>;
+  const char *msg;
+  std::size_t len;
+};
+#endif // CXXBRIDGE1_RUST_ERROR
+
+#ifndef CXXBRIDGE1_RUST_ISIZE
+#define CXXBRIDGE1_RUST_ISIZE
+#if defined(_WIN32)
+using isize = SSIZE_T;
+#else
+using isize = ssize_t;
+#endif
+#endif // CXXBRIDGE1_RUST_ISIZE
+
+std::ostream &operator<<(std::ostream &, const String &);
+std::ostream &operator<<(std::ostream &, const Str &);
+
+#ifndef CXXBRIDGE1_RUST_OPAQUE
+#define CXXBRIDGE1_RUST_OPAQUE
+// Base class of generated opaque Rust types.
+class Opaque {
+public:
+  Opaque() = delete;
+  Opaque(const Opaque &) = delete;
+  ~Opaque() = delete;
+};
+#endif // CXXBRIDGE1_RUST_OPAQUE
+
+template <typename T>
+std::size_t size_of();
+template <typename T>
+std::size_t align_of();
+
+// IsRelocatable<T> is used in assertions that a C++ type passed by value
+// between Rust and C++ is soundly relocatable by Rust.
+//
+// There may be legitimate reasons to opt out of the check for support of types
+// that the programmer knows are soundly Rust-movable despite not being
+// recognized as such by the C++ type system due to a move constructor or
+// destructor. To opt out of the relocatability check, do either of the
+// following things in any header used by `include!` in the bridge.
+//
+//      --- if you define the type:
+//      struct MyType {
+//        ...
+//    +   using IsRelocatable = std::true_type;
+//      };
+//
+//      --- otherwise:
+//    + template <>
+//    + struct rust::IsRelocatable<MyType> : std::true_type {};
+template <typename T>
+struct IsRelocatable;
+
+using u8 = std::uint8_t;
+using u16 = std::uint16_t;
+using u32 = std::uint32_t;
+using u64 = std::uint64_t;
+using usize = std::size_t; // see static asserts in cxx.cc
+using i8 = std::int8_t;
+using i16 = std::int16_t;
+using i32 = std::int32_t;
+using i64 = std::int64_t;
+using f32 = float;
+using f64 = double;
+
+// Snake case aliases for use in code that uses this style for type names.
+using string = String;
+using str = Str;
+template <typename T>
+using slice = Slice<T>;
+template <typename T>
+using box = Box<T>;
+template <typename T>
+using vec = Vec<T>;
+using error = Error;
+template <typename Signature>
+using fn = Fn<Signature>;
+template <typename T>
+using is_relocatable = IsRelocatable<T>;
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+/// end public API, begin implementation details
+
+#ifndef CXXBRIDGE1_PANIC
+#define CXXBRIDGE1_PANIC
+template <typename Exception>
+void panic [[noreturn]] (const char *msg);
+#endif // CXXBRIDGE1_PANIC
+
+#ifndef CXXBRIDGE1_RUST_FN
+#define CXXBRIDGE1_RUST_FN
+template <typename Ret, typename... Args>
+Ret Fn<Ret(Args...)>::operator()(Args... args) const noexcept {
+  return (*this->trampoline)(std::forward<Args>(args)..., this->fn);
+}
+
+template <typename Ret, typename... Args>
+Fn<Ret(Args...)> Fn<Ret(Args...)>::operator*() const noexcept {
+  return *this;
+}
+#endif // CXXBRIDGE1_RUST_FN
+
+#ifndef CXXBRIDGE1_RUST_BITCOPY_T
+#define CXXBRIDGE1_RUST_BITCOPY_T
+struct unsafe_bitcopy_t final {
+  explicit unsafe_bitcopy_t() = default;
+};
+#endif // CXXBRIDGE1_RUST_BITCOPY_T
+
+#ifndef CXXBRIDGE1_RUST_BITCOPY
+#define CXXBRIDGE1_RUST_BITCOPY
+constexpr unsafe_bitcopy_t unsafe_bitcopy{};
+#endif // CXXBRIDGE1_RUST_BITCOPY
+
+#ifndef CXXBRIDGE1_RUST_SLICE
+#define CXXBRIDGE1_RUST_SLICE
+template <typename T>
+Slice<T>::Slice() noexcept {
+  sliceInit(this, reinterpret_cast<void *>(align_of<T>()), 0);
+}
+
+template <typename T>
+Slice<T>::Slice(T *s, std::size_t count) noexcept {
+  assert(s != nullptr || count == 0);
+  sliceInit(this,
+            s == nullptr && count == 0
+                ? reinterpret_cast<void *>(align_of<T>())
+                : const_cast<typename std::remove_const<T>::type *>(s),
+            count);
+}
+
+template <typename T>
+T *Slice<T>::data() const noexcept {
+  return reinterpret_cast<T *>(slicePtr(this));
+}
+
+template <typename T>
+std::size_t Slice<T>::size() const noexcept {
+  return sliceLen(this);
+}
+
+template <typename T>
+std::size_t Slice<T>::length() const noexcept {
+  return this->size();
+}
+
+template <typename T>
+bool Slice<T>::empty() const noexcept {
+  return this->size() == 0;
+}
+
+template <typename T>
+T &Slice<T>::operator[](std::size_t n) const noexcept {
+  assert(n < this->size());
+  auto ptr = static_cast<char *>(slicePtr(this)) + size_of<T>() * n;
+  return *reinterpret_cast<T *>(ptr);
+}
+
+template <typename T>
+T &Slice<T>::at(std::size_t n) const {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Slice index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+T &Slice<T>::front() const noexcept {
+  assert(!this->empty());
+  return (*this)[0];
+}
+
+template <typename T>
+T &Slice<T>::back() const noexcept {
+  assert(!this->empty());
+  return (*this)[this->size() - 1];
+}
+
+template <typename T>
+typename Slice<T>::iterator::reference
+Slice<T>::iterator::operator*() const noexcept {
+  return *static_cast<T *>(this->pos);
+}
+
+template <typename T>
+typename Slice<T>::iterator::pointer
+Slice<T>::iterator::operator->() const noexcept {
+  return static_cast<T *>(this->pos);
+}
+
+template <typename T>
+typename Slice<T>::iterator::reference Slice<T>::iterator::operator[](
+    typename Slice<T>::iterator::difference_type n) const noexcept {
+  auto ptr = static_cast<char *>(this->pos) + this->stride * n;
+  return *reinterpret_cast<T *>(ptr);
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator++() noexcept {
+  this->pos = static_cast<char *>(this->pos) + this->stride;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator++(int) noexcept {
+  auto ret = iterator(*this);
+  this->pos = static_cast<char *>(this->pos) + this->stride;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator--() noexcept {
+  this->pos = static_cast<char *>(this->pos) - this->stride;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator--(int) noexcept {
+  auto ret = iterator(*this);
+  this->pos = static_cast<char *>(this->pos) - this->stride;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator+=(
+    typename Slice<T>::iterator::difference_type n) noexcept {
+  this->pos = static_cast<char *>(this->pos) + this->stride * n;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator &Slice<T>::iterator::operator-=(
+    typename Slice<T>::iterator::difference_type n) noexcept {
+  this->pos = static_cast<char *>(this->pos) - this->stride * n;
+  return *this;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator+(
+    typename Slice<T>::iterator::difference_type n) const noexcept {
+  auto ret = iterator(*this);
+  ret.pos = static_cast<char *>(this->pos) + this->stride * n;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::iterator::operator-(
+    typename Slice<T>::iterator::difference_type n) const noexcept {
+  auto ret = iterator(*this);
+  ret.pos = static_cast<char *>(this->pos) - this->stride * n;
+  return ret;
+}
+
+template <typename T>
+typename Slice<T>::iterator::difference_type
+Slice<T>::iterator::operator-(const iterator &other) const noexcept {
+  auto diff = std::distance(static_cast<char *>(other.pos),
+                            static_cast<char *>(this->pos));
+  return diff / this->stride;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator==(const iterator &other) const noexcept {
+  return this->pos == other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator!=(const iterator &other) const noexcept {
+  return this->pos != other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator<(const iterator &other) const noexcept {
+  return this->pos < other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator<=(const iterator &other) const noexcept {
+  return this->pos <= other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator>(const iterator &other) const noexcept {
+  return this->pos > other.pos;
+}
+
+template <typename T>
+bool Slice<T>::iterator::operator>=(const iterator &other) const noexcept {
+  return this->pos >= other.pos;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::begin() const noexcept {
+  iterator it;
+  it.pos = slicePtr(this);
+  it.stride = size_of<T>();
+  return it;
+}
+
+template <typename T>
+typename Slice<T>::iterator Slice<T>::end() const noexcept {
+  iterator it = this->begin();
+  it.pos = static_cast<char *>(it.pos) + it.stride * this->size();
+  return it;
+}
+
+template <typename T>
+void Slice<T>::swap(Slice &rhs) noexcept {
+  std::swap(*this, rhs);
+}
+#endif // CXXBRIDGE1_RUST_SLICE
+
+#ifndef CXXBRIDGE1_RUST_BOX
+#define CXXBRIDGE1_RUST_BOX
+template <typename T>
+class Box<T>::uninit {};
+
+template <typename T>
+class Box<T>::allocation {
+  static T *alloc() noexcept;
+  static void dealloc(T *) noexcept;
+
+public:
+  allocation() noexcept : ptr(alloc()) {}
+  ~allocation() noexcept {
+    if (this->ptr) {
+      dealloc(this->ptr);
+    }
+  }
+  T *ptr;
+};
+
+template <typename T>
+Box<T>::Box(Box &&other) noexcept : ptr(other.ptr) {
+  other.ptr = nullptr;
+}
+
+template <typename T>
+Box<T>::Box(const T &val) {
+  allocation alloc;
+  ::new (alloc.ptr) T(val);
+  this->ptr = alloc.ptr;
+  alloc.ptr = nullptr;
+}
+
+template <typename T>
+Box<T>::Box(T &&val) {
+  allocation alloc;
+  ::new (alloc.ptr) T(std::move(val));
+  this->ptr = alloc.ptr;
+  alloc.ptr = nullptr;
+}
+
+template <typename T>
+Box<T>::~Box() noexcept {
+  if (this->ptr) {
+    this->drop();
+  }
+}
+
+template <typename T>
+Box<T> &Box<T>::operator=(Box &&other) &noexcept {
+  if (this->ptr) {
+    this->drop();
+  }
+  this->ptr = other.ptr;
+  other.ptr = nullptr;
+  return *this;
+}
+
+template <typename T>
+const T *Box<T>::operator->() const noexcept {
+  return this->ptr;
+}
+
+template <typename T>
+const T &Box<T>::operator*() const noexcept {
+  return *this->ptr;
+}
+
+template <typename T>
+T *Box<T>::operator->() noexcept {
+  return this->ptr;
+}
+
+template <typename T>
+T &Box<T>::operator*() noexcept {
+  return *this->ptr;
+}
+
+template <typename T>
+template <typename... Fields>
+Box<T> Box<T>::in_place(Fields &&...fields) {
+  allocation alloc;
+  auto ptr = alloc.ptr;
+  ::new (ptr) T{std::forward<Fields>(fields)...};
+  alloc.ptr = nullptr;
+  return from_raw(ptr);
+}
+
+template <typename T>
+void Box<T>::swap(Box &rhs) noexcept {
+  using std::swap;
+  swap(this->ptr, rhs.ptr);
+}
+
+template <typename T>
+Box<T> Box<T>::from_raw(T *raw) noexcept {
+  Box box = uninit{};
+  box.ptr = raw;
+  return box;
+}
+
+template <typename T>
+T *Box<T>::into_raw() noexcept {
+  T *raw = this->ptr;
+  this->ptr = nullptr;
+  return raw;
+}
+
+template <typename T>
+Box<T>::Box(uninit) noexcept {}
+#endif // CXXBRIDGE1_RUST_BOX
+
+#ifndef CXXBRIDGE1_RUST_VEC
+#define CXXBRIDGE1_RUST_VEC
+template <typename T>
+Vec<T>::Vec(std::initializer_list<T> init) : Vec{} {
+  this->reserve_total(init.size());
+  std::move(init.begin(), init.end(), std::back_inserter(*this));
+}
+
+template <typename T>
+Vec<T>::Vec(const Vec &other) : Vec() {
+  this->reserve_total(other.size());
+  std::copy(other.begin(), other.end(), std::back_inserter(*this));
+}
+
+template <typename T>
+Vec<T>::Vec(Vec &&other) noexcept : repr(other.repr) {
+  new (&other) Vec();
+}
+
+template <typename T>
+Vec<T>::~Vec() noexcept {
+  this->drop();
+}
+
+template <typename T>
+Vec<T> &Vec<T>::operator=(Vec &&other) &noexcept {
+  this->drop();
+  this->repr = other.repr;
+  new (&other) Vec();
+  return *this;
+}
+
+template <typename T>
+Vec<T> &Vec<T>::operator=(const Vec &other) & {
+  if (this != &other) {
+    this->drop();
+    new (this) Vec(other);
+  }
+  return *this;
+}
+
+template <typename T>
+bool Vec<T>::empty() const noexcept {
+  return this->size() == 0;
+}
+
+template <typename T>
+T *Vec<T>::data() noexcept {
+  return const_cast<T *>(const_cast<const Vec<T> *>(this)->data());
+}
+
+template <typename T>
+const T &Vec<T>::operator[](std::size_t n) const noexcept {
+  assert(n < this->size());
+  auto data = reinterpret_cast<const char *>(this->data());
+  return *reinterpret_cast<const T *>(data + n * size_of<T>());
+}
+
+template <typename T>
+const T &Vec<T>::at(std::size_t n) const {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Vec index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+const T &Vec<T>::front() const noexcept {
+  assert(!this->empty());
+  return (*this)[0];
+}
+
+template <typename T>
+const T &Vec<T>::back() const noexcept {
+  assert(!this->empty());
+  return (*this)[this->size() - 1];
+}
+
+template <typename T>
+T &Vec<T>::operator[](std::size_t n) noexcept {
+  assert(n < this->size());
+  auto data = reinterpret_cast<char *>(this->data());
+  return *reinterpret_cast<T *>(data + n * size_of<T>());
+}
+
+template <typename T>
+T &Vec<T>::at(std::size_t n) {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Vec index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+T &Vec<T>::front() noexcept {
+  assert(!this->empty());
+  return (*this)[0];
+}
+
+template <typename T>
+T &Vec<T>::back() noexcept {
+  assert(!this->empty());
+  return (*this)[this->size() - 1];
+}
+
+template <typename T>
+void Vec<T>::reserve(std::size_t new_cap) {
+  this->reserve_total(new_cap);
+}
+
+template <typename T>
+void Vec<T>::push_back(const T &value) {
+  this->emplace_back(value);
+}
+
+template <typename T>
+void Vec<T>::push_back(T &&value) {
+  this->emplace_back(std::move(value));
+}
+
+template <typename T>
+template <typename... Args>
+void Vec<T>::emplace_back(Args &&...args) {
+  auto size = this->size();
+  this->reserve_total(size + 1);
+  ::new (reinterpret_cast<T *>(reinterpret_cast<char *>(this->data()) +
+                               size * size_of<T>()))
+      T(std::forward<Args>(args)...);
+  this->set_len(size + 1);
+}
+
+template <typename T>
+void Vec<T>::clear() {
+  this->truncate(0);
+}
+
+template <typename T>
+typename Vec<T>::iterator Vec<T>::begin() noexcept {
+  return Slice<T>(this->data(), this->size()).begin();
+}
+
+template <typename T>
+typename Vec<T>::iterator Vec<T>::end() noexcept {
+  return Slice<T>(this->data(), this->size()).end();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::begin() const noexcept {
+  return this->cbegin();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::end() const noexcept {
+  return this->cend();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::cbegin() const noexcept {
+  return Slice<const T>(this->data(), this->size()).begin();
+}
+
+template <typename T>
+typename Vec<T>::const_iterator Vec<T>::cend() const noexcept {
+  return Slice<const T>(this->data(), this->size()).end();
+}
+
+template <typename T>
+void Vec<T>::swap(Vec &rhs) noexcept {
+  using std::swap;
+  swap(this->repr, rhs.repr);
+}
+
+// Internal API only intended for the cxxbridge code generator.
+template <typename T>
+Vec<T>::Vec(unsafe_bitcopy_t, const Vec &bits) noexcept : repr(bits.repr) {}
+#endif // CXXBRIDGE1_RUST_VEC
+
+#ifndef CXXBRIDGE1_IS_COMPLETE
+#define CXXBRIDGE1_IS_COMPLETE
+namespace detail {
+namespace {
+template <typename T, typename = std::size_t>
+struct is_complete : std::false_type {};
+template <typename T>
+struct is_complete<T, decltype(sizeof(T))> : std::true_type {};
+} // namespace
+} // namespace detail
+#endif // CXXBRIDGE1_IS_COMPLETE
+
+#ifndef CXXBRIDGE1_LAYOUT
+#define CXXBRIDGE1_LAYOUT
+class layout {
+  template <typename T>
+  friend std::size_t size_of();
+  template <typename T>
+  friend std::size_t align_of();
+  template <typename T>
+  static typename std::enable_if<std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_size_of() {
+    return T::layout::size();
+  }
+  template <typename T>
+  static typename std::enable_if<!std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_size_of() {
+    return sizeof(T);
+  }
+  template <typename T>
+  static
+      typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type
+      size_of() {
+    return do_size_of<T>();
+  }
+  template <typename T>
+  static typename std::enable_if<std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_align_of() {
+    return T::layout::align();
+  }
+  template <typename T>
+  static typename std::enable_if<!std::is_base_of<Opaque, T>::value,
+                                 std::size_t>::type
+  do_align_of() {
+    return alignof(T);
+  }
+  template <typename T>
+  static
+      typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type
+      align_of() {
+    return do_align_of<T>();
+  }
+};
+
+template <typename T>
+std::size_t size_of() {
+  return layout::size_of<T>();
+}
+
+template <typename T>
+std::size_t align_of() {
+  return layout::align_of<T>();
+}
+#endif // CXXBRIDGE1_LAYOUT
+
+#ifndef CXXBRIDGE1_RELOCATABLE
+#define CXXBRIDGE1_RELOCATABLE
+namespace detail {
+template <typename... Ts>
+struct make_void {
+  using type = void;
+};
+
+template <typename... Ts>
+using void_t = typename make_void<Ts...>::type;
+
+template <typename Void, template <typename...> class, typename...>
+struct detect : std::false_type {};
+template <template <typename...> class T, typename... A>
+struct detect<void_t<T<A...>>, T, A...> : std::true_type {};
+
+template <template <typename...> class T, typename... A>
+using is_detected = detect<void, T, A...>;
+
+template <typename T>
+using detect_IsRelocatable = typename T::IsRelocatable;
+
+template <typename T>
+struct get_IsRelocatable
+    : std::is_same<typename T::IsRelocatable, std::true_type> {};
+} // namespace detail
+
+template <typename T>
+struct IsRelocatable
+    : std::conditional<
+          detail::is_detected<detail::detect_IsRelocatable, T>::value,
+          detail::get_IsRelocatable<T>,
+          std::integral_constant<
+              bool, std::is_trivially_move_constructible<T>::value &&
+                        std::is_trivially_destructible<T>::value>>::type {};
+#endif // CXXBRIDGE1_RELOCATABLE
+
+} // namespace cxxbridge1
+} // namespace rust
diff --git a/src/gen/mod.rs b/src/gen/mod.rs
new file mode 100644 (file)
index 0000000..d8b90d0
--- /dev/null
@@ -0,0 +1,151 @@
+// Functionality that is shared between the cxx_build::bridge entry point and
+// the cxxbridge CLI command.
+
+mod block;
+mod builtin;
+mod check;
+pub(super) mod error;
+mod file;
+pub(super) mod fs;
+mod ifndef;
+pub(super) mod include;
+mod namespace;
+mod nested;
+pub(super) mod out;
+mod write;
+
+pub(super) use self::error::Error;
+use self::error::{format_err, Result};
+use self::file::File;
+use self::include::Include;
+use crate::syntax::report::Errors;
+use crate::syntax::{self, Types};
+use std::path::Path;
+
+/// Options for C++ code generation.
+///
+/// We expect options to be added over time, so this is a non-exhaustive struct.
+/// To instantiate one you need to crate a default value and mutate those fields
+/// that you want to modify.
+///
+/// ```
+/// # use cxx_gen::Opt;
+/// #
+/// let impl_annotations = r#"__attribute__((visibility("default")))"#.to_owned();
+///
+/// let mut opt = Opt::default();
+/// opt.cxx_impl_annotations = Some(impl_annotations);
+/// ```
+#[non_exhaustive]
+pub struct Opt {
+    /// Any additional headers to #include. The cxxbridge tool does not parse or
+    /// even require the given paths to exist; they simply go into the generated
+    /// C++ code as #include lines.
+    pub include: Vec<Include>,
+    /// Optional annotation for implementations of C++ function wrappers that
+    /// may be exposed to Rust. You may for example need to provide
+    /// `__declspec(dllexport)` or `__attribute__((visibility("default")))` if
+    /// Rust code from one shared object or executable depends on these C++
+    /// functions in another.
+    pub cxx_impl_annotations: Option<String>,
+
+    pub(super) gen_header: bool,
+    pub(super) gen_implementation: bool,
+    pub(super) allow_dot_includes: bool,
+}
+
+/// Results of code generation.
+#[derive(Default)]
+pub struct GeneratedCode {
+    /// The bytes of a C++ header file.
+    pub header: Vec<u8>,
+    /// The bytes of a C++ implementation file (e.g. .cc, cpp etc.)
+    pub implementation: Vec<u8>,
+}
+
+impl Default for Opt {
+    fn default() -> Self {
+        Opt {
+            include: Vec::new(),
+            cxx_impl_annotations: None,
+            gen_header: true,
+            gen_implementation: true,
+            allow_dot_includes: true,
+        }
+    }
+}
+
+pub(super) fn generate_from_path(path: &Path, opt: &Opt) -> GeneratedCode {
+    let source = match read_to_string(path) {
+        Ok(source) => source,
+        Err(err) => format_err(path, "", err),
+    };
+    match generate_from_string(&source, opt) {
+        Ok(out) => out,
+        Err(err) => format_err(path, &source, err),
+    }
+}
+
+fn read_to_string(path: &Path) -> Result<String> {
+    let bytes = if path == Path::new("-") {
+        fs::read_stdin()
+    } else {
+        fs::read(path)
+    }?;
+    match String::from_utf8(bytes) {
+        Ok(string) => Ok(string),
+        Err(err) => Err(Error::Utf8(path.to_owned(), err.utf8_error())),
+    }
+}
+
+fn generate_from_string(source: &str, opt: &Opt) -> Result<GeneratedCode> {
+    let mut source = source;
+    if source.starts_with("#!") && !source.starts_with("#![") {
+        let shebang_end = source.find('\n').unwrap_or(source.len());
+        source = &source[shebang_end..];
+    }
+    proc_macro2::fallback::force();
+    let syntax: File = syn::parse_str(source)?;
+    generate(syntax, opt)
+}
+
+pub(super) fn generate(syntax: File, opt: &Opt) -> Result<GeneratedCode> {
+    if syntax.modules.is_empty() {
+        return Err(Error::NoBridgeMod);
+    }
+
+    let ref mut apis = Vec::new();
+    let ref mut errors = Errors::new();
+    for bridge in syntax.modules {
+        let ref namespace = bridge.namespace;
+        let trusted = bridge.unsafety.is_some();
+        apis.extend(syntax::parse_items(
+            errors,
+            bridge.content,
+            trusted,
+            namespace,
+        ));
+    }
+
+    let ref types = Types::collect(errors, apis);
+    check::precheck(errors, apis, opt);
+    errors.propagate()?;
+    let generator = check::Generator::Build;
+    check::typecheck(errors, apis, types, generator);
+    errors.propagate()?;
+
+    // Some callers may wish to generate both header and implementation from the
+    // same token stream to avoid parsing twice. Others only need to generate
+    // one or the other.
+    let (mut header, mut implementation) = Default::default();
+    if opt.gen_header {
+        header = write::gen(apis, types, opt, true);
+    }
+    if opt.gen_implementation {
+        implementation = write::gen(apis, types, opt, false);
+    }
+    Ok(GeneratedCode {
+        header,
+        implementation,
+    })
+}
diff --git a/src/gen/namespace.rs b/src/gen/namespace.rs
new file mode 100644 (file)
index 0000000..b79c38f
--- /dev/null
@@ -0,0 +1,14 @@
+use crate::syntax::namespace::Namespace;
+use crate::syntax::Api;
+
+impl Api {
+    pub fn namespace(&self) -> &Namespace {
+        match self {
+            Api::CxxFunction(efn) | Api::RustFunction(efn) => &efn.name.namespace,
+            Api::CxxType(ety) | Api::RustType(ety) => &ety.name.namespace,
+            Api::Enum(enm) => &enm.name.namespace,
+            Api::Struct(strct) => &strct.name.namespace,
+            Api::Impl(_) | Api::Include(_) | Api::TypeAlias(_) => Default::default(),
+        }
+    }
+}
diff --git a/src/gen/nested.rs b/src/gen/nested.rs
new file mode 100644 (file)
index 0000000..2129d10
--- /dev/null
@@ -0,0 +1,153 @@
+use crate::syntax::map::UnorderedMap as Map;
+use crate::syntax::Api;
+use proc_macro2::Ident;
+
+pub struct NamespaceEntries<'a> {
+    direct: Vec<&'a Api>,
+    nested: Vec<(&'a Ident, NamespaceEntries<'a>)>,
+}
+
+impl<'a> NamespaceEntries<'a> {
+    pub fn new(apis: Vec<&'a Api>) -> Self {
+        sort_by_inner_namespace(apis, 0)
+    }
+
+    pub fn direct_content(&self) -> &[&'a Api] {
+        &self.direct
+    }
+
+    pub fn nested_content(&self) -> impl Iterator<Item = (&'a Ident, &NamespaceEntries<'a>)> {
+        self.nested.iter().map(|(k, entries)| (*k, entries))
+    }
+}
+
+fn sort_by_inner_namespace(apis: Vec<&Api>, depth: usize) -> NamespaceEntries {
+    let mut direct = Vec::new();
+    let mut nested_namespaces = Vec::new();
+    let mut index_of_namespace = Map::new();
+
+    for api in &apis {
+        if let Some(first_ns_elem) = api.namespace().iter().nth(depth) {
+            match index_of_namespace.get(first_ns_elem) {
+                None => {
+                    index_of_namespace.insert(first_ns_elem, nested_namespaces.len());
+                    nested_namespaces.push((first_ns_elem, vec![*api]));
+                }
+                Some(&index) => nested_namespaces[index].1.push(*api),
+            }
+            continue;
+        }
+        direct.push(*api);
+    }
+
+    let nested = nested_namespaces
+        .into_iter()
+        .map(|(k, apis)| (k, sort_by_inner_namespace(apis, depth + 1)))
+        .collect();
+
+    NamespaceEntries { direct, nested }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::NamespaceEntries;
+    use crate::syntax::attrs::OtherAttrs;
+    use crate::syntax::namespace::Namespace;
+    use crate::syntax::{Api, Doc, ExternType, ForeignName, Lang, Lifetimes, Pair};
+    use proc_macro2::{Ident, Span};
+    use std::iter::FromIterator;
+    use syn::punctuated::Punctuated;
+    use syn::Token;
+
+    #[test]
+    fn test_ns_entries_sort() {
+        let apis = &[
+            make_api(None, "C"),
+            make_api(None, "A"),
+            make_api(Some("G"), "E"),
+            make_api(Some("D"), "F"),
+            make_api(Some("G"), "H"),
+            make_api(Some("D::K"), "L"),
+            make_api(Some("D::K"), "M"),
+            make_api(None, "B"),
+            make_api(Some("D"), "I"),
+            make_api(Some("D"), "J"),
+        ];
+
+        let root = NamespaceEntries::new(Vec::from_iter(apis));
+
+        // ::
+        let root_direct = root.direct_content();
+        assert_eq!(root_direct.len(), 3);
+        assert_ident(root_direct[0], "C");
+        assert_ident(root_direct[1], "A");
+        assert_ident(root_direct[2], "B");
+
+        let mut root_nested = root.nested_content();
+        let (id, g) = root_nested.next().unwrap();
+        assert_eq!(id, "G");
+        let (id, d) = root_nested.next().unwrap();
+        assert_eq!(id, "D");
+        assert!(root_nested.next().is_none());
+
+        // ::G
+        let g_direct = g.direct_content();
+        assert_eq!(g_direct.len(), 2);
+        assert_ident(g_direct[0], "E");
+        assert_ident(g_direct[1], "H");
+
+        let mut g_nested = g.nested_content();
+        assert!(g_nested.next().is_none());
+
+        // ::D
+        let d_direct = d.direct_content();
+        assert_eq!(d_direct.len(), 3);
+        assert_ident(d_direct[0], "F");
+        assert_ident(d_direct[1], "I");
+        assert_ident(d_direct[2], "J");
+
+        let mut d_nested = d.nested_content();
+        let (id, k) = d_nested.next().unwrap();
+        assert_eq!(id, "K");
+
+        // ::D::K
+        let k_direct = k.direct_content();
+        assert_eq!(k_direct.len(), 2);
+        assert_ident(k_direct[0], "L");
+        assert_ident(k_direct[1], "M");
+    }
+
+    fn assert_ident(api: &Api, expected: &str) {
+        if let Api::CxxType(cxx_type) = api {
+            assert_eq!(cxx_type.name.cxx.to_string(), expected);
+        } else {
+            unreachable!()
+        }
+    }
+
+    fn make_api(ns: Option<&str>, ident: &str) -> Api {
+        let ns = ns.map_or(Namespace::ROOT, |ns| syn::parse_str(ns).unwrap());
+        Api::CxxType(ExternType {
+            lang: Lang::Rust,
+            doc: Doc::new(),
+            derives: Vec::new(),
+            attrs: OtherAttrs::none(),
+            visibility: Token![pub](Span::call_site()),
+            type_token: Token![type](Span::call_site()),
+            name: Pair {
+                namespace: ns,
+                cxx: ForeignName::parse(ident, Span::call_site()).unwrap(),
+                rust: Ident::new(ident, Span::call_site()),
+            },
+            generics: Lifetimes {
+                lt_token: None,
+                lifetimes: Punctuated::new(),
+                gt_token: None,
+            },
+            colon_token: None,
+            bounds: Vec::new(),
+            semi_token: Token![;](Span::call_site()),
+            trusted: false,
+        })
+    }
+}
diff --git a/src/gen/out.rs b/src/gen/out.rs
new file mode 100644 (file)
index 0000000..3b4d739
--- /dev/null
@@ -0,0 +1,210 @@
+use crate::gen::block::Block;
+use crate::gen::builtin::Builtins;
+use crate::gen::include::Includes;
+use crate::gen::Opt;
+use crate::syntax::namespace::Namespace;
+use crate::syntax::Types;
+use std::cell::RefCell;
+use std::fmt::{self, Arguments, Write};
+
+pub(crate) struct OutFile<'a> {
+    pub header: bool,
+    pub opt: &'a Opt,
+    pub types: &'a Types<'a>,
+    pub include: Includes<'a>,
+    pub builtin: Builtins<'a>,
+    content: RefCell<Content<'a>>,
+}
+
+#[derive(Default)]
+pub struct Content<'a> {
+    bytes: String,
+    namespace: &'a Namespace,
+    blocks: Vec<BlockBoundary<'a>>,
+    section_pending: bool,
+    blocks_pending: usize,
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+enum BlockBoundary<'a> {
+    Begin(Block<'a>),
+    End(Block<'a>),
+}
+
+impl<'a> OutFile<'a> {
+    pub fn new(header: bool, opt: &'a Opt, types: &'a Types) -> Self {
+        OutFile {
+            header,
+            opt,
+            types,
+            include: Includes::new(),
+            builtin: Builtins::new(),
+            content: RefCell::new(Content::new()),
+        }
+    }
+
+    // Write a blank line if the preceding section had any contents.
+    pub fn next_section(&mut self) {
+        self.content.get_mut().next_section();
+    }
+
+    pub fn begin_block(&mut self, block: Block<'a>) {
+        self.content.get_mut().begin_block(block);
+    }
+
+    pub fn end_block(&mut self, block: Block<'a>) {
+        self.content.get_mut().end_block(block);
+    }
+
+    pub fn set_namespace(&mut self, namespace: &'a Namespace) {
+        self.content.get_mut().set_namespace(namespace);
+    }
+
+    pub fn write_fmt(&self, args: Arguments) {
+        let content = &mut *self.content.borrow_mut();
+        Write::write_fmt(content, args).unwrap();
+    }
+
+    pub fn content(&mut self) -> Vec<u8> {
+        self.flush();
+        let include = &self.include.content.bytes;
+        let builtin = &self.builtin.content.bytes;
+        let content = &self.content.get_mut().bytes;
+        let len = include.len() + builtin.len() + content.len() + 2;
+        let mut out = String::with_capacity(len);
+        out.push_str(include);
+        if !out.is_empty() && !builtin.is_empty() {
+            out.push('\n');
+        }
+        out.push_str(builtin);
+        if !out.is_empty() && !content.is_empty() {
+            out.push('\n');
+        }
+        out.push_str(content);
+        if out.is_empty() {
+            out.push_str("// empty\n");
+        }
+        out.into_bytes()
+    }
+
+    fn flush(&mut self) {
+        self.include.content.flush();
+        self.builtin.content.flush();
+        self.content.get_mut().flush();
+    }
+}
+
+impl<'a> Write for Content<'a> {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        self.write(s);
+        Ok(())
+    }
+}
+
+impl<'a> PartialEq for Content<'a> {
+    fn eq(&self, _other: &Self) -> bool {
+        true
+    }
+}
+
+impl<'a> Content<'a> {
+    fn new() -> Self {
+        Content::default()
+    }
+
+    pub fn next_section(&mut self) {
+        self.section_pending = true;
+    }
+
+    pub fn begin_block(&mut self, block: Block<'a>) {
+        self.push_block_boundary(BlockBoundary::Begin(block));
+    }
+
+    pub fn end_block(&mut self, block: Block<'a>) {
+        self.push_block_boundary(BlockBoundary::End(block));
+    }
+
+    pub fn set_namespace(&mut self, namespace: &'a Namespace) {
+        for name in self.namespace.iter().rev() {
+            self.end_block(Block::UserDefinedNamespace(name));
+        }
+        for name in namespace {
+            self.begin_block(Block::UserDefinedNamespace(name));
+        }
+        self.namespace = namespace;
+    }
+
+    pub fn write_fmt(&mut self, args: Arguments) {
+        Write::write_fmt(self, args).unwrap();
+    }
+
+    fn write(&mut self, b: &str) {
+        if !b.is_empty() {
+            if self.blocks_pending > 0 {
+                self.flush_blocks();
+            }
+            if self.section_pending && !self.bytes.is_empty() {
+                self.bytes.push('\n');
+            }
+            self.bytes.push_str(b);
+            self.section_pending = false;
+            self.blocks_pending = 0;
+        }
+    }
+
+    fn push_block_boundary(&mut self, boundary: BlockBoundary<'a>) {
+        if self.blocks_pending > 0 && boundary == self.blocks.last().unwrap().rev() {
+            self.blocks.pop();
+            self.blocks_pending -= 1;
+        } else {
+            self.blocks.push(boundary);
+            self.blocks_pending += 1;
+        }
+    }
+
+    fn flush(&mut self) {
+        self.set_namespace(Default::default());
+        if self.blocks_pending > 0 {
+            self.flush_blocks();
+        }
+    }
+
+    fn flush_blocks(&mut self) {
+        self.section_pending = !self.bytes.is_empty();
+        let mut read = self.blocks.len() - self.blocks_pending;
+        let mut write = read;
+
+        while read < self.blocks.len() {
+            match self.blocks[read] {
+                BlockBoundary::Begin(begin_block) => {
+                    if self.section_pending {
+                        self.bytes.push('\n');
+                        self.section_pending = false;
+                    }
+                    Block::write_begin(begin_block, &mut self.bytes);
+                    self.blocks[write] = BlockBoundary::Begin(begin_block);
+                    write += 1;
+                }
+                BlockBoundary::End(end_block) => {
+                    write = write.checked_sub(1).unwrap();
+                    let begin_block = self.blocks[write];
+                    assert_eq!(begin_block, BlockBoundary::Begin(end_block));
+                    Block::write_end(end_block, &mut self.bytes);
+                    self.section_pending = true;
+                }
+            }
+            read += 1;
+        }
+
+        self.blocks.truncate(write);
+    }
+}
+
+impl<'a> BlockBoundary<'a> {
+    fn rev(self) -> BlockBoundary<'a> {
+        match self {
+            BlockBoundary::Begin(block) => BlockBoundary::End(block),
+            BlockBoundary::End(block) => BlockBoundary::Begin(block),
+        }
+    }
+}
diff --git a/src/gen/write.rs b/src/gen/write.rs
new file mode 100644 (file)
index 0000000..cc98902
--- /dev/null
@@ -0,0 +1,1912 @@
+use crate::gen::block::Block;
+use crate::gen::nested::NamespaceEntries;
+use crate::gen::out::OutFile;
+use crate::gen::{builtin, include, Opt};
+use crate::syntax::atom::Atom::{self, *};
+use crate::syntax::instantiate::{ImplKey, NamedImplKey};
+use crate::syntax::map::UnorderedMap as Map;
+use crate::syntax::set::UnorderedSet;
+use crate::syntax::symbol::Symbol;
+use crate::syntax::trivial::{self, TrivialReason};
+use crate::syntax::{
+    derive, mangle, Api, Doc, Enum, EnumRepr, ExternFn, ExternType, Pair, Signature, Struct, Trait,
+    Type, TypeAlias, Types, Var,
+};
+use proc_macro2::Ident;
+
+pub(super) fn gen(apis: &[Api], types: &Types, opt: &Opt, header: bool) -> Vec<u8> {
+    let mut out_file = OutFile::new(header, opt, types);
+    let out = &mut out_file;
+
+    pick_includes_and_builtins(out, apis);
+    out.include.extend(&opt.include);
+
+    write_forward_declarations(out, apis);
+    write_data_structures(out, apis);
+    write_functions(out, apis);
+    write_generic_instantiations(out);
+
+    builtin::write(out);
+    include::write(out);
+
+    out_file.content()
+}
+
+fn write_forward_declarations(out: &mut OutFile, apis: &[Api]) {
+    let needs_forward_declaration = |api: &&Api| match api {
+        Api::Struct(_) | Api::CxxType(_) | Api::RustType(_) => true,
+        Api::Enum(enm) => !out.types.cxx.contains(&enm.name.rust),
+        _ => false,
+    };
+
+    let apis_by_namespace =
+        NamespaceEntries::new(apis.iter().filter(needs_forward_declaration).collect());
+
+    write(out, &apis_by_namespace, 0);
+
+    fn write(out: &mut OutFile, ns_entries: &NamespaceEntries, indent: usize) {
+        let apis = ns_entries.direct_content();
+
+        for api in apis {
+            write!(out, "{:1$}", "", indent);
+            match api {
+                Api::Struct(strct) => write_struct_decl(out, &strct.name),
+                Api::Enum(enm) => write_enum_decl(out, enm),
+                Api::CxxType(ety) => write_struct_using(out, &ety.name),
+                Api::RustType(ety) => write_struct_decl(out, &ety.name),
+                _ => unreachable!(),
+            }
+        }
+
+        for (namespace, nested_ns_entries) in ns_entries.nested_content() {
+            writeln!(out, "{:2$}namespace {} {{", "", namespace, indent);
+            write(out, nested_ns_entries, indent + 2);
+            writeln!(out, "{:1$}}}", "", indent);
+        }
+    }
+}
+
+fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
+    let mut methods_for_type = Map::new();
+    for api in apis {
+        if let Api::CxxFunction(efn) | Api::RustFunction(efn) = api {
+            if let Some(receiver) = &efn.sig.receiver {
+                methods_for_type
+                    .entry(&receiver.ty.rust)
+                    .or_insert_with(Vec::new)
+                    .push(efn);
+            }
+        }
+    }
+
+    let mut structs_written = UnorderedSet::new();
+    let mut toposorted_structs = out.types.toposorted_structs.iter();
+    for api in apis {
+        match api {
+            Api::Struct(strct) if !structs_written.contains(&strct.name.rust) => {
+                for next in &mut toposorted_structs {
+                    if !out.types.cxx.contains(&strct.name.rust) {
+                        out.next_section();
+                        let methods = methods_for_type
+                            .get(&strct.name.rust)
+                            .map(Vec::as_slice)
+                            .unwrap_or_default();
+                        write_struct(out, next, methods);
+                    }
+                    structs_written.insert(&next.name.rust);
+                    if next.name.rust == strct.name.rust {
+                        break;
+                    }
+                }
+            }
+            Api::Enum(enm) => {
+                out.next_section();
+                if !out.types.cxx.contains(&enm.name.rust) {
+                    write_enum(out, enm);
+                } else if !enm.variants_from_header {
+                    check_enum(out, enm);
+                }
+            }
+            Api::RustType(ety) => {
+                out.next_section();
+                let methods = methods_for_type
+                    .get(&ety.name.rust)
+                    .map(Vec::as_slice)
+                    .unwrap_or_default();
+                write_opaque_type(out, ety, methods);
+            }
+            _ => {}
+        }
+    }
+
+    if out.header {
+        return;
+    }
+
+    out.set_namespace(Default::default());
+
+    out.next_section();
+    for api in apis {
+        if let Api::TypeAlias(ety) = api {
+            if let Some(reasons) = out.types.required_trivial.get(&ety.name.rust) {
+                check_trivial_extern_type(out, ety, reasons)
+            }
+        }
+    }
+}
+
+fn write_functions<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
+    if !out.header {
+        for api in apis {
+            match api {
+                Api::Struct(strct) => write_struct_operator_decls(out, strct),
+                Api::RustType(ety) => write_opaque_type_layout_decls(out, ety),
+                Api::CxxFunction(efn) => write_cxx_function_shim(out, efn),
+                Api::RustFunction(efn) => write_rust_function_decl(out, efn),
+                _ => {}
+            }
+        }
+
+        write_std_specializations(out, apis);
+    }
+
+    for api in apis {
+        match api {
+            Api::Struct(strct) => write_struct_operators(out, strct),
+            Api::RustType(ety) => write_opaque_type_layout(out, ety),
+            Api::RustFunction(efn) => {
+                out.next_section();
+                write_rust_function_shim(out, efn);
+            }
+            _ => {}
+        }
+    }
+}
+
+fn write_std_specializations(out: &mut OutFile, apis: &[Api]) {
+    out.set_namespace(Default::default());
+    out.begin_block(Block::Namespace("std"));
+
+    for api in apis {
+        if let Api::Struct(strct) = api {
+            if derive::contains(&strct.derives, Trait::Hash) {
+                out.next_section();
+                out.include.cstddef = true;
+                out.include.functional = true;
+                let qualified = strct.name.to_fully_qualified();
+                writeln!(out, "template <> struct hash<{}> {{", qualified);
+                writeln!(
+                    out,
+                    "  ::std::size_t operator()(const {} &self) const noexcept {{",
+                    qualified,
+                );
+                let link_name = mangle::operator(&strct.name, "hash");
+                write!(out, "    return ::");
+                for name in &strct.name.namespace {
+                    write!(out, "{}::", name);
+                }
+                writeln!(out, "{}(self);", link_name);
+                writeln!(out, "  }}");
+                writeln!(out, "}};");
+            }
+        }
+    }
+
+    out.end_block(Block::Namespace("std"));
+}
+
+fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
+    for api in apis {
+        if let Api::Include(include) = api {
+            out.include.insert(include);
+        }
+    }
+
+    for ty in out.types {
+        match ty {
+            Type::Ident(ident) => match Atom::from(&ident.rust) {
+                Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(I8) | Some(I16) | Some(I32)
+                | Some(I64) => out.include.cstdint = true,
+                Some(Usize) => out.include.cstddef = true,
+                Some(Isize) => out.builtin.rust_isize = true,
+                Some(CxxString) => out.include.string = true,
+                Some(RustString) => out.builtin.rust_string = true,
+                Some(Bool) | Some(Char) | Some(F32) | Some(F64) | None => {}
+            },
+            Type::RustBox(_) => out.builtin.rust_box = true,
+            Type::RustVec(_) => out.builtin.rust_vec = true,
+            Type::UniquePtr(_) => out.include.memory = true,
+            Type::SharedPtr(_) | Type::WeakPtr(_) => out.include.memory = true,
+            Type::Str(_) => out.builtin.rust_str = true,
+            Type::CxxVector(_) => out.include.vector = true,
+            Type::Fn(_) => out.builtin.rust_fn = true,
+            Type::SliceRef(_) => out.builtin.rust_slice = true,
+            Type::Array(_) => out.include.array = true,
+            Type::Ref(_) | Type::Void(_) | Type::Ptr(_) => {}
+        }
+    }
+}
+
+fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&ExternFn]) {
+    let operator_eq = derive::contains(&strct.derives, Trait::PartialEq);
+    let operator_ord = derive::contains(&strct.derives, Trait::PartialOrd);
+
+    out.set_namespace(&strct.name.namespace);
+    let guard = format!("CXXBRIDGE1_STRUCT_{}", strct.name.to_symbol());
+    writeln!(out, "#ifndef {}", guard);
+    writeln!(out, "#define {}", guard);
+    for line in strct.doc.to_string().lines() {
+        writeln!(out, "//{}", line);
+    }
+    writeln!(out, "struct {} final {{", strct.name.cxx);
+
+    for field in &strct.fields {
+        for line in field.doc.to_string().lines() {
+            writeln!(out, "  //{}", line);
+        }
+        write!(out, "  ");
+        write_type_space(out, &field.ty);
+        writeln!(out, "{};", field.name.cxx);
+    }
+
+    out.next_section();
+
+    for method in methods {
+        if !method.doc.is_empty() {
+            out.next_section();
+        }
+        for line in method.doc.to_string().lines() {
+            writeln!(out, "  //{}", line);
+        }
+        write!(out, "  ");
+        let sig = &method.sig;
+        let local_name = method.name.cxx.to_string();
+        let indirect_call = false;
+        write_rust_function_shim_decl(out, &local_name, sig, indirect_call);
+        writeln!(out, ";");
+        if !method.doc.is_empty() {
+            out.next_section();
+        }
+    }
+
+    if operator_eq {
+        writeln!(
+            out,
+            "  bool operator==(const {} &) const noexcept;",
+            strct.name.cxx,
+        );
+        writeln!(
+            out,
+            "  bool operator!=(const {} &) const noexcept;",
+            strct.name.cxx,
+        );
+    }
+
+    if operator_ord {
+        writeln!(
+            out,
+            "  bool operator<(const {} &) const noexcept;",
+            strct.name.cxx,
+        );
+        writeln!(
+            out,
+            "  bool operator<=(const {} &) const noexcept;",
+            strct.name.cxx,
+        );
+        writeln!(
+            out,
+            "  bool operator>(const {} &) const noexcept;",
+            strct.name.cxx,
+        );
+        writeln!(
+            out,
+            "  bool operator>=(const {} &) const noexcept;",
+            strct.name.cxx,
+        );
+    }
+
+    out.include.type_traits = true;
+    writeln!(out, "  using IsRelocatable = ::std::true_type;");
+
+    writeln!(out, "}};");
+    writeln!(out, "#endif // {}", guard);
+}
+
+fn write_struct_decl(out: &mut OutFile, ident: &Pair) {
+    writeln!(out, "struct {};", ident.cxx);
+}
+
+fn write_enum_decl(out: &mut OutFile, enm: &Enum) {
+    let repr = match &enm.repr {
+        EnumRepr::Foreign { .. } => return,
+        EnumRepr::Native { atom, .. } => *atom,
+    };
+    write!(out, "enum class {} : ", enm.name.cxx);
+    write_atom(out, repr);
+    writeln!(out, ";");
+}
+
+fn write_struct_using(out: &mut OutFile, ident: &Pair) {
+    writeln!(out, "using {} = {};", ident.cxx, ident.to_fully_qualified());
+}
+
+fn write_opaque_type<'a>(out: &mut OutFile<'a>, ety: &'a ExternType, methods: &[&ExternFn]) {
+    out.set_namespace(&ety.name.namespace);
+    let guard = format!("CXXBRIDGE1_STRUCT_{}", ety.name.to_symbol());
+    writeln!(out, "#ifndef {}", guard);
+    writeln!(out, "#define {}", guard);
+    for line in ety.doc.to_string().lines() {
+        writeln!(out, "//{}", line);
+    }
+
+    out.builtin.opaque = true;
+    writeln!(
+        out,
+        "struct {} final : public ::rust::Opaque {{",
+        ety.name.cxx,
+    );
+
+    for (i, method) in methods.iter().enumerate() {
+        if i > 0 && !method.doc.is_empty() {
+            out.next_section();
+        }
+        for line in method.doc.to_string().lines() {
+            writeln!(out, "  //{}", line);
+        }
+        write!(out, "  ");
+        let sig = &method.sig;
+        let local_name = method.name.cxx.to_string();
+        let indirect_call = false;
+        write_rust_function_shim_decl(out, &local_name, sig, indirect_call);
+        writeln!(out, ";");
+        if !method.doc.is_empty() {
+            out.next_section();
+        }
+    }
+
+    writeln!(out, "  ~{}() = delete;", ety.name.cxx);
+    writeln!(out);
+
+    out.builtin.layout = true;
+    out.include.cstddef = true;
+    writeln!(out, "private:");
+    writeln!(out, "  friend ::rust::layout;");
+    writeln!(out, "  struct layout {{");
+    writeln!(out, "    static ::std::size_t size() noexcept;");
+    writeln!(out, "    static ::std::size_t align() noexcept;");
+    writeln!(out, "  }};");
+    writeln!(out, "}};");
+    writeln!(out, "#endif // {}", guard);
+}
+
+fn write_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) {
+    let repr = match &enm.repr {
+        EnumRepr::Foreign { .. } => return,
+        EnumRepr::Native { atom, .. } => *atom,
+    };
+    out.set_namespace(&enm.name.namespace);
+    let guard = format!("CXXBRIDGE1_ENUM_{}", enm.name.to_symbol());
+    writeln!(out, "#ifndef {}", guard);
+    writeln!(out, "#define {}", guard);
+    for line in enm.doc.to_string().lines() {
+        writeln!(out, "//{}", line);
+    }
+    write!(out, "enum class {} : ", enm.name.cxx);
+    write_atom(out, repr);
+    writeln!(out, " {{");
+    for variant in &enm.variants {
+        for line in variant.doc.to_string().lines() {
+            writeln!(out, "  //{}", line);
+        }
+        writeln!(out, "  {} = {},", variant.name.cxx, variant.discriminant);
+    }
+    writeln!(out, "}};");
+    writeln!(out, "#endif // {}", guard);
+}
+
+fn check_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) {
+    let repr = match &enm.repr {
+        EnumRepr::Foreign { .. } => return,
+        EnumRepr::Native { atom, .. } => *atom,
+    };
+    out.set_namespace(&enm.name.namespace);
+    out.include.type_traits = true;
+    writeln!(
+        out,
+        "static_assert(::std::is_enum<{}>::value, \"expected enum\");",
+        enm.name.cxx,
+    );
+    write!(out, "static_assert(sizeof({}) == sizeof(", enm.name.cxx);
+    write_atom(out, repr);
+    writeln!(out, "), \"incorrect size\");");
+    for variant in &enm.variants {
+        write!(out, "static_assert(static_cast<");
+        write_atom(out, repr);
+        writeln!(
+            out,
+            ">({}::{}) == {}, \"disagrees with the value in #[cxx::bridge]\");",
+            enm.name.cxx, variant.name.cxx, variant.discriminant,
+        );
+    }
+}
+
+fn check_trivial_extern_type(out: &mut OutFile, alias: &TypeAlias, reasons: &[TrivialReason]) {
+    // NOTE: The following static assertion is just nice-to-have and not
+    // necessary for soundness. That's because triviality is always declared by
+    // the user in the form of an unsafe impl of cxx::ExternType:
+    //
+    //     unsafe impl ExternType for MyType {
+    //         type Id = cxx::type_id!("...");
+    //         type Kind = cxx::kind::Trivial;
+    //     }
+    //
+    // Since the user went on the record with their unsafe impl to unsafely
+    // claim they KNOW that the type is trivial, it's fine for that to be on
+    // them if that were wrong. However, in practice correctly reasoning about
+    // the relocatability of C++ types is challenging, particularly if the type
+    // definition were to change over time, so for now we add this check.
+    //
+    // There may be legitimate reasons to opt out of this assertion for support
+    // of types that the programmer knows are soundly Rust-movable despite not
+    // being recognized as such by the C++ type system due to a move constructor
+    // or destructor. To opt out of the relocatability check, they need to do
+    // one of the following things in any header used by `include!` in their
+    // bridge.
+    //
+    //      --- if they define the type:
+    //      struct MyType {
+    //        ...
+    //    +   using IsRelocatable = std::true_type;
+    //      };
+    //
+    //      --- otherwise:
+    //    + template <>
+    //    + struct rust::IsRelocatable<MyType> : std::true_type {};
+    //
+
+    let id = alias.name.to_fully_qualified();
+    out.builtin.relocatable = true;
+    writeln!(out, "static_assert(");
+    writeln!(out, "    ::rust::IsRelocatable<{}>::value,", id);
+    writeln!(
+        out,
+        "    \"type {} should be trivially move constructible and trivially destructible in C++ to be used as {} in Rust\");",
+        id.trim_start_matches("::"),
+        trivial::as_what(&alias.name, reasons),
+    );
+}
+
+fn write_struct_operator_decls<'a>(out: &mut OutFile<'a>, strct: &'a Struct) {
+    out.set_namespace(&strct.name.namespace);
+    out.begin_block(Block::ExternC);
+
+    if derive::contains(&strct.derives, Trait::PartialEq) {
+        let link_name = mangle::operator(&strct.name, "eq");
+        writeln!(
+            out,
+            "bool {}(const {1} &, const {1} &) noexcept;",
+            link_name, strct.name.cxx,
+        );
+
+        if !derive::contains(&strct.derives, Trait::Eq) {
+            let link_name = mangle::operator(&strct.name, "ne");
+            writeln!(
+                out,
+                "bool {}(const {1} &, const {1} &) noexcept;",
+                link_name, strct.name.cxx,
+            );
+        }
+    }
+
+    if derive::contains(&strct.derives, Trait::PartialOrd) {
+        let link_name = mangle::operator(&strct.name, "lt");
+        writeln!(
+            out,
+            "bool {}(const {1} &, const {1} &) noexcept;",
+            link_name, strct.name.cxx,
+        );
+
+        let link_name = mangle::operator(&strct.name, "le");
+        writeln!(
+            out,
+            "bool {}(const {1} &, const {1} &) noexcept;",
+            link_name, strct.name.cxx,
+        );
+
+        if !derive::contains(&strct.derives, Trait::Ord) {
+            let link_name = mangle::operator(&strct.name, "gt");
+            writeln!(
+                out,
+                "bool {}(const {1} &, const {1} &) noexcept;",
+                link_name, strct.name.cxx,
+            );
+
+            let link_name = mangle::operator(&strct.name, "ge");
+            writeln!(
+                out,
+                "bool {}(const {1} &, const {1} &) noexcept;",
+                link_name, strct.name.cxx,
+            );
+        }
+    }
+
+    if derive::contains(&strct.derives, Trait::Hash) {
+        out.include.cstddef = true;
+        let link_name = mangle::operator(&strct.name, "hash");
+        writeln!(
+            out,
+            "::std::size_t {}(const {} &) noexcept;",
+            link_name, strct.name.cxx,
+        );
+    }
+
+    out.end_block(Block::ExternC);
+}
+
+fn write_struct_operators<'a>(out: &mut OutFile<'a>, strct: &'a Struct) {
+    if out.header {
+        return;
+    }
+
+    out.set_namespace(&strct.name.namespace);
+
+    if derive::contains(&strct.derives, Trait::PartialEq) {
+        out.next_section();
+        writeln!(
+            out,
+            "bool {0}::operator==(const {0} &rhs) const noexcept {{",
+            strct.name.cxx,
+        );
+        let link_name = mangle::operator(&strct.name, "eq");
+        writeln!(out, "  return {}(*this, rhs);", link_name);
+        writeln!(out, "}}");
+
+        out.next_section();
+        writeln!(
+            out,
+            "bool {0}::operator!=(const {0} &rhs) const noexcept {{",
+            strct.name.cxx,
+        );
+        if derive::contains(&strct.derives, Trait::Eq) {
+            writeln!(out, "  return !(*this == rhs);");
+        } else {
+            let link_name = mangle::operator(&strct.name, "ne");
+            writeln!(out, "  return {}(*this, rhs);", link_name);
+        }
+        writeln!(out, "}}");
+    }
+
+    if derive::contains(&strct.derives, Trait::PartialOrd) {
+        out.next_section();
+        writeln!(
+            out,
+            "bool {0}::operator<(const {0} &rhs) const noexcept {{",
+            strct.name.cxx,
+        );
+        let link_name = mangle::operator(&strct.name, "lt");
+        writeln!(out, "  return {}(*this, rhs);", link_name);
+        writeln!(out, "}}");
+
+        out.next_section();
+        writeln!(
+            out,
+            "bool {0}::operator<=(const {0} &rhs) const noexcept {{",
+            strct.name.cxx,
+        );
+        let link_name = mangle::operator(&strct.name, "le");
+        writeln!(out, "  return {}(*this, rhs);", link_name);
+        writeln!(out, "}}");
+
+        out.next_section();
+        writeln!(
+            out,
+            "bool {0}::operator>(const {0} &rhs) const noexcept {{",
+            strct.name.cxx,
+        );
+        if derive::contains(&strct.derives, Trait::Ord) {
+            writeln!(out, "  return !(*this <= rhs);");
+        } else {
+            let link_name = mangle::operator(&strct.name, "gt");
+            writeln!(out, "  return {}(*this, rhs);", link_name);
+        }
+        writeln!(out, "}}");
+
+        out.next_section();
+        writeln!(
+            out,
+            "bool {0}::operator>=(const {0} &rhs) const noexcept {{",
+            strct.name.cxx,
+        );
+        if derive::contains(&strct.derives, Trait::Ord) {
+            writeln!(out, "  return !(*this < rhs);");
+        } else {
+            let link_name = mangle::operator(&strct.name, "ge");
+            writeln!(out, "  return {}(*this, rhs);", link_name);
+        }
+        writeln!(out, "}}");
+    }
+}
+
+fn write_opaque_type_layout_decls<'a>(out: &mut OutFile<'a>, ety: &'a ExternType) {
+    out.set_namespace(&ety.name.namespace);
+    out.begin_block(Block::ExternC);
+
+    let link_name = mangle::operator(&ety.name, "sizeof");
+    writeln!(out, "::std::size_t {}() noexcept;", link_name);
+
+    let link_name = mangle::operator(&ety.name, "alignof");
+    writeln!(out, "::std::size_t {}() noexcept;", link_name);
+
+    out.end_block(Block::ExternC);
+}
+
+fn write_opaque_type_layout<'a>(out: &mut OutFile<'a>, ety: &'a ExternType) {
+    if out.header {
+        return;
+    }
+
+    out.set_namespace(&ety.name.namespace);
+
+    out.next_section();
+    let link_name = mangle::operator(&ety.name, "sizeof");
+    writeln!(
+        out,
+        "::std::size_t {}::layout::size() noexcept {{",
+        ety.name.cxx,
+    );
+    writeln!(out, "  return {}();", link_name);
+    writeln!(out, "}}");
+
+    out.next_section();
+    let link_name = mangle::operator(&ety.name, "alignof");
+    writeln!(
+        out,
+        "::std::size_t {}::layout::align() noexcept {{",
+        ety.name.cxx,
+    );
+    writeln!(out, "  return {}();", link_name);
+    writeln!(out, "}}");
+}
+
+fn begin_function_definition(out: &mut OutFile) {
+    if let Some(annotation) = &out.opt.cxx_impl_annotations {
+        write!(out, "{} ", annotation);
+    }
+}
+
+fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
+    out.next_section();
+    out.set_namespace(&efn.name.namespace);
+    out.begin_block(Block::ExternC);
+    begin_function_definition(out);
+    if efn.throws {
+        out.builtin.ptr_len = true;
+        write!(out, "::rust::repr::PtrLen ");
+    } else {
+        write_extern_return_type_space(out, &efn.ret);
+    }
+    let mangled = mangle::extern_fn(efn, out.types);
+    write!(out, "{}(", mangled);
+    if let Some(receiver) = &efn.receiver {
+        if !receiver.mutable {
+            write!(out, "const ");
+        }
+        write!(
+            out,
+            "{} &self",
+            out.types.resolve(&receiver.ty).name.to_fully_qualified(),
+        );
+    }
+    for (i, arg) in efn.args.iter().enumerate() {
+        if i > 0 || efn.receiver.is_some() {
+            write!(out, ", ");
+        }
+        if arg.ty == RustString {
+            write!(out, "const ");
+        } else if let Type::RustVec(_) = arg.ty {
+            write!(out, "const ");
+        }
+        write_extern_arg(out, arg);
+    }
+    let indirect_return = indirect_return(efn, out.types);
+    if indirect_return {
+        if !efn.args.is_empty() || efn.receiver.is_some() {
+            write!(out, ", ");
+        }
+        write_indirect_return_type_space(out, efn.ret.as_ref().unwrap());
+        write!(out, "*return$");
+    }
+    writeln!(out, ") noexcept {{");
+    write!(out, "  ");
+    write_return_type(out, &efn.ret);
+    match &efn.receiver {
+        None => write!(out, "(*{}$)(", efn.name.rust),
+        Some(receiver) => write!(
+            out,
+            "({}::*{}$)(",
+            out.types.resolve(&receiver.ty).name.to_fully_qualified(),
+            efn.name.rust,
+        ),
+    }
+    for (i, arg) in efn.args.iter().enumerate() {
+        if i > 0 {
+            write!(out, ", ");
+        }
+        write_type(out, &arg.ty);
+    }
+    write!(out, ")");
+    if let Some(receiver) = &efn.receiver {
+        if !receiver.mutable {
+            write!(out, " const");
+        }
+    }
+    write!(out, " = ");
+    match &efn.receiver {
+        None => write!(out, "{}", efn.name.to_fully_qualified()),
+        Some(receiver) => write!(
+            out,
+            "&{}::{}",
+            out.types.resolve(&receiver.ty).name.to_fully_qualified(),
+            efn.name.cxx,
+        ),
+    }
+    writeln!(out, ";");
+    write!(out, "  ");
+    if efn.throws {
+        out.builtin.ptr_len = true;
+        out.builtin.trycatch = true;
+        writeln!(out, "::rust::repr::PtrLen throw$;");
+        writeln!(out, "  ::rust::behavior::trycatch(");
+        writeln!(out, "      [&] {{");
+        write!(out, "        ");
+    }
+    if indirect_return {
+        out.include.new = true;
+        write!(out, "new (return$) ");
+        write_indirect_return_type(out, efn.ret.as_ref().unwrap());
+        write!(out, "(");
+    } else if efn.ret.is_some() {
+        write!(out, "return ");
+    }
+    match &efn.ret {
+        Some(Type::Ref(_)) => write!(out, "&"),
+        Some(Type::Str(_)) if !indirect_return => {
+            out.builtin.rust_str_repr = true;
+            write!(out, "::rust::impl<::rust::Str>::repr(");
+        }
+        Some(ty @ Type::SliceRef(_)) if !indirect_return => {
+            out.builtin.rust_slice_repr = true;
+            write!(out, "::rust::impl<");
+            write_type(out, ty);
+            write!(out, ">::repr(");
+        }
+        _ => {}
+    }
+    match &efn.receiver {
+        None => write!(out, "{}$(", efn.name.rust),
+        Some(_) => write!(out, "(self.*{}$)(", efn.name.rust),
+    }
+    for (i, arg) in efn.args.iter().enumerate() {
+        if i > 0 {
+            write!(out, ", ");
+        }
+        if let Type::RustBox(_) = &arg.ty {
+            write_type(out, &arg.ty);
+            write!(out, "::from_raw({})", arg.name.cxx);
+        } else if let Type::UniquePtr(_) = &arg.ty {
+            write_type(out, &arg.ty);
+            write!(out, "({})", arg.name.cxx);
+        } else if arg.ty == RustString {
+            out.builtin.unsafe_bitcopy = true;
+            write!(
+                out,
+                "::rust::String(::rust::unsafe_bitcopy, *{})",
+                arg.name.cxx,
+            );
+        } else if let Type::RustVec(_) = arg.ty {
+            out.builtin.unsafe_bitcopy = true;
+            write_type(out, &arg.ty);
+            write!(out, "(::rust::unsafe_bitcopy, *{})", arg.name.cxx);
+        } else if out.types.needs_indirect_abi(&arg.ty) {
+            out.include.utility = true;
+            write!(out, "::std::move(*{})", arg.name.cxx);
+        } else {
+            write!(out, "{}", arg.name.cxx);
+        }
+    }
+    write!(out, ")");
+    match &efn.ret {
+        Some(Type::RustBox(_)) => write!(out, ".into_raw()"),
+        Some(Type::UniquePtr(_)) => write!(out, ".release()"),
+        Some(Type::Str(_)) | Some(Type::SliceRef(_)) if !indirect_return => write!(out, ")"),
+        _ => {}
+    }
+    if indirect_return {
+        write!(out, ")");
+    }
+    writeln!(out, ";");
+    if efn.throws {
+        out.include.cstring = true;
+        out.builtin.exception = true;
+        writeln!(out, "        throw$.ptr = nullptr;");
+        writeln!(out, "      }},");
+        writeln!(out, "      [&](const char *catch$) noexcept {{");
+        writeln!(out, "        throw$.len = ::std::strlen(catch$);");
+        writeln!(
+            out,
+            "        throw$.ptr = const_cast<char *>(::cxxbridge1$exception(catch$, throw$.len));",
+        );
+        writeln!(out, "      }});");
+        writeln!(out, "  return throw$;");
+    }
+    writeln!(out, "}}");
+    for arg in &efn.args {
+        if let Type::Fn(f) = &arg.ty {
+            let var = &arg.name;
+            write_function_pointer_trampoline(out, efn, var, f);
+        }
+    }
+    out.end_block(Block::ExternC);
+}
+
+fn write_function_pointer_trampoline(out: &mut OutFile, efn: &ExternFn, var: &Pair, f: &Signature) {
+    let r_trampoline = mangle::r_trampoline(efn, var, out.types);
+    let indirect_call = true;
+    write_rust_function_decl_impl(out, &r_trampoline, f, indirect_call);
+
+    out.next_section();
+    let c_trampoline = mangle::c_trampoline(efn, var, out.types).to_string();
+    let doc = Doc::new();
+    write_rust_function_shim_impl(out, &c_trampoline, f, &doc, &r_trampoline, indirect_call);
+}
+
+fn write_rust_function_decl<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
+    out.set_namespace(&efn.name.namespace);
+    out.begin_block(Block::ExternC);
+    let link_name = mangle::extern_fn(efn, out.types);
+    let indirect_call = false;
+    write_rust_function_decl_impl(out, &link_name, efn, indirect_call);
+    out.end_block(Block::ExternC);
+}
+
+fn write_rust_function_decl_impl(
+    out: &mut OutFile,
+    link_name: &Symbol,
+    sig: &Signature,
+    indirect_call: bool,
+) {
+    out.next_section();
+    if sig.throws {
+        out.builtin.ptr_len = true;
+        write!(out, "::rust::repr::PtrLen ");
+    } else {
+        write_extern_return_type_space(out, &sig.ret);
+    }
+    write!(out, "{}(", link_name);
+    let mut needs_comma = false;
+    if let Some(receiver) = &sig.receiver {
+        if !receiver.mutable {
+            write!(out, "const ");
+        }
+        write!(
+            out,
+            "{} &self",
+            out.types.resolve(&receiver.ty).name.to_fully_qualified(),
+        );
+        needs_comma = true;
+    }
+    for arg in &sig.args {
+        if needs_comma {
+            write!(out, ", ");
+        }
+        write_extern_arg(out, arg);
+        needs_comma = true;
+    }
+    if indirect_return(sig, out.types) {
+        if needs_comma {
+            write!(out, ", ");
+        }
+        match sig.ret.as_ref().unwrap() {
+            Type::Ref(ret) => {
+                write_pointee_type(out, &ret.inner, ret.mutable);
+                write!(out, " *");
+            }
+            ret => write_type_space(out, ret),
+        }
+        write!(out, "*return$");
+        needs_comma = true;
+    }
+    if indirect_call {
+        if needs_comma {
+            write!(out, ", ");
+        }
+        write!(out, "void *");
+    }
+    writeln!(out, ") noexcept;");
+}
+
+fn write_rust_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
+    out.set_namespace(&efn.name.namespace);
+    let local_name = match &efn.sig.receiver {
+        None => efn.name.cxx.to_string(),
+        Some(receiver) => format!(
+            "{}::{}",
+            out.types.resolve(&receiver.ty).name.cxx,
+            efn.name.cxx,
+        ),
+    };
+    let doc = &efn.doc;
+    let invoke = mangle::extern_fn(efn, out.types);
+    let indirect_call = false;
+    write_rust_function_shim_impl(out, &local_name, efn, doc, &invoke, indirect_call);
+}
+
+fn write_rust_function_shim_decl(
+    out: &mut OutFile,
+    local_name: &str,
+    sig: &Signature,
+    indirect_call: bool,
+) {
+    begin_function_definition(out);
+    write_return_type(out, &sig.ret);
+    write!(out, "{}(", local_name);
+    for (i, arg) in sig.args.iter().enumerate() {
+        if i > 0 {
+            write!(out, ", ");
+        }
+        write_type_space(out, &arg.ty);
+        write!(out, "{}", arg.name.cxx);
+    }
+    if indirect_call {
+        if !sig.args.is_empty() {
+            write!(out, ", ");
+        }
+        write!(out, "void *extern$");
+    }
+    write!(out, ")");
+    if let Some(receiver) = &sig.receiver {
+        if !receiver.mutable {
+            write!(out, " const");
+        }
+    }
+    if !sig.throws {
+        write!(out, " noexcept");
+    }
+}
+
+fn write_rust_function_shim_impl(
+    out: &mut OutFile,
+    local_name: &str,
+    sig: &Signature,
+    doc: &Doc,
+    invoke: &Symbol,
+    indirect_call: bool,
+) {
+    if out.header && sig.receiver.is_some() {
+        // We've already defined this inside the struct.
+        return;
+    }
+    if sig.receiver.is_none() {
+        // Member functions already documented at their declaration.
+        for line in doc.to_string().lines() {
+            writeln!(out, "//{}", line);
+        }
+    }
+    write_rust_function_shim_decl(out, local_name, sig, indirect_call);
+    if out.header {
+        writeln!(out, ";");
+        return;
+    }
+    writeln!(out, " {{");
+    for arg in &sig.args {
+        if arg.ty != RustString && out.types.needs_indirect_abi(&arg.ty) {
+            out.include.utility = true;
+            out.builtin.manually_drop = true;
+            write!(out, "  ::rust::ManuallyDrop<");
+            write_type(out, &arg.ty);
+            writeln!(out, "> {}$(::std::move({0}));", arg.name.cxx);
+        }
+    }
+    write!(out, "  ");
+    let indirect_return = indirect_return(sig, out.types);
+    if indirect_return {
+        out.builtin.maybe_uninit = true;
+        write!(out, "::rust::MaybeUninit<");
+        match sig.ret.as_ref().unwrap() {
+            Type::Ref(ret) => {
+                write_pointee_type(out, &ret.inner, ret.mutable);
+                write!(out, " *");
+            }
+            ret => write_type(out, ret),
+        }
+        writeln!(out, "> return$;");
+        write!(out, "  ");
+    } else if let Some(ret) = &sig.ret {
+        write!(out, "return ");
+        match ret {
+            Type::RustBox(_) => {
+                write_type(out, ret);
+                write!(out, "::from_raw(");
+            }
+            Type::UniquePtr(_) => {
+                write_type(out, ret);
+                write!(out, "(");
+            }
+            Type::Ref(_) => write!(out, "*"),
+            Type::Str(_) => {
+                out.builtin.rust_str_new_unchecked = true;
+                write!(out, "::rust::impl<::rust::Str>::new_unchecked(");
+            }
+            Type::SliceRef(_) => {
+                out.builtin.rust_slice_new = true;
+                write!(out, "::rust::impl<");
+                write_type(out, ret);
+                write!(out, ">::slice(");
+            }
+            _ => {}
+        }
+    }
+    if sig.throws {
+        out.builtin.ptr_len = true;
+        write!(out, "::rust::repr::PtrLen error$ = ");
+    }
+    write!(out, "{}(", invoke);
+    let mut needs_comma = false;
+    if sig.receiver.is_some() {
+        write!(out, "*this");
+        needs_comma = true;
+    }
+    for arg in &sig.args {
+        if needs_comma {
+            write!(out, ", ");
+        }
+        if out.types.needs_indirect_abi(&arg.ty) {
+            write!(out, "&");
+        }
+        write!(out, "{}", arg.name.cxx);
+        match &arg.ty {
+            Type::RustBox(_) => write!(out, ".into_raw()"),
+            Type::UniquePtr(_) => write!(out, ".release()"),
+            ty if ty != RustString && out.types.needs_indirect_abi(ty) => write!(out, "$.value"),
+            _ => {}
+        }
+        needs_comma = true;
+    }
+    if indirect_return {
+        if needs_comma {
+            write!(out, ", ");
+        }
+        write!(out, "&return$.value");
+        needs_comma = true;
+    }
+    if indirect_call {
+        if needs_comma {
+            write!(out, ", ");
+        }
+        write!(out, "extern$");
+    }
+    write!(out, ")");
+    if !indirect_return {
+        if let Some(ret) = &sig.ret {
+            if let Type::RustBox(_) | Type::UniquePtr(_) | Type::Str(_) | Type::SliceRef(_) = ret {
+                write!(out, ")");
+            }
+        }
+    }
+    writeln!(out, ";");
+    if sig.throws {
+        out.builtin.rust_error = true;
+        writeln!(out, "  if (error$.ptr) {{");
+        writeln!(out, "    throw ::rust::impl<::rust::Error>::error(error$);");
+        writeln!(out, "  }}");
+    }
+    if indirect_return {
+        write!(out, "  return ");
+        match sig.ret.as_ref().unwrap() {
+            Type::Ref(_) => write!(out, "*return$.value"),
+            _ => {
+                out.include.utility = true;
+                write!(out, "::std::move(return$.value)");
+            }
+        }
+        writeln!(out, ";");
+    }
+    writeln!(out, "}}");
+}
+
+fn write_return_type(out: &mut OutFile, ty: &Option<Type>) {
+    match ty {
+        None => write!(out, "void "),
+        Some(ty) => write_type_space(out, ty),
+    }
+}
+
+fn indirect_return(sig: &Signature, types: &Types) -> bool {
+    sig.ret
+        .as_ref()
+        .map_or(false, |ret| sig.throws || types.needs_indirect_abi(ret))
+}
+
+fn write_indirect_return_type(out: &mut OutFile, ty: &Type) {
+    match ty {
+        Type::RustBox(ty) | Type::UniquePtr(ty) => {
+            write_type_space(out, &ty.inner);
+            write!(out, "*");
+        }
+        Type::Ref(ty) => {
+            if !ty.mutable {
+                write!(out, "const ");
+            }
+            write_type(out, &ty.inner);
+            write!(out, " *");
+        }
+        _ => write_type(out, ty),
+    }
+}
+
+fn write_indirect_return_type_space(out: &mut OutFile, ty: &Type) {
+    write_indirect_return_type(out, ty);
+    match ty {
+        Type::RustBox(_) | Type::UniquePtr(_) | Type::Ref(_) => {}
+        Type::Str(_) | Type::SliceRef(_) => write!(out, " "),
+        _ => write_space_after_type(out, ty),
+    }
+}
+
+fn write_extern_return_type_space(out: &mut OutFile, ty: &Option<Type>) {
+    match ty {
+        Some(Type::RustBox(ty)) | Some(Type::UniquePtr(ty)) => {
+            write_type_space(out, &ty.inner);
+            write!(out, "*");
+        }
+        Some(Type::Ref(ty)) => {
+            if !ty.mutable {
+                write!(out, "const ");
+            }
+            write_type(out, &ty.inner);
+            write!(out, " *");
+        }
+        Some(Type::Str(_)) | Some(Type::SliceRef(_)) => {
+            out.builtin.repr_fat = true;
+            write!(out, "::rust::repr::Fat ");
+        }
+        Some(ty) if out.types.needs_indirect_abi(ty) => write!(out, "void "),
+        _ => write_return_type(out, ty),
+    }
+}
+
+fn write_extern_arg(out: &mut OutFile, arg: &Var) {
+    match &arg.ty {
+        Type::RustBox(ty) | Type::UniquePtr(ty) | Type::CxxVector(ty) => {
+            write_type_space(out, &ty.inner);
+            write!(out, "*");
+        }
+        _ => write_type_space(out, &arg.ty),
+    }
+    if out.types.needs_indirect_abi(&arg.ty) {
+        write!(out, "*");
+    }
+    write!(out, "{}", arg.name.cxx);
+}
+
+fn write_type(out: &mut OutFile, ty: &Type) {
+    match ty {
+        Type::Ident(ident) => match Atom::from(&ident.rust) {
+            Some(atom) => write_atom(out, atom),
+            None => write!(
+                out,
+                "{}",
+                out.types.resolve(ident).name.to_fully_qualified(),
+            ),
+        },
+        Type::RustBox(ty) => {
+            write!(out, "::rust::Box<");
+            write_type(out, &ty.inner);
+            write!(out, ">");
+        }
+        Type::RustVec(ty) => {
+            write!(out, "::rust::Vec<");
+            write_type(out, &ty.inner);
+            write!(out, ">");
+        }
+        Type::UniquePtr(ptr) => {
+            write!(out, "::std::unique_ptr<");
+            write_type(out, &ptr.inner);
+            write!(out, ">");
+        }
+        Type::SharedPtr(ptr) => {
+            write!(out, "::std::shared_ptr<");
+            write_type(out, &ptr.inner);
+            write!(out, ">");
+        }
+        Type::WeakPtr(ptr) => {
+            write!(out, "::std::weak_ptr<");
+            write_type(out, &ptr.inner);
+            write!(out, ">");
+        }
+        Type::CxxVector(ty) => {
+            write!(out, "::std::vector<");
+            write_type(out, &ty.inner);
+            write!(out, ">");
+        }
+        Type::Ref(r) => {
+            write_pointee_type(out, &r.inner, r.mutable);
+            write!(out, " &");
+        }
+        Type::Ptr(p) => {
+            write_pointee_type(out, &p.inner, p.mutable);
+            write!(out, " *");
+        }
+        Type::Str(_) => {
+            write!(out, "::rust::Str");
+        }
+        Type::SliceRef(slice) => {
+            write!(out, "::rust::Slice<");
+            if slice.mutability.is_none() {
+                write!(out, "const ");
+            }
+            write_type(out, &slice.inner);
+            write!(out, ">");
+        }
+        Type::Fn(f) => {
+            write!(out, "::rust::Fn<");
+            match &f.ret {
+                Some(ret) => write_type(out, ret),
+                None => write!(out, "void"),
+            }
+            write!(out, "(");
+            for (i, arg) in f.args.iter().enumerate() {
+                if i > 0 {
+                    write!(out, ", ");
+                }
+                write_type(out, &arg.ty);
+            }
+            write!(out, ")>");
+        }
+        Type::Array(a) => {
+            write!(out, "::std::array<");
+            write_type(out, &a.inner);
+            write!(out, ", {}>", &a.len);
+        }
+        Type::Void(_) => unreachable!(),
+    }
+}
+
+// Write just the T type behind a &T or &mut T or *const T or *mut T.
+fn write_pointee_type(out: &mut OutFile, inner: &Type, mutable: bool) {
+    if let Type::Ptr(_) = inner {
+        write_type_space(out, inner);
+        if !mutable {
+            write!(out, "const");
+        }
+    } else {
+        if !mutable {
+            write!(out, "const ");
+        }
+        write_type(out, inner);
+    }
+}
+
+fn write_atom(out: &mut OutFile, atom: Atom) {
+    match atom {
+        Bool => write!(out, "bool"),
+        Char => write!(out, "char"),
+        U8 => write!(out, "::std::uint8_t"),
+        U16 => write!(out, "::std::uint16_t"),
+        U32 => write!(out, "::std::uint32_t"),
+        U64 => write!(out, "::std::uint64_t"),
+        Usize => write!(out, "::std::size_t"),
+        I8 => write!(out, "::std::int8_t"),
+        I16 => write!(out, "::std::int16_t"),
+        I32 => write!(out, "::std::int32_t"),
+        I64 => write!(out, "::std::int64_t"),
+        Isize => write!(out, "::rust::isize"),
+        F32 => write!(out, "float"),
+        F64 => write!(out, "double"),
+        CxxString => write!(out, "::std::string"),
+        RustString => write!(out, "::rust::String"),
+    }
+}
+
+fn write_type_space(out: &mut OutFile, ty: &Type) {
+    write_type(out, ty);
+    write_space_after_type(out, ty);
+}
+
+fn write_space_after_type(out: &mut OutFile, ty: &Type) {
+    match ty {
+        Type::Ident(_)
+        | Type::RustBox(_)
+        | Type::UniquePtr(_)
+        | Type::SharedPtr(_)
+        | Type::WeakPtr(_)
+        | Type::Str(_)
+        | Type::CxxVector(_)
+        | Type::RustVec(_)
+        | Type::SliceRef(_)
+        | Type::Fn(_)
+        | Type::Array(_) => write!(out, " "),
+        Type::Ref(_) | Type::Ptr(_) => {}
+        Type::Void(_) => unreachable!(),
+    }
+}
+
+#[derive(Copy, Clone)]
+enum UniquePtr<'a> {
+    Ident(&'a Ident),
+    CxxVector(&'a Ident),
+}
+
+trait ToTypename {
+    fn to_typename(&self, types: &Types) -> String;
+}
+
+impl ToTypename for Ident {
+    fn to_typename(&self, types: &Types) -> String {
+        types.resolve(self).name.to_fully_qualified()
+    }
+}
+
+impl<'a> ToTypename for UniquePtr<'a> {
+    fn to_typename(&self, types: &Types) -> String {
+        match self {
+            UniquePtr::Ident(ident) => ident.to_typename(types),
+            UniquePtr::CxxVector(element) => {
+                format!("::std::vector<{}>", element.to_typename(types))
+            }
+        }
+    }
+}
+
+trait ToMangled {
+    fn to_mangled(&self, types: &Types) -> Symbol;
+}
+
+impl ToMangled for Ident {
+    fn to_mangled(&self, types: &Types) -> Symbol {
+        types.resolve(self).name.to_symbol()
+    }
+}
+
+impl<'a> ToMangled for UniquePtr<'a> {
+    fn to_mangled(&self, types: &Types) -> Symbol {
+        match self {
+            UniquePtr::Ident(ident) => ident.to_mangled(types),
+            UniquePtr::CxxVector(element) => element.to_mangled(types).prefix_with("std$vector$"),
+        }
+    }
+}
+
+fn write_generic_instantiations(out: &mut OutFile) {
+    if out.header {
+        return;
+    }
+
+    out.next_section();
+    out.set_namespace(Default::default());
+    out.begin_block(Block::ExternC);
+    for impl_key in out.types.impls.keys() {
+        out.next_section();
+        match *impl_key {
+            ImplKey::RustBox(ident) => write_rust_box_extern(out, ident),
+            ImplKey::RustVec(ident) => write_rust_vec_extern(out, ident),
+            ImplKey::UniquePtr(ident) => write_unique_ptr(out, ident),
+            ImplKey::SharedPtr(ident) => write_shared_ptr(out, ident),
+            ImplKey::WeakPtr(ident) => write_weak_ptr(out, ident),
+            ImplKey::CxxVector(ident) => write_cxx_vector(out, ident),
+        }
+    }
+    out.end_block(Block::ExternC);
+
+    out.begin_block(Block::Namespace("rust"));
+    out.begin_block(Block::InlineNamespace("cxxbridge1"));
+    for impl_key in out.types.impls.keys() {
+        match *impl_key {
+            ImplKey::RustBox(ident) => write_rust_box_impl(out, ident),
+            ImplKey::RustVec(ident) => write_rust_vec_impl(out, ident),
+            _ => {}
+        }
+    }
+    out.end_block(Block::InlineNamespace("cxxbridge1"));
+    out.end_block(Block::Namespace("rust"));
+}
+
+fn write_rust_box_extern(out: &mut OutFile, key: NamedImplKey) {
+    let resolve = out.types.resolve(&key);
+    let inner = resolve.name.to_fully_qualified();
+    let instance = resolve.name.to_symbol();
+
+    writeln!(
+        out,
+        "{} *cxxbridge1$box${}$alloc() noexcept;",
+        inner, instance,
+    );
+    writeln!(
+        out,
+        "void cxxbridge1$box${}$dealloc({} *) noexcept;",
+        instance, inner,
+    );
+    writeln!(
+        out,
+        "void cxxbridge1$box${}$drop(::rust::Box<{}> *ptr) noexcept;",
+        instance, inner,
+    );
+}
+
+fn write_rust_vec_extern(out: &mut OutFile, key: NamedImplKey) {
+    let element = key.rust;
+    let inner = element.to_typename(out.types);
+    let instance = element.to_mangled(out.types);
+
+    out.include.cstddef = true;
+
+    writeln!(
+        out,
+        "void cxxbridge1$rust_vec${}$new(const ::rust::Vec<{}> *ptr) noexcept;",
+        instance, inner,
+    );
+    writeln!(
+        out,
+        "void cxxbridge1$rust_vec${}$drop(::rust::Vec<{}> *ptr) noexcept;",
+        instance, inner,
+    );
+    writeln!(
+        out,
+        "::std::size_t cxxbridge1$rust_vec${}$len(const ::rust::Vec<{}> *ptr) noexcept;",
+        instance, inner,
+    );
+    writeln!(
+        out,
+        "::std::size_t cxxbridge1$rust_vec${}$capacity(const ::rust::Vec<{}> *ptr) noexcept;",
+        instance, inner,
+    );
+    writeln!(
+        out,
+        "const {} *cxxbridge1$rust_vec${}$data(const ::rust::Vec<{0}> *ptr) noexcept;",
+        inner, instance,
+    );
+    writeln!(
+        out,
+        "void cxxbridge1$rust_vec${}$reserve_total(::rust::Vec<{}> *ptr, ::std::size_t new_cap) noexcept;",
+        instance, inner,
+    );
+    writeln!(
+        out,
+        "void cxxbridge1$rust_vec${}$set_len(::rust::Vec<{}> *ptr, ::std::size_t len) noexcept;",
+        instance, inner,
+    );
+    writeln!(
+        out,
+        "void cxxbridge1$rust_vec${}$truncate(::rust::Vec<{}> *ptr, ::std::size_t len) noexcept;",
+        instance, inner,
+    );
+}
+
+fn write_rust_box_impl(out: &mut OutFile, key: NamedImplKey) {
+    let resolve = out.types.resolve(&key);
+    let inner = resolve.name.to_fully_qualified();
+    let instance = resolve.name.to_symbol();
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "{} *Box<{}>::allocation::alloc() noexcept {{",
+        inner, inner,
+    );
+    writeln!(out, "  return cxxbridge1$box${}$alloc();", instance);
+    writeln!(out, "}}");
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void Box<{}>::allocation::dealloc({} *ptr) noexcept {{",
+        inner, inner,
+    );
+    writeln!(out, "  cxxbridge1$box${}$dealloc(ptr);", instance);
+    writeln!(out, "}}");
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(out, "void Box<{}>::drop() noexcept {{", inner);
+    writeln!(out, "  cxxbridge1$box${}$drop(this);", instance);
+    writeln!(out, "}}");
+}
+
+fn write_rust_vec_impl(out: &mut OutFile, key: NamedImplKey) {
+    let element = key.rust;
+    let inner = element.to_typename(out.types);
+    let instance = element.to_mangled(out.types);
+
+    out.include.cstddef = true;
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(out, "Vec<{}>::Vec() noexcept {{", inner);
+    writeln!(out, "  cxxbridge1$rust_vec${}$new(this);", instance);
+    writeln!(out, "}}");
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(out, "void Vec<{}>::drop() noexcept {{", inner);
+    writeln!(out, "  return cxxbridge1$rust_vec${}$drop(this);", instance);
+    writeln!(out, "}}");
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "::std::size_t Vec<{}>::size() const noexcept {{",
+        inner,
+    );
+    writeln!(out, "  return cxxbridge1$rust_vec${}$len(this);", instance);
+    writeln!(out, "}}");
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "::std::size_t Vec<{}>::capacity() const noexcept {{",
+        inner,
+    );
+    writeln!(
+        out,
+        "  return cxxbridge1$rust_vec${}$capacity(this);",
+        instance,
+    );
+    writeln!(out, "}}");
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(out, "const {} *Vec<{0}>::data() const noexcept {{", inner);
+    writeln!(out, "  return cxxbridge1$rust_vec${}$data(this);", instance);
+    writeln!(out, "}}");
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void Vec<{}>::reserve_total(::std::size_t new_cap) noexcept {{",
+        inner,
+    );
+    writeln!(
+        out,
+        "  return cxxbridge1$rust_vec${}$reserve_total(this, new_cap);",
+        instance,
+    );
+    writeln!(out, "}}");
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void Vec<{}>::set_len(::std::size_t len) noexcept {{",
+        inner,
+    );
+    writeln!(
+        out,
+        "  return cxxbridge1$rust_vec${}$set_len(this, len);",
+        instance,
+    );
+    writeln!(out, "}}");
+
+    writeln!(out, "template <>");
+    begin_function_definition(out);
+    writeln!(out, "void Vec<{}>::truncate(::std::size_t len) {{", inner,);
+    writeln!(
+        out,
+        "  return cxxbridge1$rust_vec${}$truncate(this, len);",
+        instance,
+    );
+    writeln!(out, "}}");
+}
+
+fn write_unique_ptr(out: &mut OutFile, key: NamedImplKey) {
+    let ty = UniquePtr::Ident(key.rust);
+    write_unique_ptr_common(out, ty);
+}
+
+// Shared by UniquePtr<T> and UniquePtr<CxxVector<T>>.
+fn write_unique_ptr_common(out: &mut OutFile, ty: UniquePtr) {
+    out.include.new = true;
+    out.include.utility = true;
+    let inner = ty.to_typename(out.types);
+    let instance = ty.to_mangled(out.types);
+
+    let can_construct_from_value = match ty {
+        // Some aliases are to opaque types; some are to trivial types. We can't
+        // know at code generation time, so we generate both C++ and Rust side
+        // bindings for a "new" method anyway. But the Rust code can't be called
+        // for Opaque types because the 'new' method is not implemented.
+        UniquePtr::Ident(ident) => out.types.is_maybe_trivial(ident),
+        UniquePtr::CxxVector(_) => false,
+    };
+
+    let conditional_delete = match ty {
+        UniquePtr::Ident(ident) => {
+            !out.types.structs.contains_key(ident) && !out.types.enums.contains_key(ident)
+        }
+        UniquePtr::CxxVector(_) => false,
+    };
+
+    if conditional_delete {
+        out.builtin.is_complete = true;
+        let definition = match ty {
+            UniquePtr::Ident(ty) => &out.types.resolve(ty).name.cxx,
+            UniquePtr::CxxVector(_) => unreachable!(),
+        };
+        writeln!(
+            out,
+            "static_assert(::rust::detail::is_complete<{}>::value, \"definition of {} is required\");",
+            inner, definition,
+        );
+    }
+    writeln!(
+        out,
+        "static_assert(sizeof(::std::unique_ptr<{}>) == sizeof(void *), \"\");",
+        inner,
+    );
+    writeln!(
+        out,
+        "static_assert(alignof(::std::unique_ptr<{}>) == alignof(void *), \"\");",
+        inner,
+    );
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void cxxbridge1$unique_ptr${}$null(::std::unique_ptr<{}> *ptr) noexcept {{",
+        instance, inner,
+    );
+    writeln!(out, "  ::new (ptr) ::std::unique_ptr<{}>();", inner);
+    writeln!(out, "}}");
+    if can_construct_from_value {
+        out.builtin.maybe_uninit = true;
+        begin_function_definition(out);
+        writeln!(
+            out,
+            "{} *cxxbridge1$unique_ptr${}$uninit(::std::unique_ptr<{}> *ptr) noexcept {{",
+            inner, instance, inner,
+        );
+        writeln!(
+            out,
+            "  {} *uninit = reinterpret_cast<{} *>(new ::rust::MaybeUninit<{}>);",
+            inner, inner, inner,
+        );
+        writeln!(out, "  ::new (ptr) ::std::unique_ptr<{}>(uninit);", inner);
+        writeln!(out, "  return uninit;");
+        writeln!(out, "}}");
+    }
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void cxxbridge1$unique_ptr${}$raw(::std::unique_ptr<{}> *ptr, {} *raw) noexcept {{",
+        instance, inner, inner,
+    );
+    writeln!(out, "  ::new (ptr) ::std::unique_ptr<{}>(raw);", inner);
+    writeln!(out, "}}");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "const {} *cxxbridge1$unique_ptr${}$get(const ::std::unique_ptr<{}>& ptr) noexcept {{",
+        inner, instance, inner,
+    );
+    writeln!(out, "  return ptr.get();");
+    writeln!(out, "}}");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "{} *cxxbridge1$unique_ptr${}$release(::std::unique_ptr<{}>& ptr) noexcept {{",
+        inner, instance, inner,
+    );
+    writeln!(out, "  return ptr.release();");
+    writeln!(out, "}}");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void cxxbridge1$unique_ptr${}$drop(::std::unique_ptr<{}> *ptr) noexcept {{",
+        instance, inner,
+    );
+    if conditional_delete {
+        out.builtin.deleter_if = true;
+        writeln!(
+            out,
+            "  ::rust::deleter_if<::rust::detail::is_complete<{}>::value>{{}}(ptr);",
+            inner,
+        );
+    } else {
+        writeln!(out, "  ptr->~unique_ptr();");
+    }
+    writeln!(out, "}}");
+}
+
+fn write_shared_ptr(out: &mut OutFile, key: NamedImplKey) {
+    let ident = key.rust;
+    let resolve = out.types.resolve(ident);
+    let inner = resolve.name.to_fully_qualified();
+    let instance = resolve.name.to_symbol();
+
+    out.include.new = true;
+    out.include.utility = true;
+
+    // Some aliases are to opaque types; some are to trivial types. We can't
+    // know at code generation time, so we generate both C++ and Rust side
+    // bindings for a "new" method anyway. But the Rust code can't be called for
+    // Opaque types because the 'new' method is not implemented.
+    let can_construct_from_value = out.types.is_maybe_trivial(ident);
+
+    writeln!(
+        out,
+        "static_assert(sizeof(::std::shared_ptr<{}>) == 2 * sizeof(void *), \"\");",
+        inner,
+    );
+    writeln!(
+        out,
+        "static_assert(alignof(::std::shared_ptr<{}>) == alignof(void *), \"\");",
+        inner,
+    );
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void cxxbridge1$shared_ptr${}$null(::std::shared_ptr<{}> *ptr) noexcept {{",
+        instance, inner,
+    );
+    writeln!(out, "  ::new (ptr) ::std::shared_ptr<{}>();", inner);
+    writeln!(out, "}}");
+    if can_construct_from_value {
+        out.builtin.maybe_uninit = true;
+        begin_function_definition(out);
+        writeln!(
+            out,
+            "{} *cxxbridge1$shared_ptr${}$uninit(::std::shared_ptr<{}> *ptr) noexcept {{",
+            inner, instance, inner,
+        );
+        writeln!(
+            out,
+            "  {} *uninit = reinterpret_cast<{} *>(new ::rust::MaybeUninit<{}>);",
+            inner, inner, inner,
+        );
+        writeln!(out, "  ::new (ptr) ::std::shared_ptr<{}>(uninit);", inner);
+        writeln!(out, "  return uninit;");
+        writeln!(out, "}}");
+    }
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void cxxbridge1$shared_ptr${}$clone(const ::std::shared_ptr<{}>& self, ::std::shared_ptr<{}> *ptr) noexcept {{",
+        instance, inner, inner,
+    );
+    writeln!(out, "  ::new (ptr) ::std::shared_ptr<{}>(self);", inner);
+    writeln!(out, "}}");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "const {} *cxxbridge1$shared_ptr${}$get(const ::std::shared_ptr<{}>& self) noexcept {{",
+        inner, instance, inner,
+    );
+    writeln!(out, "  return self.get();");
+    writeln!(out, "}}");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void cxxbridge1$shared_ptr${}$drop(::std::shared_ptr<{}> *self) noexcept {{",
+        instance, inner,
+    );
+    writeln!(out, "  self->~shared_ptr();");
+    writeln!(out, "}}");
+}
+
+fn write_weak_ptr(out: &mut OutFile, key: NamedImplKey) {
+    let resolve = out.types.resolve(&key);
+    let inner = resolve.name.to_fully_qualified();
+    let instance = resolve.name.to_symbol();
+
+    out.include.new = true;
+    out.include.utility = true;
+
+    writeln!(
+        out,
+        "static_assert(sizeof(::std::weak_ptr<{}>) == 2 * sizeof(void *), \"\");",
+        inner,
+    );
+    writeln!(
+        out,
+        "static_assert(alignof(::std::weak_ptr<{}>) == alignof(void *), \"\");",
+        inner,
+    );
+    writeln!(
+        out,
+        "void cxxbridge1$weak_ptr${}$null(::std::weak_ptr<{}> *ptr) noexcept {{",
+        instance, inner,
+    );
+    writeln!(out, "  ::new (ptr) ::std::weak_ptr<{}>();", inner);
+    writeln!(out, "}}");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void cxxbridge1$weak_ptr${}$clone(const ::std::weak_ptr<{}>& self, ::std::weak_ptr<{}> *ptr) noexcept {{",
+        instance, inner, inner,
+    );
+    writeln!(out, "  ::new (ptr) ::std::weak_ptr<{}>(self);", inner);
+    writeln!(out, "}}");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void cxxbridge1$weak_ptr${}$downgrade(const ::std::shared_ptr<{}>& shared, ::std::weak_ptr<{}> *weak) noexcept {{",
+        instance, inner, inner,
+    );
+    writeln!(out, "  ::new (weak) ::std::weak_ptr<{}>(shared);", inner);
+    writeln!(out, "}}");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void cxxbridge1$weak_ptr${}$upgrade(const ::std::weak_ptr<{}>& weak, ::std::shared_ptr<{}> *shared) noexcept {{",
+        instance, inner, inner,
+    );
+    writeln!(
+        out,
+        "  ::new (shared) ::std::shared_ptr<{}>(weak.lock());",
+        inner,
+    );
+    writeln!(out, "}}");
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "void cxxbridge1$weak_ptr${}$drop(::std::weak_ptr<{}> *self) noexcept {{",
+        instance, inner,
+    );
+    writeln!(out, "  self->~weak_ptr();");
+    writeln!(out, "}}");
+}
+
+fn write_cxx_vector(out: &mut OutFile, key: NamedImplKey) {
+    let element = key.rust;
+    let inner = element.to_typename(out.types);
+    let instance = element.to_mangled(out.types);
+
+    out.include.cstddef = true;
+    out.include.utility = true;
+    out.builtin.destroy = true;
+
+    writeln!(
+        out,
+        "::std::size_t cxxbridge1$std$vector${}$size(const ::std::vector<{}> &s) noexcept {{",
+        instance, inner,
+    );
+    writeln!(out, "  return s.size();");
+    writeln!(out, "}}");
+
+    begin_function_definition(out);
+    writeln!(
+        out,
+        "{} *cxxbridge1$std$vector${}$get_unchecked(::std::vector<{}> *s, ::std::size_t pos) noexcept {{",
+        inner, instance, inner,
+    );
+    writeln!(out, "  return &(*s)[pos];");
+    writeln!(out, "}}");
+
+    if out.types.is_maybe_trivial(element) {
+        begin_function_definition(out);
+        writeln!(
+            out,
+            "void cxxbridge1$std$vector${}$push_back(::std::vector<{}> *v, {} *value) noexcept {{",
+            instance, inner, inner,
+        );
+        writeln!(out, "  v->push_back(::std::move(*value));");
+        writeln!(out, "  ::rust::destroy(value);");
+        writeln!(out, "}}");
+
+        begin_function_definition(out);
+        writeln!(
+            out,
+            "void cxxbridge1$std$vector${}$pop_back(::std::vector<{}> *v, {} *out) noexcept {{",
+            instance, inner, inner,
+        );
+        writeln!(out, "  ::new (out) {}(::std::move(v->back()));", inner);
+        writeln!(out, "  v->pop_back();");
+        writeln!(out, "}}");
+    }
+
+    out.include.memory = true;
+    write_unique_ptr_common(out, UniquePtr::CxxVector(element));
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..8b1a393
--- /dev/null
@@ -0,0 +1 @@
+// empty
diff --git a/src/main.rs b/src/main.rs
new file mode 100644 (file)
index 0000000..1c15db6
--- /dev/null
@@ -0,0 +1,116 @@
+#![allow(
+    clippy::cast_sign_loss,
+    clippy::cognitive_complexity,
+    clippy::default_trait_access,
+    clippy::enum_glob_use,
+    clippy::if_same_then_else,
+    clippy::inherent_to_string,
+    clippy::items_after_statements,
+    clippy::large_enum_variant,
+    clippy::match_bool,
+    clippy::match_on_vec_items,
+    clippy::match_same_arms,
+    clippy::module_name_repetitions,
+    clippy::needless_pass_by_value,
+    clippy::new_without_default,
+    clippy::nonminimal_bool,
+    clippy::option_if_let_else,
+    clippy::or_fun_call,
+    clippy::redundant_else,
+    clippy::shadow_unrelated,
+    clippy::similar_names,
+    clippy::single_match_else,
+    clippy::struct_excessive_bools,
+    clippy::too_many_arguments,
+    clippy::too_many_lines,
+    clippy::toplevel_ref_arg,
+    // clippy bug: https://github.com/rust-lang/rust-clippy/issues/6983
+    clippy::wrong_self_convention
+)]
+
+mod app;
+mod gen;
+mod output;
+mod syntax;
+
+use crate::gen::error::{report, Result};
+use crate::gen::fs;
+use crate::gen::include::{self, Include};
+use crate::output::Output;
+use std::io::{self, Write};
+use std::path::PathBuf;
+use std::process;
+
+#[derive(Debug)]
+struct Opt {
+    input: Option<PathBuf>,
+    header: bool,
+    cxx_impl_annotations: Option<String>,
+    include: Vec<Include>,
+    outputs: Vec<Output>,
+}
+
+fn main() {
+    if let Err(err) = try_main() {
+        let _ = writeln!(io::stderr(), "cxxbridge: {}", report(err));
+        process::exit(1);
+    }
+}
+
+enum Kind {
+    GeneratedHeader,
+    GeneratedImplementation,
+    Header,
+}
+
+fn try_main() -> Result<()> {
+    let opt = app::from_args();
+
+    let mut outputs = Vec::new();
+    let mut gen_header = false;
+    let mut gen_implementation = false;
+    for output in opt.outputs {
+        let kind = if opt.input.is_none() {
+            Kind::Header
+        } else if opt.header
+            || output.ends_with(".h")
+            || output.ends_with(".hh")
+            || output.ends_with(".hpp")
+        {
+            gen_header = true;
+            Kind::GeneratedHeader
+        } else {
+            gen_implementation = true;
+            Kind::GeneratedImplementation
+        };
+        outputs.push((output, kind));
+    }
+
+    let gen = gen::Opt {
+        include: opt.include,
+        cxx_impl_annotations: opt.cxx_impl_annotations,
+        gen_header,
+        gen_implementation,
+        ..Default::default()
+    };
+
+    let generated_code = if let Some(input) = opt.input {
+        gen::generate_from_path(&input, &gen)
+    } else {
+        Default::default()
+    };
+
+    for (output, kind) in outputs {
+        let content = match kind {
+            Kind::GeneratedHeader => &generated_code.header,
+            Kind::GeneratedImplementation => &generated_code.implementation,
+            Kind::Header => include::HEADER.as_bytes(),
+        };
+        match output {
+            Output::Stdout => drop(io::stdout().write_all(content)),
+            Output::File(path) => fs::write(path, content)?,
+        }
+    }
+
+    Ok(())
+}
diff --git a/src/output.rs b/src/output.rs
new file mode 100644 (file)
index 0000000..a46581b
--- /dev/null
@@ -0,0 +1,16 @@
+use std::path::PathBuf;
+
+#[derive(Debug)]
+pub(crate) enum Output {
+    Stdout,
+    File(PathBuf),
+}
+
+impl Output {
+    pub(crate) fn ends_with(&self, suffix: &str) -> bool {
+        match self {
+            Output::Stdout => false,
+            Output::File(path) => path.to_string_lossy().ends_with(suffix),
+        }
+    }
+}
diff --git a/src/syntax/atom.rs b/src/syntax/atom.rs
new file mode 100644 (file)
index 0000000..d4ad78f
--- /dev/null
@@ -0,0 +1,103 @@
+use crate::syntax::Type;
+use proc_macro2::Ident;
+use std::fmt::{self, Display};
+
+#[derive(Copy, Clone, PartialEq)]
+pub enum Atom {
+    Bool,
+    Char, // C char, not Rust char
+    U8,
+    U16,
+    U32,
+    U64,
+    Usize,
+    I8,
+    I16,
+    I32,
+    I64,
+    Isize,
+    F32,
+    F64,
+    CxxString,
+    RustString,
+}
+
+impl Atom {
+    pub fn from(ident: &Ident) -> Option<Self> {
+        Self::from_str(ident.to_string().as_str())
+    }
+
+    pub fn from_str(s: &str) -> Option<Self> {
+        use self::Atom::*;
+        match s {
+            "bool" => Some(Bool),
+            "c_char" => Some(Char),
+            "u8" => Some(U8),
+            "u16" => Some(U16),
+            "u32" => Some(U32),
+            "u64" => Some(U64),
+            "usize" => Some(Usize),
+            "i8" => Some(I8),
+            "i16" => Some(I16),
+            "i32" => Some(I32),
+            "i64" => Some(I64),
+            "isize" => Some(Isize),
+            "f32" => Some(F32),
+            "f64" => Some(F64),
+            "CxxString" => Some(CxxString),
+            "String" => Some(RustString),
+            _ => None,
+        }
+    }
+}
+
+impl Display for Atom {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str(self.as_ref())
+    }
+}
+
+impl AsRef<str> for Atom {
+    fn as_ref(&self) -> &str {
+        use self::Atom::*;
+        match self {
+            Bool => "bool",
+            Char => "c_char",
+            U8 => "u8",
+            U16 => "u16",
+            U32 => "u32",
+            U64 => "u64",
+            Usize => "usize",
+            I8 => "i8",
+            I16 => "i16",
+            I32 => "i32",
+            I64 => "i64",
+            Isize => "isize",
+            F32 => "f32",
+            F64 => "f64",
+            CxxString => "CxxString",
+            RustString => "String",
+        }
+    }
+}
+
+impl PartialEq<Atom> for Type {
+    fn eq(&self, atom: &Atom) -> bool {
+        match self {
+            Type::Ident(ident) => ident.rust == atom,
+            _ => false,
+        }
+    }
+}
+
+impl PartialEq<Atom> for &Ident {
+    fn eq(&self, atom: &Atom) -> bool {
+        *self == atom
+    }
+}
+
+impl PartialEq<Atom> for &Type {
+    fn eq(&self, atom: &Atom) -> bool {
+        *self == atom
+    }
+}
diff --git a/src/syntax/attrs.rs b/src/syntax/attrs.rs
new file mode 100644 (file)
index 0000000..6f4d0c4
--- /dev/null
@@ -0,0 +1,276 @@
+use crate::syntax::namespace::Namespace;
+use crate::syntax::report::Errors;
+use crate::syntax::Atom::{self, *};
+use crate::syntax::{Derive, Doc, ForeignName};
+use proc_macro2::{Ident, TokenStream};
+use quote::ToTokens;
+use syn::parse::{Nothing, Parse, ParseStream, Parser as _};
+use syn::{parenthesized, token, Attribute, Error, LitStr, Path, Result, Token};
+
+// Intended usage:
+//
+//     let mut doc = Doc::new();
+//     let mut cxx_name = None;
+//     let mut rust_name = None;
+//     /* ... */
+//     let attrs = attrs::parse(
+//         cx,
+//         item.attrs,
+//         attrs::Parser {
+//             doc: Some(&mut doc),
+//             cxx_name: Some(&mut cxx_name),
+//             rust_name: Some(&mut rust_name),
+//             /* ... */
+//             ..Default::default()
+//         },
+//     );
+//
+#[derive(Default)]
+pub struct Parser<'a> {
+    pub doc: Option<&'a mut Doc>,
+    pub derives: Option<&'a mut Vec<Derive>>,
+    pub repr: Option<&'a mut Option<Atom>>,
+    pub namespace: Option<&'a mut Namespace>,
+    pub cxx_name: Option<&'a mut Option<ForeignName>>,
+    pub rust_name: Option<&'a mut Option<Ident>>,
+    pub variants_from_header: Option<&'a mut Option<Attribute>>,
+
+    // Suppress clippy needless_update lint ("struct update has no effect, all
+    // the fields in the struct have already been specified") when preemptively
+    // writing `..Default::default()`.
+    pub(crate) _more: (),
+}
+
+pub fn parse(cx: &mut Errors, attrs: Vec<Attribute>, mut parser: Parser) -> OtherAttrs {
+    let mut passthrough_attrs = Vec::new();
+    for attr in attrs {
+        if attr.path.is_ident("doc") {
+            match parse_doc_attribute.parse2(attr.tokens.clone()) {
+                Ok(attr) => {
+                    if let Some(doc) = &mut parser.doc {
+                        match attr {
+                            DocAttribute::Doc(lit) => doc.push(lit),
+                            DocAttribute::Hidden => doc.hidden = true,
+                        }
+                        continue;
+                    }
+                }
+                Err(err) => {
+                    cx.push(err);
+                    break;
+                }
+            }
+        } else if attr.path.is_ident("derive") {
+            match attr.parse_args_with(|attr: ParseStream| parse_derive_attribute(cx, attr)) {
+                Ok(attr) => {
+                    if let Some(derives) = &mut parser.derives {
+                        derives.extend(attr);
+                        continue;
+                    }
+                }
+                Err(err) => {
+                    cx.push(err);
+                    break;
+                }
+            }
+        } else if attr.path.is_ident("repr") {
+            match attr.parse_args_with(parse_repr_attribute) {
+                Ok(attr) => {
+                    if let Some(repr) = &mut parser.repr {
+                        **repr = Some(attr);
+                        continue;
+                    }
+                }
+                Err(err) => {
+                    cx.push(err);
+                    break;
+                }
+            }
+        } else if attr.path.is_ident("namespace") {
+            match parse_namespace_attribute.parse2(attr.tokens.clone()) {
+                Ok(attr) => {
+                    if let Some(namespace) = &mut parser.namespace {
+                        **namespace = attr;
+                        continue;
+                    }
+                }
+                Err(err) => {
+                    cx.push(err);
+                    break;
+                }
+            }
+        } else if attr.path.is_ident("cxx_name") {
+            match parse_cxx_name_attribute.parse2(attr.tokens.clone()) {
+                Ok(attr) => {
+                    if let Some(cxx_name) = &mut parser.cxx_name {
+                        **cxx_name = Some(attr);
+                        continue;
+                    }
+                }
+                Err(err) => {
+                    cx.push(err);
+                    break;
+                }
+            }
+        } else if attr.path.is_ident("rust_name") {
+            match parse_rust_name_attribute.parse2(attr.tokens.clone()) {
+                Ok(attr) => {
+                    if let Some(rust_name) = &mut parser.rust_name {
+                        **rust_name = Some(attr);
+                        continue;
+                    }
+                }
+                Err(err) => {
+                    cx.push(err);
+                    break;
+                }
+            }
+        } else if attr.path.is_ident("variants_from_header") && cfg!(feature = "experimental") {
+            if let Err(err) = Nothing::parse.parse2(attr.tokens.clone()) {
+                cx.push(err);
+            }
+            if let Some(variants_from_header) = &mut parser.variants_from_header {
+                **variants_from_header = Some(attr);
+                continue;
+            }
+        } else if attr.path.is_ident("allow")
+            || attr.path.is_ident("warn")
+            || attr.path.is_ident("deny")
+            || attr.path.is_ident("forbid")
+            || attr.path.is_ident("deprecated")
+            || attr.path.is_ident("must_use")
+        {
+            // https://doc.rust-lang.org/reference/attributes/diagnostics.html
+            passthrough_attrs.push(attr);
+            continue;
+        } else if attr.path.is_ident("serde") {
+            passthrough_attrs.push(attr);
+            continue;
+        } else if attr.path.segments.len() > 1 {
+            let tool = &attr.path.segments.first().unwrap().ident;
+            if tool == "rustfmt" {
+                // Skip, rustfmt only needs to find it in the pre-expansion source file.
+                continue;
+            } else if tool == "clippy" {
+                passthrough_attrs.push(attr);
+                continue;
+            }
+        }
+        cx.error(attr, "unsupported attribute");
+        break;
+    }
+    OtherAttrs(passthrough_attrs)
+}
+
+enum DocAttribute {
+    Doc(LitStr),
+    Hidden,
+}
+
+mod kw {
+    syn::custom_keyword!(hidden);
+}
+
+fn parse_doc_attribute(input: ParseStream) -> Result<DocAttribute> {
+    let lookahead = input.lookahead1();
+    if lookahead.peek(Token![=]) {
+        input.parse::<Token![=]>()?;
+        let lit: LitStr = input.parse()?;
+        Ok(DocAttribute::Doc(lit))
+    } else if lookahead.peek(token::Paren) {
+        let content;
+        parenthesized!(content in input);
+        content.parse::<kw::hidden>()?;
+        Ok(DocAttribute::Hidden)
+    } else {
+        Err(lookahead.error())
+    }
+}
+
+fn parse_derive_attribute(cx: &mut Errors, input: ParseStream) -> Result<Vec<Derive>> {
+    let paths = input.parse_terminated::<Path, Token![,]>(Path::parse_mod_style)?;
+
+    let mut derives = Vec::new();
+    for path in paths {
+        if let Some(ident) = path.get_ident() {
+            if let Some(derive) = Derive::from(ident) {
+                derives.push(derive);
+                continue;
+            }
+        }
+        cx.error(path, "unsupported derive");
+    }
+    Ok(derives)
+}
+
+fn parse_repr_attribute(input: ParseStream) -> Result<Atom> {
+    let begin = input.cursor();
+    let ident: Ident = input.parse()?;
+    if let Some(atom) = Atom::from(&ident) {
+        match atom {
+            U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize if input.is_empty() => {
+                return Ok(atom);
+            }
+            _ => {}
+        }
+    }
+    Err(Error::new_spanned(
+        begin.token_stream(),
+        "unrecognized repr",
+    ))
+}
+
+fn parse_namespace_attribute(input: ParseStream) -> Result<Namespace> {
+    input.parse::<Token![=]>()?;
+    let namespace = input.parse::<Namespace>()?;
+    Ok(namespace)
+}
+
+fn parse_cxx_name_attribute(input: ParseStream) -> Result<ForeignName> {
+    input.parse::<Token![=]>()?;
+    if input.peek(LitStr) {
+        let lit: LitStr = input.parse()?;
+        ForeignName::parse(&lit.value(), lit.span())
+    } else {
+        let ident: Ident = input.parse()?;
+        ForeignName::parse(&ident.to_string(), ident.span())
+    }
+}
+
+fn parse_rust_name_attribute(input: ParseStream) -> Result<Ident> {
+    input.parse::<Token![=]>()?;
+    if input.peek(LitStr) {
+        let lit: LitStr = input.parse()?;
+        lit.parse()
+    } else {
+        input.parse()
+    }
+}
+
+pub struct OtherAttrs(Vec<Attribute>);
+
+impl OtherAttrs {
+    pub fn none() -> Self {
+        OtherAttrs(Vec::new())
+    }
+}
+
+impl ToTokens for OtherAttrs {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        for attr in &self.0 {
+            let Attribute {
+                pound_token,
+                style,
+                bracket_token,
+                path,
+                tokens: attr_tokens,
+            } = attr;
+            pound_token.to_tokens(tokens);
+            let _ = style; // ignore; render outer and inner attrs both as outer
+            bracket_token.surround(tokens, |tokens| {
+                path.to_tokens(tokens);
+                attr_tokens.to_tokens(tokens);
+            });
+        }
+    }
+}
diff --git a/src/syntax/check.rs b/src/syntax/check.rs
new file mode 100644 (file)
index 0000000..698782f
--- /dev/null
@@ -0,0 +1,734 @@
+use crate::syntax::atom::Atom::{self, *};
+use crate::syntax::report::Errors;
+use crate::syntax::visit::{self, Visit};
+use crate::syntax::{
+    error, ident, trivial, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, Lifetimes,
+    NamedType, Ptr, Receiver, Ref, Signature, SliceRef, Struct, Trait, Ty1, Type, TypeAlias, Types,
+};
+use proc_macro2::{Delimiter, Group, Ident, TokenStream};
+use quote::{quote, ToTokens};
+use std::fmt::Display;
+use syn::{GenericParam, Generics, Lifetime};
+
+pub(crate) struct Check<'a> {
+    apis: &'a [Api],
+    types: &'a Types<'a>,
+    errors: &'a mut Errors,
+    generator: Generator,
+}
+
+pub(crate) enum Generator {
+    // cxx-build crate, cxxbridge cli, cxx-gen.
+    #[allow(dead_code)]
+    Build,
+    // cxxbridge-macro. This is relevant in that the macro output is going to
+    // get fed straight to rustc, so for errors that rustc already contains
+    // logic to catch (probably with a better diagnostic than what the proc
+    // macro API is able to produce), we avoid duplicating them in our own
+    // diagnostics.
+    #[allow(dead_code)]
+    Macro,
+}
+
+pub(crate) fn typecheck(cx: &mut Errors, apis: &[Api], types: &Types, generator: Generator) {
+    do_typecheck(&mut Check {
+        apis,
+        types,
+        errors: cx,
+        generator,
+    });
+}
+
+fn do_typecheck(cx: &mut Check) {
+    ident::check_all(cx, cx.apis);
+
+    for ty in cx.types {
+        match ty {
+            Type::Ident(ident) => check_type_ident(cx, ident),
+            Type::RustBox(ptr) => check_type_box(cx, ptr),
+            Type::RustVec(ty) => check_type_rust_vec(cx, ty),
+            Type::UniquePtr(ptr) => check_type_unique_ptr(cx, ptr),
+            Type::SharedPtr(ptr) => check_type_shared_ptr(cx, ptr),
+            Type::WeakPtr(ptr) => check_type_weak_ptr(cx, ptr),
+            Type::CxxVector(ptr) => check_type_cxx_vector(cx, ptr),
+            Type::Ref(ty) => check_type_ref(cx, ty),
+            Type::Ptr(ty) => check_type_ptr(cx, ty),
+            Type::Array(array) => check_type_array(cx, array),
+            Type::Fn(ty) => check_type_fn(cx, ty),
+            Type::SliceRef(ty) => check_type_slice_ref(cx, ty),
+            Type::Str(_) | Type::Void(_) => {}
+        }
+    }
+
+    for api in cx.apis {
+        match api {
+            Api::Include(_) => {}
+            Api::Struct(strct) => check_api_struct(cx, strct),
+            Api::Enum(enm) => check_api_enum(cx, enm),
+            Api::CxxType(ety) | Api::RustType(ety) => check_api_type(cx, ety),
+            Api::CxxFunction(efn) | Api::RustFunction(efn) => check_api_fn(cx, efn),
+            Api::TypeAlias(alias) => check_api_type_alias(cx, alias),
+            Api::Impl(imp) => check_api_impl(cx, imp),
+        }
+    }
+}
+
+impl Check<'_> {
+    pub(crate) fn error(&mut self, sp: impl ToTokens, msg: impl Display) {
+        self.errors.error(sp, msg);
+    }
+}
+
+fn check_type_ident(cx: &mut Check, name: &NamedType) {
+    let ident = &name.rust;
+    if Atom::from(ident).is_none()
+        && !cx.types.structs.contains_key(ident)
+        && !cx.types.enums.contains_key(ident)
+        && !cx.types.cxx.contains(ident)
+        && !cx.types.rust.contains(ident)
+    {
+        let msg = format!("unsupported type: {}", ident);
+        cx.error(ident, &msg);
+    }
+}
+
+fn check_type_box(cx: &mut Check, ptr: &Ty1) {
+    if let Type::Ident(ident) = &ptr.inner {
+        if cx.types.cxx.contains(&ident.rust)
+            && !cx.types.aliases.contains_key(&ident.rust)
+            && !cx.types.structs.contains_key(&ident.rust)
+            && !cx.types.enums.contains_key(&ident.rust)
+        {
+            cx.error(ptr, error::BOX_CXX_TYPE.msg);
+        }
+
+        if Atom::from(&ident.rust).is_none() {
+            return;
+        }
+    }
+
+    cx.error(ptr, "unsupported target type of Box");
+}
+
+fn check_type_rust_vec(cx: &mut Check, ty: &Ty1) {
+    match &ty.inner {
+        Type::Ident(ident) => {
+            if cx.types.cxx.contains(&ident.rust)
+                && !cx.types.aliases.contains_key(&ident.rust)
+                && !cx.types.structs.contains_key(&ident.rust)
+                && !cx.types.enums.contains_key(&ident.rust)
+            {
+                cx.error(ty, "Rust Vec containing C++ type is not supported yet");
+                return;
+            }
+
+            match Atom::from(&ident.rust) {
+                None | Some(Char) | Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize)
+                | Some(I8) | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32)
+                | Some(F64) | Some(RustString) => return,
+                Some(Bool) => { /* todo */ }
+                Some(CxxString) => {}
+            }
+        }
+        Type::Str(_) => return,
+        _ => {}
+    }
+
+    cx.error(ty, "unsupported element type of Vec");
+}
+
+fn check_type_unique_ptr(cx: &mut Check, ptr: &Ty1) {
+    if let Type::Ident(ident) = &ptr.inner {
+        if cx.types.rust.contains(&ident.rust) {
+            cx.error(ptr, "unique_ptr of a Rust type is not supported yet");
+            return;
+        }
+
+        match Atom::from(&ident.rust) {
+            None | Some(CxxString) => return,
+            _ => {}
+        }
+    } else if let Type::CxxVector(_) = &ptr.inner {
+        return;
+    }
+
+    cx.error(ptr, "unsupported unique_ptr target type");
+}
+
+fn check_type_shared_ptr(cx: &mut Check, ptr: &Ty1) {
+    if let Type::Ident(ident) = &ptr.inner {
+        if cx.types.rust.contains(&ident.rust) {
+            cx.error(ptr, "shared_ptr of a Rust type is not supported yet");
+            return;
+        }
+
+        match Atom::from(&ident.rust) {
+            None | Some(Bool) | Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize)
+            | Some(I8) | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32)
+            | Some(F64) | Some(CxxString) => return,
+            Some(Char) | Some(RustString) => {}
+        }
+    } else if let Type::CxxVector(_) = &ptr.inner {
+        cx.error(ptr, "std::shared_ptr<std::vector> is not supported yet");
+        return;
+    }
+
+    cx.error(ptr, "unsupported shared_ptr target type");
+}
+
+fn check_type_weak_ptr(cx: &mut Check, ptr: &Ty1) {
+    if let Type::Ident(ident) = &ptr.inner {
+        if cx.types.rust.contains(&ident.rust) {
+            cx.error(ptr, "weak_ptr of a Rust type is not supported yet");
+            return;
+        }
+
+        match Atom::from(&ident.rust) {
+            None | Some(Bool) | Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize)
+            | Some(I8) | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32)
+            | Some(F64) | Some(CxxString) => return,
+            Some(Char) | Some(RustString) => {}
+        }
+    } else if let Type::CxxVector(_) = &ptr.inner {
+        cx.error(ptr, "std::weak_ptr<std::vector> is not supported yet");
+        return;
+    }
+
+    cx.error(ptr, "unsupported weak_ptr target type");
+}
+
+fn check_type_cxx_vector(cx: &mut Check, ptr: &Ty1) {
+    if let Type::Ident(ident) = &ptr.inner {
+        if cx.types.rust.contains(&ident.rust) {
+            cx.error(
+                ptr,
+                "C++ vector containing a Rust type is not supported yet",
+            );
+            return;
+        }
+
+        match Atom::from(&ident.rust) {
+            None | Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize) | Some(I8)
+            | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32) | Some(F64)
+            | Some(CxxString) => return,
+            Some(Char) => { /* todo */ }
+            Some(Bool) | Some(RustString) => {}
+        }
+    }
+
+    cx.error(ptr, "unsupported vector element type");
+}
+
+fn check_type_ref(cx: &mut Check, ty: &Ref) {
+    if ty.mutable && !ty.pinned {
+        if let Some(requires_pin) = match &ty.inner {
+            Type::Ident(ident) if ident.rust == CxxString || is_opaque_cxx(cx, &ident.rust) => {
+                Some(ident.rust.to_string())
+            }
+            Type::CxxVector(_) => Some("CxxVector<...>".to_owned()),
+            _ => None,
+        } {
+            cx.error(
+                ty,
+                format!(
+                    "mutable reference to C++ type requires a pin -- use Pin<&mut {}>",
+                    requires_pin,
+                ),
+            );
+        }
+    }
+
+    match ty.inner {
+        Type::Fn(_) | Type::Void(_) => {}
+        Type::Ref(_) => {
+            cx.error(ty, "C++ does not allow references to references");
+            return;
+        }
+        _ => return,
+    }
+
+    cx.error(ty, "unsupported reference type");
+}
+
+fn check_type_ptr(cx: &mut Check, ty: &Ptr) {
+    match ty.inner {
+        Type::Fn(_) | Type::Void(_) => {}
+        Type::Ref(_) => {
+            cx.error(ty, "C++ does not allow pointer to reference as a type");
+            return;
+        }
+        _ => return,
+    }
+
+    cx.error(ty, "unsupported pointer type");
+}
+
+fn check_type_slice_ref(cx: &mut Check, ty: &SliceRef) {
+    let supported = !is_unsized(cx, &ty.inner)
+        || match &ty.inner {
+            Type::Ident(ident) => {
+                cx.types.rust.contains(&ident.rust) || cx.types.aliases.contains_key(&ident.rust)
+            }
+            _ => false,
+        };
+
+    if !supported {
+        let mutable = if ty.mutable { "mut " } else { "" };
+        let mut msg = format!("unsupported &{}[T] element type", mutable);
+        if let Type::Ident(ident) = &ty.inner {
+            if is_opaque_cxx(cx, &ident.rust) {
+                msg += ": opaque C++ type is not supported yet";
+            }
+        }
+        cx.error(ty, msg);
+    }
+}
+
+fn check_type_array(cx: &mut Check, ty: &Array) {
+    let supported = !is_unsized(cx, &ty.inner);
+
+    if !supported {
+        cx.error(ty, "unsupported array element type");
+    }
+}
+
+fn check_type_fn(cx: &mut Check, ty: &Signature) {
+    if ty.throws {
+        cx.error(ty, "function pointer returning Result is not supported yet");
+    }
+
+    for arg in &ty.args {
+        if let Type::Ptr(_) = arg.ty {
+            if ty.unsafety.is_none() {
+                cx.error(
+                    arg,
+                    "pointer argument requires that the function pointer be marked unsafe",
+                );
+            }
+        }
+    }
+}
+
+fn check_api_struct(cx: &mut Check, strct: &Struct) {
+    let name = &strct.name;
+    check_reserved_name(cx, &name.rust);
+    check_lifetimes(cx, &strct.generics);
+
+    if strct.fields.is_empty() {
+        let span = span_for_struct_error(strct);
+        cx.error(span, "structs without any fields are not supported");
+    }
+
+    if cx.types.cxx.contains(&name.rust) {
+        if let Some(ety) = cx.types.untrusted.get(&name.rust) {
+            let msg = "extern shared struct must be declared in an `unsafe extern` block";
+            cx.error(ety, msg);
+        }
+    }
+
+    for derive in &strct.derives {
+        if derive.what == Trait::ExternType {
+            let msg = format!("derive({}) on shared struct is not supported", derive);
+            cx.error(derive, msg);
+        }
+    }
+
+    for field in &strct.fields {
+        if let Type::Fn(_) = field.ty {
+            cx.error(
+                field,
+                "function pointers in a struct field are not implemented yet",
+            );
+        } else if is_unsized(cx, &field.ty) {
+            let desc = describe(cx, &field.ty);
+            let msg = format!("using {} by value is not supported", desc);
+            cx.error(field, msg);
+        }
+    }
+}
+
+fn check_api_enum(cx: &mut Check, enm: &Enum) {
+    check_reserved_name(cx, &enm.name.rust);
+    check_lifetimes(cx, &enm.generics);
+
+    if enm.variants.is_empty() && !enm.explicit_repr && !enm.variants_from_header {
+        let span = span_for_enum_error(enm);
+        cx.error(
+            span,
+            "explicit #[repr(...)] is required for enum without any variants",
+        );
+    }
+
+    for derive in &enm.derives {
+        if derive.what == Trait::Default || derive.what == Trait::ExternType {
+            let msg = format!("derive({}) on shared enum is not supported", derive);
+            cx.error(derive, msg);
+        }
+    }
+}
+
+fn check_api_type(cx: &mut Check, ety: &ExternType) {
+    check_reserved_name(cx, &ety.name.rust);
+    check_lifetimes(cx, &ety.generics);
+
+    for derive in &ety.derives {
+        if derive.what == Trait::ExternType && ety.lang == Lang::Rust {
+            continue;
+        }
+        let lang = match ety.lang {
+            Lang::Rust => "Rust",
+            Lang::Cxx => "C++",
+        };
+        let msg = format!(
+            "derive({}) on opaque {} type is not supported yet",
+            derive, lang,
+        );
+        cx.error(derive, msg);
+    }
+
+    if !ety.bounds.is_empty() {
+        let bounds = &ety.bounds;
+        let span = quote!(#(#bounds)*);
+        cx.error(span, "extern type bounds are not implemented yet");
+    }
+
+    if let Some(reasons) = cx.types.required_trivial.get(&ety.name.rust) {
+        let msg = format!(
+            "needs a cxx::ExternType impl in order to be used as {}",
+            trivial::as_what(&ety.name, reasons),
+        );
+        cx.error(ety, msg);
+    }
+}
+
+fn check_api_fn(cx: &mut Check, efn: &ExternFn) {
+    match efn.lang {
+        Lang::Cxx => {
+            if !efn.generics.params.is_empty() && !efn.trusted {
+                let ref span = span_for_generics_error(efn);
+                cx.error(span, "extern C++ function with lifetimes must be declared in `unsafe extern \"C++\"` block");
+            }
+        }
+        Lang::Rust => {
+            if !efn.generics.params.is_empty() && efn.unsafety.is_none() {
+                let ref span = span_for_generics_error(efn);
+                let message = format!(
+                    "must be `unsafe fn {}` in order to expose explicit lifetimes to C++",
+                    efn.name.rust,
+                );
+                cx.error(span, message);
+            }
+        }
+    }
+
+    check_generics(cx, &efn.sig.generics);
+
+    if let Some(receiver) = &efn.receiver {
+        let ref span = span_for_receiver_error(receiver);
+
+        if receiver.ty.rust == "Self" {
+            let mutability = match receiver.mutable {
+                true => "mut ",
+                false => "",
+            };
+            let msg = format!(
+                "unnamed receiver type is only allowed if the surrounding extern block contains exactly one extern type; use `self: &{mutability}TheType`",
+                mutability = mutability,
+            );
+            cx.error(span, msg);
+        } else if cx.types.enums.contains_key(&receiver.ty.rust) {
+            cx.error(
+                span,
+                "unsupported receiver type; C++ does not allow member functions on enums",
+            );
+        } else if !cx.types.structs.contains_key(&receiver.ty.rust)
+            && !cx.types.cxx.contains(&receiver.ty.rust)
+            && !cx.types.rust.contains(&receiver.ty.rust)
+        {
+            cx.error(span, "unrecognized receiver type");
+        } else if receiver.mutable && !receiver.pinned && is_opaque_cxx(cx, &receiver.ty.rust) {
+            cx.error(
+                span,
+                format!(
+                    "mutable reference to opaque C++ type requires a pin -- use `self: Pin<&mut {}>`",
+                    receiver.ty.rust,
+                ),
+            );
+        }
+    }
+
+    for arg in &efn.args {
+        if let Type::Fn(_) = arg.ty {
+            if efn.lang == Lang::Rust {
+                cx.error(
+                    arg,
+                    "passing a function pointer from C++ to Rust is not implemented yet",
+                );
+            }
+        } else if let Type::Ptr(_) = arg.ty {
+            if efn.sig.unsafety.is_none() {
+                cx.error(
+                    arg,
+                    "pointer argument requires that the function be marked unsafe",
+                );
+            }
+        } else if is_unsized(cx, &arg.ty) {
+            let desc = describe(cx, &arg.ty);
+            let msg = format!("passing {} by value is not supported", desc);
+            cx.error(arg, msg);
+        }
+    }
+
+    if let Some(ty) = &efn.ret {
+        if let Type::Fn(_) = ty {
+            cx.error(ty, "returning a function pointer is not implemented yet");
+        } else if is_unsized(cx, ty) {
+            let desc = describe(cx, ty);
+            let msg = format!("returning {} by value is not supported", desc);
+            cx.error(ty, msg);
+        }
+    }
+
+    if efn.lang == Lang::Cxx {
+        check_mut_return_restriction(cx, efn);
+    }
+}
+
+fn check_api_type_alias(cx: &mut Check, alias: &TypeAlias) {
+    check_lifetimes(cx, &alias.generics);
+
+    for derive in &alias.derives {
+        let msg = format!("derive({}) on extern type alias is not supported", derive);
+        cx.error(derive, msg);
+    }
+}
+
+fn check_api_impl(cx: &mut Check, imp: &Impl) {
+    let ty = &imp.ty;
+
+    check_lifetimes(cx, &imp.impl_generics);
+
+    if let Some(negative) = imp.negative_token {
+        let span = quote!(#negative #ty);
+        cx.error(span, "negative impl is not supported yet");
+        return;
+    }
+
+    match ty {
+        Type::RustBox(ty)
+        | Type::RustVec(ty)
+        | Type::UniquePtr(ty)
+        | Type::SharedPtr(ty)
+        | Type::WeakPtr(ty)
+        | Type::CxxVector(ty) => {
+            if let Type::Ident(inner) = &ty.inner {
+                if Atom::from(&inner.rust).is_none() {
+                    return;
+                }
+            }
+        }
+        _ => {}
+    }
+
+    cx.error(imp, "unsupported Self type of explicit impl");
+}
+
+fn check_mut_return_restriction(cx: &mut Check, efn: &ExternFn) {
+    if efn.sig.unsafety.is_some() {
+        // Unrestricted as long as the function is made unsafe-to-call.
+        return;
+    }
+
+    match &efn.ret {
+        Some(Type::Ref(ty)) if ty.mutable => {}
+        Some(Type::SliceRef(slice)) if slice.mutable => {}
+        _ => return,
+    }
+
+    if let Some(receiver) = &efn.receiver {
+        if receiver.mutable {
+            return;
+        }
+        let resolve = match cx.types.try_resolve(&receiver.ty) {
+            Some(resolve) => resolve,
+            None => return,
+        };
+        if !resolve.generics.lifetimes.is_empty() {
+            return;
+        }
+    }
+
+    struct FindLifetimeMut<'a> {
+        cx: &'a Check<'a>,
+        found: bool,
+    }
+
+    impl<'t, 'a> Visit<'t> for FindLifetimeMut<'a> {
+        fn visit_type(&mut self, ty: &'t Type) {
+            self.found |= match ty {
+                Type::Ref(ty) => ty.mutable,
+                Type::SliceRef(slice) => slice.mutable,
+                Type::Ident(ident) if Atom::from(&ident.rust).is_none() => {
+                    match self.cx.types.try_resolve(ident) {
+                        Some(resolve) => !resolve.generics.lifetimes.is_empty(),
+                        None => true,
+                    }
+                }
+                _ => false,
+            };
+            visit::visit_type(self, ty);
+        }
+    }
+
+    let mut visitor = FindLifetimeMut { cx, found: false };
+
+    for arg in &efn.args {
+        visitor.visit_type(&arg.ty);
+    }
+
+    if visitor.found {
+        return;
+    }
+
+    cx.error(
+        efn,
+        "&mut return type is not allowed unless there is a &mut argument",
+    );
+}
+
+fn check_reserved_name(cx: &mut Check, ident: &Ident) {
+    if ident == "Box"
+        || ident == "UniquePtr"
+        || ident == "SharedPtr"
+        || ident == "WeakPtr"
+        || ident == "Vec"
+        || ident == "CxxVector"
+        || ident == "str"
+        || Atom::from(ident).is_some()
+    {
+        cx.error(ident, "reserved name");
+    }
+}
+
+fn check_reserved_lifetime(cx: &mut Check, lifetime: &Lifetime) {
+    if lifetime.ident == "static" {
+        match cx.generator {
+            Generator::Macro => { /* rustc already reports this */ }
+            Generator::Build => {
+                cx.error(lifetime, error::RESERVED_LIFETIME);
+            }
+        }
+    }
+}
+
+fn check_lifetimes(cx: &mut Check, generics: &Lifetimes) {
+    for lifetime in &generics.lifetimes {
+        check_reserved_lifetime(cx, lifetime);
+    }
+}
+
+fn check_generics(cx: &mut Check, generics: &Generics) {
+    for generic_param in &generics.params {
+        if let GenericParam::Lifetime(def) = generic_param {
+            check_reserved_lifetime(cx, &def.lifetime);
+        }
+    }
+}
+
+fn is_unsized(cx: &mut Check, ty: &Type) -> bool {
+    match ty {
+        Type::Ident(ident) => {
+            let ident = &ident.rust;
+            ident == CxxString || is_opaque_cxx(cx, ident) || cx.types.rust.contains(ident)
+        }
+        Type::Array(array) => is_unsized(cx, &array.inner),
+        Type::CxxVector(_) | Type::Fn(_) | Type::Void(_) => true,
+        Type::RustBox(_)
+        | Type::RustVec(_)
+        | Type::UniquePtr(_)
+        | Type::SharedPtr(_)
+        | Type::WeakPtr(_)
+        | Type::Ref(_)
+        | Type::Ptr(_)
+        | Type::Str(_)
+        | Type::SliceRef(_) => false,
+    }
+}
+
+fn is_opaque_cxx(cx: &mut Check, ty: &Ident) -> bool {
+    cx.types.cxx.contains(ty)
+        && !cx.types.structs.contains_key(ty)
+        && !cx.types.enums.contains_key(ty)
+        && !(cx.types.aliases.contains_key(ty) && cx.types.required_trivial.contains_key(ty))
+}
+
+fn span_for_struct_error(strct: &Struct) -> TokenStream {
+    let struct_token = strct.struct_token;
+    let mut brace_token = Group::new(Delimiter::Brace, TokenStream::new());
+    brace_token.set_span(strct.brace_token.span);
+    quote!(#struct_token #brace_token)
+}
+
+fn span_for_enum_error(enm: &Enum) -> TokenStream {
+    let enum_token = enm.enum_token;
+    let mut brace_token = Group::new(Delimiter::Brace, TokenStream::new());
+    brace_token.set_span(enm.brace_token.span);
+    quote!(#enum_token #brace_token)
+}
+
+fn span_for_receiver_error(receiver: &Receiver) -> TokenStream {
+    let ampersand = receiver.ampersand;
+    let lifetime = &receiver.lifetime;
+    let mutability = receiver.mutability;
+    if receiver.shorthand {
+        let var = receiver.var;
+        quote!(#ampersand #lifetime #mutability #var)
+    } else {
+        let ty = &receiver.ty;
+        quote!(#ampersand #lifetime #mutability #ty)
+    }
+}
+
+fn span_for_generics_error(efn: &ExternFn) -> TokenStream {
+    let unsafety = efn.unsafety;
+    let fn_token = efn.fn_token;
+    let generics = &efn.generics;
+    quote!(#unsafety #fn_token #generics)
+}
+
+fn describe(cx: &mut Check, ty: &Type) -> String {
+    match ty {
+        Type::Ident(ident) => {
+            if cx.types.structs.contains_key(&ident.rust) {
+                "struct".to_owned()
+            } else if cx.types.enums.contains_key(&ident.rust) {
+                "enum".to_owned()
+            } else if cx.types.aliases.contains_key(&ident.rust) {
+                "C++ type".to_owned()
+            } else if cx.types.cxx.contains(&ident.rust) {
+                "opaque C++ type".to_owned()
+            } else if cx.types.rust.contains(&ident.rust) {
+                "opaque Rust type".to_owned()
+            } else if Atom::from(&ident.rust) == Some(CxxString) {
+                "C++ string".to_owned()
+            } else if Atom::from(&ident.rust) == Some(Char) {
+                "C char".to_owned()
+            } else {
+                ident.rust.to_string()
+            }
+        }
+        Type::RustBox(_) => "Box".to_owned(),
+        Type::RustVec(_) => "Vec".to_owned(),
+        Type::UniquePtr(_) => "unique_ptr".to_owned(),
+        Type::SharedPtr(_) => "shared_ptr".to_owned(),
+        Type::WeakPtr(_) => "weak_ptr".to_owned(),
+        Type::Ref(_) => "reference".to_owned(),
+        Type::Ptr(_) => "raw pointer".to_owned(),
+        Type::Str(_) => "&str".to_owned(),
+        Type::CxxVector(_) => "C++ vector".to_owned(),
+        Type::SliceRef(_) => "slice".to_owned(),
+        Type::Fn(_) => "function pointer".to_owned(),
+        Type::Void(_) => "()".to_owned(),
+        Type::Array(_) => "array".to_owned(),
+    }
+}
diff --git a/src/syntax/derive.rs b/src/syntax/derive.rs
new file mode 100644 (file)
index 0000000..7727fbc
--- /dev/null
@@ -0,0 +1,81 @@
+use proc_macro2::{Ident, Span};
+use std::fmt::{self, Display};
+
+#[derive(Copy, Clone)]
+pub struct Derive {
+    pub what: Trait,
+    pub span: Span,
+}
+
+#[derive(Copy, Clone, PartialEq)]
+pub enum Trait {
+    Clone,
+    Copy,
+    Debug,
+    Default,
+    Eq,
+    ExternType,
+    Hash,
+    Ord,
+    PartialEq,
+    PartialOrd,
+    Serialize,
+    Deserialize,
+}
+
+impl Derive {
+    pub fn from(ident: &Ident) -> Option<Self> {
+        let what = match ident.to_string().as_str() {
+            "Clone" => Trait::Clone,
+            "Copy" => Trait::Copy,
+            "Debug" => Trait::Debug,
+            "Default" => Trait::Default,
+            "Eq" => Trait::Eq,
+            "ExternType" => Trait::ExternType,
+            "Hash" => Trait::Hash,
+            "Ord" => Trait::Ord,
+            "PartialEq" => Trait::PartialEq,
+            "PartialOrd" => Trait::PartialOrd,
+            "Serialize" => Trait::Serialize,
+            "Deserialize" => Trait::Deserialize,
+            _ => return None,
+        };
+        let span = ident.span();
+        Some(Derive { what, span })
+    }
+}
+
+impl PartialEq<Trait> for Derive {
+    fn eq(&self, other: &Trait) -> bool {
+        self.what == *other
+    }
+}
+
+impl AsRef<str> for Trait {
+    fn as_ref(&self) -> &str {
+        match self {
+            Trait::Clone => "Clone",
+            Trait::Copy => "Copy",
+            Trait::Debug => "Debug",
+            Trait::Default => "Default",
+            Trait::Eq => "Eq",
+            Trait::ExternType => "ExternType",
+            Trait::Hash => "Hash",
+            Trait::Ord => "Ord",
+            Trait::PartialEq => "PartialEq",
+            Trait::PartialOrd => "PartialOrd",
+            Trait::Serialize => "Serialize",
+            Trait::Deserialize => "Deserialize",
+        }
+    }
+}
+
+impl Display for Derive {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str(self.what.as_ref())
+    }
+}
+
+pub fn contains(derives: &[Derive], query: Trait) -> bool {
+    derives.iter().any(|derive| derive.what == query)
+}
diff --git a/src/syntax/discriminant.rs b/src/syntax/discriminant.rs
new file mode 100644 (file)
index 0000000..fff8f75
--- /dev/null
@@ -0,0 +1,335 @@
+use crate::syntax::Atom::{self, *};
+use proc_macro2::{Literal, Span, TokenStream};
+use quote::ToTokens;
+use std::cmp::Ordering;
+use std::collections::BTreeSet;
+use std::fmt::{self, Display};
+use std::str::FromStr;
+use std::u64;
+use syn::{Error, Expr, Lit, Result, Token, UnOp};
+
+pub struct DiscriminantSet {
+    repr: Option<Atom>,
+    values: BTreeSet<Discriminant>,
+    previous: Option<Discriminant>,
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct Discriminant {
+    sign: Sign,
+    magnitude: u64,
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+enum Sign {
+    Negative,
+    Positive,
+}
+
+impl DiscriminantSet {
+    pub fn new(repr: Option<Atom>) -> Self {
+        DiscriminantSet {
+            repr,
+            values: BTreeSet::new(),
+            previous: None,
+        }
+    }
+
+    pub fn insert(&mut self, expr: &Expr) -> Result<Discriminant> {
+        let (discriminant, repr) = expr_to_discriminant(expr)?;
+        match (self.repr, repr) {
+            (None, Some(new_repr)) => {
+                if let Some(limits) = Limits::of(new_repr) {
+                    for &past in &self.values {
+                        if limits.min <= past && past <= limits.max {
+                            continue;
+                        }
+                        let msg = format!(
+                            "discriminant value `{}` is outside the limits of {}",
+                            past, new_repr,
+                        );
+                        return Err(Error::new(Span::call_site(), msg));
+                    }
+                }
+                self.repr = Some(new_repr);
+            }
+            (Some(prev), Some(repr)) if prev != repr => {
+                let msg = format!("expected {}, found {}", prev, repr);
+                return Err(Error::new(Span::call_site(), msg));
+            }
+            _ => {}
+        }
+        insert(self, discriminant)
+    }
+
+    pub fn insert_next(&mut self) -> Result<Discriminant> {
+        let discriminant = match self.previous {
+            None => Discriminant::zero(),
+            Some(mut discriminant) => match discriminant.sign {
+                Sign::Negative => {
+                    discriminant.magnitude -= 1;
+                    if discriminant.magnitude == 0 {
+                        discriminant.sign = Sign::Positive;
+                    }
+                    discriminant
+                }
+                Sign::Positive => {
+                    if discriminant.magnitude == u64::MAX {
+                        let msg = format!("discriminant overflow on value after {}", u64::MAX);
+                        return Err(Error::new(Span::call_site(), msg));
+                    }
+                    discriminant.magnitude += 1;
+                    discriminant
+                }
+            },
+        };
+        insert(self, discriminant)
+    }
+
+    pub fn inferred_repr(&self) -> Result<Atom> {
+        if let Some(repr) = self.repr {
+            return Ok(repr);
+        }
+        if self.values.is_empty() {
+            return Ok(U8);
+        }
+        let min = *self.values.iter().next().unwrap();
+        let max = *self.values.iter().next_back().unwrap();
+        for limits in &LIMITS {
+            if limits.min <= min && max <= limits.max {
+                return Ok(limits.repr);
+            }
+        }
+        let msg = "these discriminant values do not fit in any supported enum repr type";
+        Err(Error::new(Span::call_site(), msg))
+    }
+}
+
+fn expr_to_discriminant(expr: &Expr) -> Result<(Discriminant, Option<Atom>)> {
+    match expr {
+        Expr::Lit(expr) => {
+            if let Lit::Int(lit) = &expr.lit {
+                let discriminant = lit.base10_parse::<Discriminant>()?;
+                let repr = parse_int_suffix(lit.suffix())?;
+                return Ok((discriminant, repr));
+            }
+        }
+        Expr::Unary(unary) => {
+            if let UnOp::Neg(_) = unary.op {
+                let (mut discriminant, repr) = expr_to_discriminant(&unary.expr)?;
+                discriminant.sign = match discriminant.sign {
+                    Sign::Positive => Sign::Negative,
+                    Sign::Negative => Sign::Positive,
+                };
+                return Ok((discriminant, repr));
+            }
+        }
+        _ => {}
+    }
+    Err(Error::new_spanned(
+        expr,
+        "enums with non-integer literal discriminants are not supported yet",
+    ))
+}
+
+fn insert(set: &mut DiscriminantSet, discriminant: Discriminant) -> Result<Discriminant> {
+    if let Some(expected_repr) = set.repr {
+        if let Some(limits) = Limits::of(expected_repr) {
+            if discriminant < limits.min || limits.max < discriminant {
+                let msg = format!(
+                    "discriminant value `{}` is outside the limits of {}",
+                    discriminant, expected_repr,
+                );
+                return Err(Error::new(Span::call_site(), msg));
+            }
+        }
+    }
+    set.values.insert(discriminant);
+    set.previous = Some(discriminant);
+    Ok(discriminant)
+}
+
+impl Discriminant {
+    pub const fn zero() -> Self {
+        Discriminant {
+            sign: Sign::Positive,
+            magnitude: 0,
+        }
+    }
+
+    const fn pos(u: u64) -> Self {
+        Discriminant {
+            sign: Sign::Positive,
+            magnitude: u,
+        }
+    }
+
+    const fn neg(i: i64) -> Self {
+        Discriminant {
+            sign: if i < 0 {
+                Sign::Negative
+            } else {
+                Sign::Positive
+            },
+            // This is `i.abs() as u64` but without overflow on MIN. Uses the
+            // fact that MIN.wrapping_abs() wraps back to MIN whose binary
+            // representation is 1<<63, and thus the `as u64` conversion
+            // produces 1<<63 too which happens to be the correct unsigned
+            // magnitude.
+            magnitude: i.wrapping_abs() as u64,
+        }
+    }
+
+    pub const fn checked_succ(self) -> Option<Self> {
+        match self.sign {
+            Sign::Negative => {
+                if self.magnitude == 1 {
+                    Some(Discriminant::zero())
+                } else {
+                    Some(Discriminant {
+                        sign: Sign::Negative,
+                        magnitude: self.magnitude - 1,
+                    })
+                }
+            }
+            Sign::Positive => match self.magnitude.checked_add(1) {
+                Some(magnitude) => Some(Discriminant {
+                    sign: Sign::Positive,
+                    magnitude,
+                }),
+                None => None,
+            },
+        }
+    }
+}
+
+impl Display for Discriminant {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        if self.sign == Sign::Negative {
+            f.write_str("-")?;
+        }
+        write!(f, "{}", self.magnitude)
+    }
+}
+
+impl ToTokens for Discriminant {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        if self.sign == Sign::Negative {
+            Token![-](Span::call_site()).to_tokens(tokens);
+        }
+        Literal::u64_unsuffixed(self.magnitude).to_tokens(tokens);
+    }
+}
+
+impl FromStr for Discriminant {
+    type Err = Error;
+
+    fn from_str(mut s: &str) -> Result<Self> {
+        let sign = if s.starts_with('-') {
+            s = &s[1..];
+            Sign::Negative
+        } else {
+            Sign::Positive
+        };
+        match s.parse::<u64>() {
+            Ok(magnitude) => Ok(Discriminant { sign, magnitude }),
+            Err(_) => Err(Error::new(
+                Span::call_site(),
+                "discriminant value outside of supported range",
+            )),
+        }
+    }
+}
+
+impl Ord for Discriminant {
+    fn cmp(&self, other: &Self) -> Ordering {
+        use self::Sign::{Negative, Positive};
+        match (self.sign, other.sign) {
+            (Negative, Negative) => self.magnitude.cmp(&other.magnitude).reverse(),
+            (Negative, Positive) => Ordering::Less, // negative < positive
+            (Positive, Negative) => Ordering::Greater, // positive > negative
+            (Positive, Positive) => self.magnitude.cmp(&other.magnitude),
+        }
+    }
+}
+
+impl PartialOrd for Discriminant {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+fn parse_int_suffix(suffix: &str) -> Result<Option<Atom>> {
+    if suffix.is_empty() {
+        return Ok(None);
+    }
+    if let Some(atom) = Atom::from_str(suffix) {
+        match atom {
+            U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize => return Ok(Some(atom)),
+            _ => {}
+        }
+    }
+    let msg = format!("unrecognized integer suffix: `{}`", suffix);
+    Err(Error::new(Span::call_site(), msg))
+}
+
+#[derive(Copy, Clone)]
+struct Limits {
+    repr: Atom,
+    min: Discriminant,
+    max: Discriminant,
+}
+
+impl Limits {
+    fn of(repr: Atom) -> Option<Limits> {
+        for limits in &LIMITS {
+            if limits.repr == repr {
+                return Some(*limits);
+            }
+        }
+        None
+    }
+}
+
+const LIMITS: [Limits; 8] = [
+    Limits {
+        repr: U8,
+        min: Discriminant::zero(),
+        max: Discriminant::pos(std::u8::MAX as u64),
+    },
+    Limits {
+        repr: I8,
+        min: Discriminant::neg(std::i8::MIN as i64),
+        max: Discriminant::pos(std::i8::MAX as u64),
+    },
+    Limits {
+        repr: U16,
+        min: Discriminant::zero(),
+        max: Discriminant::pos(std::u16::MAX as u64),
+    },
+    Limits {
+        repr: I16,
+        min: Discriminant::neg(std::i16::MIN as i64),
+        max: Discriminant::pos(std::i16::MAX as u64),
+    },
+    Limits {
+        repr: U32,
+        min: Discriminant::zero(),
+        max: Discriminant::pos(std::u32::MAX as u64),
+    },
+    Limits {
+        repr: I32,
+        min: Discriminant::neg(std::i32::MIN as i64),
+        max: Discriminant::pos(std::i32::MAX as u64),
+    },
+    Limits {
+        repr: U64,
+        min: Discriminant::zero(),
+        max: Discriminant::pos(std::u64::MAX),
+    },
+    Limits {
+        repr: I64,
+        min: Discriminant::neg(std::i64::MIN),
+        max: Discriminant::pos(std::i64::MAX as u64),
+    },
+];
diff --git a/src/syntax/doc.rs b/src/syntax/doc.rs
new file mode 100644 (file)
index 0000000..55a1de1
--- /dev/null
@@ -0,0 +1,44 @@
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+use syn::LitStr;
+
+pub struct Doc {
+    pub(crate) hidden: bool,
+    fragments: Vec<LitStr>,
+}
+
+impl Doc {
+    pub fn new() -> Self {
+        Doc {
+            hidden: false,
+            fragments: Vec::new(),
+        }
+    }
+
+    pub fn push(&mut self, lit: LitStr) {
+        self.fragments.push(lit);
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.fragments.is_empty()
+    }
+
+    pub fn to_string(&self) -> String {
+        let mut doc = String::new();
+        for lit in &self.fragments {
+            doc += &lit.value();
+            doc.push('\n');
+        }
+        doc
+    }
+}
+
+impl ToTokens for Doc {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let fragments = &self.fragments;
+        tokens.extend(quote! { #(#[doc = #fragments])* });
+        if self.hidden {
+            tokens.extend(quote! { #[doc(hidden)] });
+        }
+    }
+}
diff --git a/src/syntax/error.rs b/src/syntax/error.rs
new file mode 100644 (file)
index 0000000..f40c4a8
--- /dev/null
@@ -0,0 +1,98 @@
+use std::fmt::{self, Display};
+
+#[derive(Copy, Clone)]
+pub struct Error {
+    pub msg: &'static str,
+    pub label: Option<&'static str>,
+    pub note: Option<&'static str>,
+}
+
+impl Display for Error {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        self.msg.fmt(formatter)
+    }
+}
+
+pub static ERRORS: &[Error] = &[
+    BOX_CXX_TYPE,
+    CXXBRIDGE_RESERVED,
+    CXX_STRING_BY_VALUE,
+    CXX_TYPE_BY_VALUE,
+    DISCRIMINANT_OVERFLOW,
+    DOT_INCLUDE,
+    DOUBLE_UNDERSCORE,
+    RESERVED_LIFETIME,
+    RUST_TYPE_BY_VALUE,
+    UNSUPPORTED_TYPE,
+    USE_NOT_ALLOWED,
+];
+
+pub static BOX_CXX_TYPE: Error = Error {
+    msg: "Box of a C++ type is not supported yet",
+    label: None,
+    note: Some("hint: use UniquePtr<> or SharedPtr<>"),
+};
+
+pub static CXXBRIDGE_RESERVED: Error = Error {
+    msg: "identifiers starting with cxxbridge are reserved",
+    label: Some("reserved identifier"),
+    note: Some("identifiers starting with cxxbridge are reserved"),
+};
+
+pub static CXX_STRING_BY_VALUE: Error = Error {
+    msg: "C++ string by value is not supported",
+    label: None,
+    note: Some("hint: wrap it in a UniquePtr<>"),
+};
+
+pub static CXX_TYPE_BY_VALUE: Error = Error {
+    msg: "C++ type by value is not supported",
+    label: None,
+    note: Some("hint: wrap it in a UniquePtr<> or SharedPtr<>"),
+};
+
+pub static DISCRIMINANT_OVERFLOW: Error = Error {
+    msg: "discriminant overflow on value after ",
+    label: Some("discriminant overflow"),
+    note: Some("note: explicitly set `= 0` if that is desired outcome"),
+};
+
+pub static DOT_INCLUDE: Error = Error {
+    msg: "#include relative to `.` or `..` is not supported in Cargo builds",
+    label: Some("#include relative to `.` or `..` is not supported in Cargo builds"),
+    note: Some("note: use a path starting with the crate name"),
+};
+
+pub static DOUBLE_UNDERSCORE: Error = Error {
+    msg: "identifiers containing double underscore are reserved in C++",
+    label: Some("reserved identifier"),
+    note: Some("identifiers containing double underscore are reserved in C++"),
+};
+
+pub static RESERVED_LIFETIME: Error = Error {
+    msg: "invalid lifetime parameter name: `'static`",
+    label: Some("'static is a reserved lifetime name"),
+    note: None,
+};
+
+pub static RUST_TYPE_BY_VALUE: Error = Error {
+    msg: "opaque Rust type by value is not supported",
+    label: None,
+    note: Some("hint: wrap it in a Box<>"),
+};
+
+pub static UNSUPPORTED_TYPE: Error = Error {
+    msg: "unsupported type: ",
+    label: Some("unsupported type"),
+    note: None,
+};
+
+pub static USE_NOT_ALLOWED: Error = Error {
+    msg: "`use` items are not allowed within cxx bridge",
+    label: Some("not allowed"),
+    note: Some(
+        "`use` items are not allowed within cxx bridge; only types defined\n\
+         within your bridge, primitive types, or types exported by the cxx\n\
+         crate may be used",
+    ),
+};
diff --git a/src/syntax/file.rs b/src/syntax/file.rs
new file mode 100644 (file)
index 0000000..99466b8
--- /dev/null
@@ -0,0 +1,123 @@
+use crate::syntax::namespace::Namespace;
+use quote::quote;
+use syn::parse::{Error, Parse, ParseStream, Result};
+use syn::{
+    braced, token, Abi, Attribute, ForeignItem, Ident, Item as RustItem, ItemEnum, ItemImpl,
+    ItemStruct, ItemUse, LitStr, Token, Visibility,
+};
+
+pub struct Module {
+    pub namespace: Namespace,
+    pub attrs: Vec<Attribute>,
+    pub vis: Visibility,
+    pub unsafety: Option<Token![unsafe]>,
+    pub mod_token: Token![mod],
+    pub ident: Ident,
+    pub brace_token: token::Brace,
+    pub content: Vec<Item>,
+}
+
+pub enum Item {
+    Struct(ItemStruct),
+    Enum(ItemEnum),
+    ForeignMod(ItemForeignMod),
+    Use(ItemUse),
+    Impl(ItemImpl),
+    Other(RustItem),
+}
+
+pub struct ItemForeignMod {
+    pub attrs: Vec<Attribute>,
+    pub unsafety: Option<Token![unsafe]>,
+    pub abi: Abi,
+    pub brace_token: token::Brace,
+    pub items: Vec<ForeignItem>,
+}
+
+impl Parse for Module {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let namespace = Namespace::ROOT;
+        let mut attrs = input.call(Attribute::parse_outer)?;
+        let vis: Visibility = input.parse()?;
+        let unsafety: Option<Token![unsafe]> = input.parse()?;
+        let mod_token: Token![mod] = input.parse()?;
+        let ident: Ident = input.parse()?;
+
+        let semi: Option<Token![;]> = input.parse()?;
+        if let Some(semi) = semi {
+            let span = quote!(#vis #mod_token #semi);
+            return Err(Error::new_spanned(
+                span,
+                "#[cxx::bridge] module must have inline contents",
+            ));
+        }
+
+        let content;
+        let brace_token = braced!(content in input);
+        attrs.extend(content.call(Attribute::parse_inner)?);
+
+        let mut items = Vec::new();
+        while !content.is_empty() {
+            items.push(content.parse()?);
+        }
+
+        Ok(Module {
+            namespace,
+            attrs,
+            vis,
+            unsafety,
+            mod_token,
+            ident,
+            brace_token,
+            content: items,
+        })
+    }
+}
+
+impl Parse for Item {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let attrs = input.call(Attribute::parse_outer)?;
+
+        let ahead = input.fork();
+        let unsafety = if ahead.parse::<Option<Token![unsafe]>>()?.is_some()
+            && ahead.parse::<Option<Token![extern]>>()?.is_some()
+            && ahead.parse::<Option<LitStr>>().is_ok()
+            && ahead.peek(token::Brace)
+        {
+            Some(input.parse()?)
+        } else {
+            None
+        };
+
+        let item = input.parse()?;
+        match item {
+            RustItem::Struct(mut item) => {
+                item.attrs.splice(..0, attrs);
+                Ok(Item::Struct(item))
+            }
+            RustItem::Enum(mut item) => {
+                item.attrs.splice(..0, attrs);
+                Ok(Item::Enum(item))
+            }
+            RustItem::ForeignMod(mut item) => {
+                item.attrs.splice(..0, attrs);
+                Ok(Item::ForeignMod(ItemForeignMod {
+                    attrs: item.attrs,
+                    unsafety,
+                    abi: item.abi,
+                    brace_token: item.brace_token,
+                    items: item.items,
+                }))
+            }
+            RustItem::Impl(mut item) => {
+                item.attrs.splice(..0, attrs);
+                Ok(Item::Impl(item))
+            }
+            RustItem::Use(mut item) => {
+                item.attrs.splice(..0, attrs);
+                Ok(Item::Use(item))
+            }
+            other => Ok(Item::Other(other)),
+        }
+    }
+}
diff --git a/src/syntax/ident.rs b/src/syntax/ident.rs
new file mode 100644 (file)
index 0000000..bb2281e
--- /dev/null
@@ -0,0 +1,57 @@
+use crate::syntax::check::Check;
+use crate::syntax::{error, Api, Pair};
+
+fn check(cx: &mut Check, name: &Pair) {
+    for segment in &name.namespace {
+        check_cxx_ident(cx, &segment.to_string());
+    }
+    check_cxx_ident(cx, &name.cxx.to_string());
+    check_rust_ident(cx, &name.rust.to_string());
+
+    fn check_cxx_ident(cx: &mut Check, ident: &str) {
+        if ident.starts_with("cxxbridge") {
+            cx.error(ident, error::CXXBRIDGE_RESERVED.msg);
+        }
+        if ident.contains("__") {
+            cx.error(ident, error::DOUBLE_UNDERSCORE.msg);
+        }
+    }
+
+    fn check_rust_ident(cx: &mut Check, ident: &str) {
+        if ident.starts_with("cxxbridge") {
+            cx.error(ident, error::CXXBRIDGE_RESERVED.msg);
+        }
+    }
+}
+
+pub(crate) fn check_all(cx: &mut Check, apis: &[Api]) {
+    for api in apis {
+        match api {
+            Api::Include(_) | Api::Impl(_) => {}
+            Api::Struct(strct) => {
+                check(cx, &strct.name);
+                for field in &strct.fields {
+                    check(cx, &field.name);
+                }
+            }
+            Api::Enum(enm) => {
+                check(cx, &enm.name);
+                for variant in &enm.variants {
+                    check(cx, &variant.name);
+                }
+            }
+            Api::CxxType(ety) | Api::RustType(ety) => {
+                check(cx, &ety.name);
+            }
+            Api::CxxFunction(efn) | Api::RustFunction(efn) => {
+                check(cx, &efn.name);
+                for arg in &efn.args {
+                    check(cx, &arg.name);
+                }
+            }
+            Api::TypeAlias(alias) => {
+                check(cx, &alias.name);
+            }
+        }
+    }
+}
diff --git a/src/syntax/impls.rs b/src/syntax/impls.rs
new file mode 100644 (file)
index 0000000..06d68dc
--- /dev/null
@@ -0,0 +1,440 @@
+use crate::syntax::{
+    Array, ExternFn, Include, Lifetimes, Ptr, Receiver, Ref, Signature, SliceRef, Ty1, Type, Var,
+};
+use std::hash::{Hash, Hasher};
+use std::mem;
+use std::ops::{Deref, DerefMut};
+
+impl PartialEq for Include {
+    fn eq(&self, other: &Self) -> bool {
+        let Include {
+            path,
+            kind,
+            begin_span: _,
+            end_span: _,
+        } = self;
+        let Include {
+            path: path2,
+            kind: kind2,
+            begin_span: _,
+            end_span: _,
+        } = other;
+        path == path2 && kind == kind2
+    }
+}
+
+impl Deref for ExternFn {
+    type Target = Signature;
+
+    fn deref(&self) -> &Self::Target {
+        &self.sig
+    }
+}
+
+impl DerefMut for ExternFn {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.sig
+    }
+}
+
+impl Hash for Type {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        mem::discriminant(self).hash(state);
+        match self {
+            Type::Ident(t) => t.hash(state),
+            Type::RustBox(t) => t.hash(state),
+            Type::UniquePtr(t) => t.hash(state),
+            Type::SharedPtr(t) => t.hash(state),
+            Type::WeakPtr(t) => t.hash(state),
+            Type::Ref(t) => t.hash(state),
+            Type::Ptr(t) => t.hash(state),
+            Type::Str(t) => t.hash(state),
+            Type::RustVec(t) => t.hash(state),
+            Type::CxxVector(t) => t.hash(state),
+            Type::Fn(t) => t.hash(state),
+            Type::SliceRef(t) => t.hash(state),
+            Type::Array(t) => t.hash(state),
+            Type::Void(_) => {}
+        }
+    }
+}
+
+impl Eq for Type {}
+
+impl PartialEq for Type {
+    fn eq(&self, other: &Self) -> bool {
+        match (self, other) {
+            (Type::Ident(lhs), Type::Ident(rhs)) => lhs == rhs,
+            (Type::RustBox(lhs), Type::RustBox(rhs)) => lhs == rhs,
+            (Type::UniquePtr(lhs), Type::UniquePtr(rhs)) => lhs == rhs,
+            (Type::SharedPtr(lhs), Type::SharedPtr(rhs)) => lhs == rhs,
+            (Type::WeakPtr(lhs), Type::WeakPtr(rhs)) => lhs == rhs,
+            (Type::Ref(lhs), Type::Ref(rhs)) => lhs == rhs,
+            (Type::Str(lhs), Type::Str(rhs)) => lhs == rhs,
+            (Type::RustVec(lhs), Type::RustVec(rhs)) => lhs == rhs,
+            (Type::CxxVector(lhs), Type::CxxVector(rhs)) => lhs == rhs,
+            (Type::Fn(lhs), Type::Fn(rhs)) => lhs == rhs,
+            (Type::SliceRef(lhs), Type::SliceRef(rhs)) => lhs == rhs,
+            (Type::Void(_), Type::Void(_)) => true,
+            (_, _) => false,
+        }
+    }
+}
+
+impl Eq for Lifetimes {}
+
+impl PartialEq for Lifetimes {
+    fn eq(&self, other: &Self) -> bool {
+        let Lifetimes {
+            lt_token: _,
+            lifetimes,
+            gt_token: _,
+        } = self;
+        let Lifetimes {
+            lt_token: _,
+            lifetimes: lifetimes2,
+            gt_token: _,
+        } = other;
+        lifetimes.iter().eq(lifetimes2)
+    }
+}
+
+impl Hash for Lifetimes {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let Lifetimes {
+            lt_token: _,
+            lifetimes,
+            gt_token: _,
+        } = self;
+        lifetimes.len().hash(state);
+        for lifetime in lifetimes {
+            lifetime.hash(state);
+        }
+    }
+}
+
+impl Eq for Ty1 {}
+
+impl PartialEq for Ty1 {
+    fn eq(&self, other: &Self) -> bool {
+        let Ty1 {
+            name,
+            langle: _,
+            inner,
+            rangle: _,
+        } = self;
+        let Ty1 {
+            name: name2,
+            langle: _,
+            inner: inner2,
+            rangle: _,
+        } = other;
+        name == name2 && inner == inner2
+    }
+}
+
+impl Hash for Ty1 {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let Ty1 {
+            name,
+            langle: _,
+            inner,
+            rangle: _,
+        } = self;
+        name.hash(state);
+        inner.hash(state);
+    }
+}
+
+impl Eq for Ref {}
+
+impl PartialEq for Ref {
+    fn eq(&self, other: &Self) -> bool {
+        let Ref {
+            pinned,
+            ampersand: _,
+            lifetime,
+            mutable,
+            inner,
+            pin_tokens: _,
+            mutability: _,
+        } = self;
+        let Ref {
+            pinned: pinned2,
+            ampersand: _,
+            lifetime: lifetime2,
+            mutable: mutable2,
+            inner: inner2,
+            pin_tokens: _,
+            mutability: _,
+        } = other;
+        pinned == pinned2 && lifetime == lifetime2 && mutable == mutable2 && inner == inner2
+    }
+}
+
+impl Hash for Ref {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let Ref {
+            pinned,
+            ampersand: _,
+            lifetime,
+            mutable,
+            inner,
+            pin_tokens: _,
+            mutability: _,
+        } = self;
+        pinned.hash(state);
+        lifetime.hash(state);
+        mutable.hash(state);
+        inner.hash(state);
+    }
+}
+
+impl Eq for Ptr {}
+
+impl PartialEq for Ptr {
+    fn eq(&self, other: &Ptr) -> bool {
+        let Ptr {
+            star: _,
+            mutable,
+            inner,
+            mutability: _,
+            constness: _,
+        } = self;
+        let Ptr {
+            star: _,
+            mutable: mutable2,
+            inner: inner2,
+            mutability: _,
+            constness: _,
+        } = other;
+        mutable == mutable2 && inner == inner2
+    }
+}
+
+impl Hash for Ptr {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let Ptr {
+            star: _,
+            mutable,
+            inner,
+            mutability: _,
+            constness: _,
+        } = self;
+        mutable.hash(state);
+        inner.hash(state);
+    }
+}
+
+impl Eq for SliceRef {}
+
+impl PartialEq for SliceRef {
+    fn eq(&self, other: &Self) -> bool {
+        let SliceRef {
+            ampersand: _,
+            lifetime,
+            mutable,
+            bracket: _,
+            inner,
+            mutability: _,
+        } = self;
+        let SliceRef {
+            ampersand: _,
+            lifetime: lifetime2,
+            mutable: mutable2,
+            bracket: _,
+            inner: inner2,
+            mutability: _,
+        } = other;
+        lifetime == lifetime2 && mutable == mutable2 && inner == inner2
+    }
+}
+
+impl Hash for SliceRef {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let SliceRef {
+            ampersand: _,
+            lifetime,
+            mutable,
+            bracket: _,
+            inner,
+            mutability: _,
+        } = self;
+        lifetime.hash(state);
+        mutable.hash(state);
+        inner.hash(state);
+    }
+}
+
+impl Eq for Array {}
+
+impl PartialEq for Array {
+    fn eq(&self, other: &Self) -> bool {
+        let Array {
+            bracket: _,
+            inner,
+            semi_token: _,
+            len,
+            len_token: _,
+        } = self;
+        let Array {
+            bracket: _,
+            inner: inner2,
+            semi_token: _,
+            len: len2,
+            len_token: _,
+        } = other;
+        inner == inner2 && len == len2
+    }
+}
+
+impl Hash for Array {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let Array {
+            bracket: _,
+            inner,
+            semi_token: _,
+            len,
+            len_token: _,
+        } = self;
+        inner.hash(state);
+        len.hash(state);
+    }
+}
+
+impl Eq for Signature {}
+
+impl PartialEq for Signature {
+    fn eq(&self, other: &Self) -> bool {
+        let Signature {
+            unsafety,
+            fn_token: _,
+            generics: _,
+            receiver,
+            args,
+            ret,
+            throws,
+            paren_token: _,
+            throws_tokens: _,
+        } = self;
+        let Signature {
+            unsafety: unsafety2,
+            fn_token: _,
+            generics: _,
+            receiver: receiver2,
+            args: args2,
+            ret: ret2,
+            throws: throws2,
+            paren_token: _,
+            throws_tokens: _,
+        } = other;
+        unsafety.is_some() == unsafety2.is_some()
+            && receiver == receiver2
+            && ret == ret2
+            && throws == throws2
+            && args.len() == args2.len()
+            && args.iter().zip(args2).all(|(arg, arg2)| {
+                let Var {
+                    doc: _,
+                    attrs: _,
+                    visibility: _,
+                    name: _,
+                    colon_token: _,
+                    ty,
+                } = arg;
+                let Var {
+                    doc: _,
+                    attrs: _,
+                    visibility: _,
+                    name: _,
+                    colon_token: _,
+                    ty: ty2,
+                } = arg2;
+                ty == ty2
+            })
+    }
+}
+
+impl Hash for Signature {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let Signature {
+            unsafety,
+            fn_token: _,
+            generics: _,
+            receiver,
+            args,
+            ret,
+            throws,
+            paren_token: _,
+            throws_tokens: _,
+        } = self;
+        unsafety.is_some().hash(state);
+        receiver.hash(state);
+        for arg in args {
+            let Var {
+                doc: _,
+                attrs: _,
+                visibility: _,
+                name: _,
+                colon_token: _,
+                ty,
+            } = arg;
+            ty.hash(state);
+        }
+        ret.hash(state);
+        throws.hash(state);
+    }
+}
+
+impl Eq for Receiver {}
+
+impl PartialEq for Receiver {
+    fn eq(&self, other: &Self) -> bool {
+        let Receiver {
+            pinned,
+            ampersand: _,
+            lifetime,
+            mutable,
+            var: _,
+            colon_token: _,
+            ty,
+            shorthand: _,
+            pin_tokens: _,
+            mutability: _,
+        } = self;
+        let Receiver {
+            pinned: pinned2,
+            ampersand: _,
+            lifetime: lifetime2,
+            mutable: mutable2,
+            var: _,
+            colon_token: _,
+            ty: ty2,
+            shorthand: _,
+            pin_tokens: _,
+            mutability: _,
+        } = other;
+        pinned == pinned2 && lifetime == lifetime2 && mutable == mutable2 && ty == ty2
+    }
+}
+
+impl Hash for Receiver {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let Receiver {
+            pinned,
+            ampersand: _,
+            lifetime,
+            mutable,
+            var: _,
+            colon_token: _,
+            ty,
+            shorthand: _,
+            pin_tokens: _,
+            mutability: _,
+        } = self;
+        pinned.hash(state);
+        lifetime.hash(state);
+        mutable.hash(state);
+        ty.hash(state);
+    }
+}
diff --git a/src/syntax/improper.rs b/src/syntax/improper.rs
new file mode 100644 (file)
index 0000000..f19eb86
--- /dev/null
@@ -0,0 +1,39 @@
+use self::ImproperCtype::*;
+use crate::syntax::atom::Atom::{self, *};
+use crate::syntax::{Type, Types};
+use proc_macro2::Ident;
+
+pub enum ImproperCtype<'a> {
+    Definite(bool),
+    Depends(&'a Ident),
+}
+
+impl<'a> Types<'a> {
+    // yes, no, maybe
+    pub fn determine_improper_ctype(&self, ty: &Type) -> ImproperCtype<'a> {
+        match ty {
+            Type::Ident(ident) => {
+                let ident = &ident.rust;
+                if let Some(atom) = Atom::from(ident) {
+                    Definite(atom == RustString)
+                } else if let Some(strct) = self.structs.get(ident) {
+                    Depends(&strct.name.rust) // iterate to fixed-point
+                } else {
+                    Definite(self.rust.contains(ident) || self.aliases.contains_key(ident))
+                }
+            }
+            Type::RustBox(_)
+            | Type::RustVec(_)
+            | Type::Str(_)
+            | Type::Fn(_)
+            | Type::Void(_)
+            | Type::SliceRef(_) => Definite(true),
+            Type::UniquePtr(_) | Type::SharedPtr(_) | Type::WeakPtr(_) | Type::CxxVector(_) => {
+                Definite(false)
+            }
+            Type::Ref(ty) => self.determine_improper_ctype(&ty.inner),
+            Type::Ptr(ty) => self.determine_improper_ctype(&ty.inner),
+            Type::Array(ty) => self.determine_improper_ctype(&ty.inner),
+        }
+    }
+}
diff --git a/src/syntax/instantiate.rs b/src/syntax/instantiate.rs
new file mode 100644 (file)
index 0000000..b6cbf24
--- /dev/null
@@ -0,0 +1,80 @@
+use crate::syntax::{NamedType, Ty1, Type};
+use proc_macro2::{Ident, Span};
+use std::hash::{Hash, Hasher};
+use syn::Token;
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+pub enum ImplKey<'a> {
+    RustBox(NamedImplKey<'a>),
+    RustVec(NamedImplKey<'a>),
+    UniquePtr(NamedImplKey<'a>),
+    SharedPtr(NamedImplKey<'a>),
+    WeakPtr(NamedImplKey<'a>),
+    CxxVector(NamedImplKey<'a>),
+}
+
+#[derive(Copy, Clone)]
+pub struct NamedImplKey<'a> {
+    pub begin_span: Span,
+    pub rust: &'a Ident,
+    pub lt_token: Option<Token![<]>,
+    pub gt_token: Option<Token![>]>,
+    pub end_span: Span,
+}
+
+impl Type {
+    pub(crate) fn impl_key(&self) -> Option<ImplKey> {
+        if let Type::RustBox(ty) = self {
+            if let Type::Ident(ident) = &ty.inner {
+                return Some(ImplKey::RustBox(NamedImplKey::new(ty, ident)));
+            }
+        } else if let Type::RustVec(ty) = self {
+            if let Type::Ident(ident) = &ty.inner {
+                return Some(ImplKey::RustVec(NamedImplKey::new(ty, ident)));
+            }
+        } else if let Type::UniquePtr(ty) = self {
+            if let Type::Ident(ident) = &ty.inner {
+                return Some(ImplKey::UniquePtr(NamedImplKey::new(ty, ident)));
+            }
+        } else if let Type::SharedPtr(ty) = self {
+            if let Type::Ident(ident) = &ty.inner {
+                return Some(ImplKey::SharedPtr(NamedImplKey::new(ty, ident)));
+            }
+        } else if let Type::WeakPtr(ty) = self {
+            if let Type::Ident(ident) = &ty.inner {
+                return Some(ImplKey::WeakPtr(NamedImplKey::new(ty, ident)));
+            }
+        } else if let Type::CxxVector(ty) = self {
+            if let Type::Ident(ident) = &ty.inner {
+                return Some(ImplKey::CxxVector(NamedImplKey::new(ty, ident)));
+            }
+        }
+        None
+    }
+}
+
+impl<'a> PartialEq for NamedImplKey<'a> {
+    fn eq(&self, other: &Self) -> bool {
+        PartialEq::eq(self.rust, other.rust)
+    }
+}
+
+impl<'a> Eq for NamedImplKey<'a> {}
+
+impl<'a> Hash for NamedImplKey<'a> {
+    fn hash<H: Hasher>(&self, hasher: &mut H) {
+        self.rust.hash(hasher);
+    }
+}
+
+impl<'a> NamedImplKey<'a> {
+    fn new(outer: &Ty1, inner: &'a NamedType) -> Self {
+        NamedImplKey {
+            begin_span: outer.name.span(),
+            rust: &inner.rust,
+            lt_token: inner.generics.lt_token,
+            gt_token: inner.generics.gt_token,
+            end_span: outer.rangle.span,
+        }
+    }
+}
diff --git a/src/syntax/mangle.rs b/src/syntax/mangle.rs
new file mode 100644 (file)
index 0000000..287b443
--- /dev/null
@@ -0,0 +1,120 @@
+// Mangled symbol arrangements:
+//
+//   (a) One-off internal symbol.
+//          pattern:  {CXXBRIDGE} $ {NAME}
+//          examples:
+//             - cxxbridge1$exception
+//          defining characteristics:
+//             - 2 segments
+//             - starts with cxxbridge
+//
+//   (b) Behavior on a builtin binding without generic parameter.
+//          pattern:  {CXXBRIDGE} $ {TYPE} $ {NAME}
+//          examples:
+//             - cxxbridge1$string$len
+//          defining characteristics:
+//             - 3 segments
+//             - starts with cxxbridge
+//
+//   (c) Behavior on a builtin binding with generic parameter.
+//          pattern:  {CXXBRIDGE} $ {TYPE} $ {PARAM...} $ {NAME}
+//          examples:
+//             - cxxbridge1$box$org$rust$Struct$alloc
+//             - cxxbridge1$unique_ptr$std$vector$u8$drop
+//          defining characteristics:
+//             - 4+ segments
+//             - starts with cxxbridge
+//
+//   (d) User-defined extern function.
+//          pattern:  {NAMESPACE...} $ {CXXBRIDGE} $ {NAME}
+//          examples:
+//             - cxxbridge1$new_client
+//             - org$rust$cxxbridge1$new_client
+//          defining characteristics:
+//             - cxxbridge is second from end
+//          FIXME: conflict with (a) if they collide with one of our one-off symbol names in the global namespace
+//
+//   (e) User-defined extern member function.
+//          pattern:  {NAMESPACE...} $ {CXXBRIDGE} $ {TYPE} $ {NAME}
+//          examples:
+//             - org$cxxbridge1$Struct$get
+//          defining characteristics:
+//             - cxxbridge is third from end
+//          FIXME: conflict with (b) if e.g. user binds a type in global namespace that collides with our builtin type names
+//
+//   (f) Operator overload.
+//          pattern:  {NAMESPACE...} $ {CXXBRIDGE} $ {TYPE} $ operator $ {NAME}
+//          examples:
+//             - org$rust$cxxbridge1$Struct$operator$eq
+//          defining characteristics:
+//             - second segment from end is `operator` (not possible in type or namespace names)
+//
+//   (g) Closure trampoline.
+//          pattern:  {NAMESPACE...} $ {CXXBRIDGE} $ {TYPE?} $ {NAME} $ {ARGUMENT} $ {DIRECTION}
+//          examples:
+//             - org$rust$cxxbridge1$Struct$invoke$f$0
+//          defining characteristics:
+//             - last symbol is `0` (C half) or `1` (Rust half) which are not legal identifiers on their own
+//
+//
+// Mangled preprocessor variable arrangements:
+//
+//   (A) One-off internal variable.
+//          pattern:  {CXXBRIDGE} _ {NAME}
+//          examples:
+//             - CXXBRIDGE1_PANIC
+//             - CXXBRIDGE1_RUST_STRING
+//          defining characteristics:
+//             - NAME does not begin with STRUCT or ENUM
+//
+//   (B) Guard around user-defined type.
+//          pattern:  {CXXBRIDGE} _ {STRUCT or ENUM} _ {NAMESPACE...} $ {TYPE}
+//          examples:
+//             - CXXBRIDGE1_STRUCT_org$rust$Struct
+//             - CXXBRIDGE1_ENUM_Enabled
+
+use crate::syntax::symbol::{self, Symbol};
+use crate::syntax::{ExternFn, Pair, Types};
+
+const CXXBRIDGE: &str = "cxxbridge1";
+
+macro_rules! join {
+    ($($segment:expr),+ $(,)?) => {
+        symbol::join(&[$(&$segment),+])
+    };
+}
+
+pub fn extern_fn(efn: &ExternFn, types: &Types) -> Symbol {
+    match &efn.receiver {
+        Some(receiver) => {
+            let receiver_ident = types.resolve(&receiver.ty);
+            join!(
+                efn.name.namespace,
+                CXXBRIDGE,
+                receiver_ident.name.cxx,
+                efn.name.rust,
+            )
+        }
+        None => join!(efn.name.namespace, CXXBRIDGE, efn.name.rust),
+    }
+}
+
+pub fn operator(receiver: &Pair, operator: &'static str) -> Symbol {
+    join!(
+        receiver.namespace,
+        CXXBRIDGE,
+        receiver.cxx,
+        "operator",
+        operator,
+    )
+}
+
+// The C half of a function pointer trampoline.
+pub fn c_trampoline(efn: &ExternFn, var: &Pair, types: &Types) -> Symbol {
+    join!(extern_fn(efn, types), var.rust, 0)
+}
+
+// The Rust half of a function pointer trampoline.
+pub fn r_trampoline(efn: &ExternFn, var: &Pair, types: &Types) -> Symbol {
+    join!(extern_fn(efn, types), var.rust, 1)
+}
diff --git a/src/syntax/map.rs b/src/syntax/map.rs
new file mode 100644 (file)
index 0000000..4873409
--- /dev/null
@@ -0,0 +1,179 @@
+use std::borrow::Borrow;
+use std::hash::Hash;
+use std::ops::Index;
+use std::slice;
+
+pub use self::ordered::OrderedMap;
+pub use self::unordered::UnorderedMap;
+pub use std::collections::hash_map::Entry;
+
+mod ordered {
+    use super::{Entry, Iter, UnorderedMap};
+    use std::borrow::Borrow;
+    use std::hash::Hash;
+    use std::mem;
+
+    pub struct OrderedMap<K, V> {
+        map: UnorderedMap<K, usize>,
+        vec: Vec<(K, V)>,
+    }
+
+    impl<K, V> OrderedMap<K, V> {
+        pub fn new() -> Self {
+            OrderedMap {
+                map: UnorderedMap::new(),
+                vec: Vec::new(),
+            }
+        }
+
+        pub fn iter(&self) -> Iter<K, V> {
+            Iter(self.vec.iter())
+        }
+
+        pub fn keys(&self) -> impl Iterator<Item = &K> {
+            self.vec.iter().map(|(k, _v)| k)
+        }
+    }
+
+    impl<K, V> OrderedMap<K, V>
+    where
+        K: Copy + Hash + Eq,
+    {
+        pub fn insert(&mut self, key: K, value: V) -> Option<V> {
+            match self.map.entry(key) {
+                Entry::Occupied(entry) => {
+                    let i = &mut self.vec[*entry.get()];
+                    Some(mem::replace(&mut i.1, value))
+                }
+                Entry::Vacant(entry) => {
+                    entry.insert(self.vec.len());
+                    self.vec.push((key, value));
+                    None
+                }
+            }
+        }
+
+        pub fn contains_key<Q>(&self, key: &Q) -> bool
+        where
+            K: Borrow<Q>,
+            Q: ?Sized + Hash + Eq,
+        {
+            self.map.contains_key(key)
+        }
+
+        pub fn get<Q>(&self, key: &Q) -> Option<&V>
+        where
+            K: Borrow<Q>,
+            Q: ?Sized + Hash + Eq,
+        {
+            let i = *self.map.get(key)?;
+            Some(&self.vec[i].1)
+        }
+    }
+
+    impl<'a, K, V> IntoIterator for &'a OrderedMap<K, V> {
+        type Item = (&'a K, &'a V);
+        type IntoIter = Iter<'a, K, V>;
+        fn into_iter(self) -> Self::IntoIter {
+            self.iter()
+        }
+    }
+}
+
+mod unordered {
+    use crate::syntax::set::UnorderedSet;
+    use std::borrow::Borrow;
+    use std::collections::hash_map::{Entry, HashMap};
+    use std::hash::Hash;
+
+    // Wrapper prohibits accidentally introducing iteration over the map, which
+    // could lead to nondeterministic generated code.
+    pub struct UnorderedMap<K, V>(HashMap<K, V>);
+
+    impl<K, V> UnorderedMap<K, V> {
+        pub fn new() -> Self {
+            UnorderedMap(HashMap::new())
+        }
+    }
+
+    impl<K, V> UnorderedMap<K, V>
+    where
+        K: Hash + Eq,
+    {
+        pub fn insert(&mut self, key: K, value: V) -> Option<V> {
+            self.0.insert(key, value)
+        }
+
+        pub fn contains_key<Q>(&self, key: &Q) -> bool
+        where
+            K: Borrow<Q>,
+            Q: ?Sized + Hash + Eq,
+        {
+            self.0.contains_key(key)
+        }
+
+        pub fn get<Q>(&self, key: &Q) -> Option<&V>
+        where
+            K: Borrow<Q>,
+            Q: ?Sized + Hash + Eq,
+        {
+            self.0.get(key)
+        }
+
+        pub fn entry(&mut self, key: K) -> Entry<K, V> {
+            self.0.entry(key)
+        }
+
+        pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
+        where
+            K: Borrow<Q>,
+            Q: ?Sized + Hash + Eq,
+        {
+            self.0.remove(key)
+        }
+
+        pub fn keys(&self) -> UnorderedSet<K>
+        where
+            K: Copy,
+        {
+            let mut set = UnorderedSet::new();
+            for key in self.0.keys() {
+                set.insert(*key);
+            }
+            set
+        }
+    }
+}
+
+pub struct Iter<'a, K, V>(slice::Iter<'a, (K, V)>);
+
+impl<'a, K, V> Iterator for Iter<'a, K, V> {
+    type Item = (&'a K, &'a V);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let (k, v) = self.0.next()?;
+        Some((k, v))
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.0.size_hint()
+    }
+}
+
+impl<K, V> Default for UnorderedMap<K, V> {
+    fn default() -> Self {
+        UnorderedMap::new()
+    }
+}
+
+impl<Q, K, V> Index<&Q> for UnorderedMap<K, V>
+where
+    K: Borrow<Q> + Hash + Eq,
+    Q: ?Sized + Hash + Eq,
+{
+    type Output = V;
+
+    fn index(&self, key: &Q) -> &V {
+        self.get(key).unwrap()
+    }
+}
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
new file mode 100644 (file)
index 0000000..1d98634
--- /dev/null
@@ -0,0 +1,288 @@
+// Functionality that is shared between the cxxbridge macro and the cmd.
+
+pub mod atom;
+pub mod attrs;
+pub mod check;
+pub mod derive;
+mod discriminant;
+mod doc;
+pub mod error;
+pub mod file;
+pub mod ident;
+mod impls;
+mod improper;
+pub mod instantiate;
+pub mod mangle;
+pub mod map;
+mod names;
+pub mod namespace;
+mod parse;
+mod pod;
+pub mod qualified;
+pub mod report;
+pub mod resolve;
+pub mod set;
+pub mod symbol;
+mod tokens;
+mod toposort;
+pub mod trivial;
+pub mod types;
+mod visit;
+
+use self::attrs::OtherAttrs;
+use self::namespace::Namespace;
+use self::parse::kw;
+use self::symbol::Symbol;
+use proc_macro2::{Ident, Span};
+use syn::punctuated::Punctuated;
+use syn::token::{Brace, Bracket, Paren};
+use syn::{Attribute, Expr, Generics, Lifetime, LitInt, Path, Token, Type as RustType};
+
+pub use self::atom::Atom;
+pub use self::derive::{Derive, Trait};
+pub use self::discriminant::Discriminant;
+pub use self::doc::Doc;
+pub use self::names::ForeignName;
+pub use self::parse::parse_items;
+pub use self::types::Types;
+
+pub enum Api {
+    Include(Include),
+    Struct(Struct),
+    Enum(Enum),
+    CxxType(ExternType),
+    CxxFunction(ExternFn),
+    RustType(ExternType),
+    RustFunction(ExternFn),
+    TypeAlias(TypeAlias),
+    Impl(Impl),
+}
+
+pub struct Include {
+    pub path: String,
+    pub kind: IncludeKind,
+    pub begin_span: Span,
+    pub end_span: Span,
+}
+
+/// Whether to emit `#include "path"` or `#include <path>`.
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum IncludeKind {
+    /// `#include "quoted/path/to"`
+    Quoted,
+    /// `#include <bracketed/path/to>`
+    Bracketed,
+}
+
+pub struct ExternType {
+    pub lang: Lang,
+    pub doc: Doc,
+    pub derives: Vec<Derive>,
+    pub attrs: OtherAttrs,
+    pub visibility: Token![pub],
+    pub type_token: Token![type],
+    pub name: Pair,
+    pub generics: Lifetimes,
+    pub colon_token: Option<Token![:]>,
+    pub bounds: Vec<Derive>,
+    pub semi_token: Token![;],
+    pub trusted: bool,
+}
+
+pub struct Struct {
+    pub doc: Doc,
+    pub derives: Vec<Derive>,
+    pub attrs: OtherAttrs,
+    pub visibility: Token![pub],
+    pub struct_token: Token![struct],
+    pub name: Pair,
+    pub generics: Lifetimes,
+    pub brace_token: Brace,
+    pub fields: Vec<Var>,
+}
+
+pub struct Enum {
+    pub doc: Doc,
+    pub derives: Vec<Derive>,
+    pub attrs: OtherAttrs,
+    pub visibility: Token![pub],
+    pub enum_token: Token![enum],
+    pub name: Pair,
+    pub generics: Lifetimes,
+    pub brace_token: Brace,
+    pub variants: Vec<Variant>,
+    pub variants_from_header: bool,
+    pub variants_from_header_attr: Option<Attribute>,
+    pub repr: EnumRepr,
+    pub explicit_repr: bool,
+}
+
+pub enum EnumRepr {
+    Native { atom: Atom, repr_type: Type },
+    Foreign { rust_type: Path },
+}
+
+pub struct ExternFn {
+    pub lang: Lang,
+    pub doc: Doc,
+    pub attrs: OtherAttrs,
+    pub visibility: Token![pub],
+    pub name: Pair,
+    pub sig: Signature,
+    pub semi_token: Token![;],
+    pub trusted: bool,
+}
+
+pub struct TypeAlias {
+    pub doc: Doc,
+    pub derives: Vec<Derive>,
+    pub attrs: OtherAttrs,
+    pub visibility: Token![pub],
+    pub type_token: Token![type],
+    pub name: Pair,
+    pub generics: Lifetimes,
+    pub eq_token: Token![=],
+    pub ty: RustType,
+    pub semi_token: Token![;],
+}
+
+pub struct Impl {
+    pub impl_token: Token![impl],
+    pub impl_generics: Lifetimes,
+    pub negative: bool,
+    pub ty: Type,
+    pub ty_generics: Lifetimes,
+    pub brace_token: Brace,
+    pub negative_token: Option<Token![!]>,
+}
+
+#[derive(Clone, Default)]
+pub struct Lifetimes {
+    pub lt_token: Option<Token![<]>,
+    pub lifetimes: Punctuated<Lifetime, Token![,]>,
+    pub gt_token: Option<Token![>]>,
+}
+
+pub struct Signature {
+    pub unsafety: Option<Token![unsafe]>,
+    pub fn_token: Token![fn],
+    pub generics: Generics,
+    pub receiver: Option<Receiver>,
+    pub args: Punctuated<Var, Token![,]>,
+    pub ret: Option<Type>,
+    pub throws: bool,
+    pub paren_token: Paren,
+    pub throws_tokens: Option<(kw::Result, Token![<], Token![>])>,
+}
+
+pub struct Var {
+    pub doc: Doc,
+    pub attrs: OtherAttrs,
+    pub visibility: Token![pub],
+    pub name: Pair,
+    pub colon_token: Token![:],
+    pub ty: Type,
+}
+
+pub struct Receiver {
+    pub pinned: bool,
+    pub ampersand: Token![&],
+    pub lifetime: Option<Lifetime>,
+    pub mutable: bool,
+    pub var: Token![self],
+    pub ty: NamedType,
+    pub colon_token: Token![:],
+    pub shorthand: bool,
+    pub pin_tokens: Option<(kw::Pin, Token![<], Token![>])>,
+    pub mutability: Option<Token![mut]>,
+}
+
+pub struct Variant {
+    pub doc: Doc,
+    pub attrs: OtherAttrs,
+    pub name: Pair,
+    pub discriminant: Discriminant,
+    pub expr: Option<Expr>,
+}
+
+pub enum Type {
+    Ident(NamedType),
+    RustBox(Box<Ty1>),
+    RustVec(Box<Ty1>),
+    UniquePtr(Box<Ty1>),
+    SharedPtr(Box<Ty1>),
+    WeakPtr(Box<Ty1>),
+    Ref(Box<Ref>),
+    Ptr(Box<Ptr>),
+    Str(Box<Ref>),
+    CxxVector(Box<Ty1>),
+    Fn(Box<Signature>),
+    Void(Span),
+    SliceRef(Box<SliceRef>),
+    Array(Box<Array>),
+}
+
+pub struct Ty1 {
+    pub name: Ident,
+    pub langle: Token![<],
+    pub inner: Type,
+    pub rangle: Token![>],
+}
+
+pub struct Ref {
+    pub pinned: bool,
+    pub ampersand: Token![&],
+    pub lifetime: Option<Lifetime>,
+    pub mutable: bool,
+    pub inner: Type,
+    pub pin_tokens: Option<(kw::Pin, Token![<], Token![>])>,
+    pub mutability: Option<Token![mut]>,
+}
+
+pub struct Ptr {
+    pub star: Token![*],
+    pub mutable: bool,
+    pub inner: Type,
+    pub mutability: Option<Token![mut]>,
+    pub constness: Option<Token![const]>,
+}
+
+pub struct SliceRef {
+    pub ampersand: Token![&],
+    pub lifetime: Option<Lifetime>,
+    pub mutable: bool,
+    pub bracket: Bracket,
+    pub inner: Type,
+    pub mutability: Option<Token![mut]>,
+}
+
+pub struct Array {
+    pub bracket: Bracket,
+    pub inner: Type,
+    pub semi_token: Token![;],
+    pub len: usize,
+    pub len_token: LitInt,
+}
+
+#[derive(Copy, Clone, PartialEq)]
+pub enum Lang {
+    Cxx,
+    Rust,
+}
+
+// An association of a defined Rust name with a fully resolved, namespace
+// qualified C++ name.
+#[derive(Clone)]
+pub struct Pair {
+    pub namespace: Namespace,
+    pub cxx: ForeignName,
+    pub rust: Ident,
+}
+
+// Wrapper for a type which needs to be resolved before it can be printed in
+// C++.
+#[derive(PartialEq, Eq, Hash)]
+pub struct NamedType {
+    pub rust: Ident,
+    pub generics: Lifetimes,
+}
diff --git a/src/syntax/names.rs b/src/syntax/names.rs
new file mode 100644 (file)
index 0000000..7a125ae
--- /dev/null
@@ -0,0 +1,75 @@
+use crate::syntax::symbol::Segment;
+use crate::syntax::{Lifetimes, NamedType, Pair, Symbol};
+use proc_macro2::{Ident, Span};
+use std::fmt::{self, Display};
+use std::iter;
+use syn::parse::{Error, Result};
+use syn::punctuated::Punctuated;
+
+#[derive(Clone)]
+pub struct ForeignName {
+    text: String,
+}
+
+impl Pair {
+    pub fn to_symbol(&self) -> Symbol {
+        let segments = self
+            .namespace
+            .iter()
+            .map(|ident| ident as &dyn Segment)
+            .chain(iter::once(&self.cxx as &dyn Segment));
+        Symbol::from_idents(segments)
+    }
+
+    pub fn to_fully_qualified(&self) -> String {
+        let mut fully_qualified = String::new();
+        for segment in &self.namespace {
+            fully_qualified += "::";
+            fully_qualified += &segment.to_string();
+        }
+        fully_qualified += "::";
+        fully_qualified += &self.cxx.to_string();
+        fully_qualified
+    }
+}
+
+impl NamedType {
+    pub fn new(rust: Ident) -> Self {
+        let generics = Lifetimes {
+            lt_token: None,
+            lifetimes: Punctuated::new(),
+            gt_token: None,
+        };
+        NamedType { rust, generics }
+    }
+
+    pub fn span(&self) -> Span {
+        self.rust.span()
+    }
+}
+
+impl ForeignName {
+    pub fn parse(text: &str, span: Span) -> Result<Self> {
+        // TODO: support C++ names containing whitespace (`unsigned int`) or
+        // non-alphanumeric characters (`operator++`).
+        match syn::parse_str::<Ident>(text) {
+            Ok(ident) => {
+                let text = ident.to_string();
+                Ok(ForeignName { text })
+            }
+            Err(err) => Err(Error::new(span, err)),
+        }
+    }
+}
+
+impl Display for ForeignName {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str(&self.text)
+    }
+}
+
+impl PartialEq<str> for ForeignName {
+    fn eq(&self, rhs: &str) -> bool {
+        self.text == rhs
+    }
+}
diff --git a/src/syntax/namespace.rs b/src/syntax/namespace.rs
new file mode 100644 (file)
index 0000000..07185e1
--- /dev/null
@@ -0,0 +1,85 @@
+use crate::syntax::qualified::QualifiedName;
+use quote::IdentFragment;
+use std::fmt::{self, Display};
+use std::iter::FromIterator;
+use std::slice::Iter;
+use syn::parse::{Parse, ParseStream, Result};
+use syn::{Ident, Token};
+
+mod kw {
+    syn::custom_keyword!(namespace);
+}
+
+#[derive(Clone, Default)]
+pub struct Namespace {
+    segments: Vec<Ident>,
+}
+
+impl Namespace {
+    pub const ROOT: Self = Namespace {
+        segments: Vec::new(),
+    };
+
+    pub fn iter(&self) -> Iter<Ident> {
+        self.segments.iter()
+    }
+
+    pub fn parse_bridge_attr_namespace(input: ParseStream) -> Result<Namespace> {
+        if input.is_empty() {
+            return Ok(Namespace::ROOT);
+        }
+
+        input.parse::<kw::namespace>()?;
+        input.parse::<Token![=]>()?;
+        let namespace = input.parse::<Namespace>()?;
+        input.parse::<Option<Token![,]>>()?;
+        Ok(namespace)
+    }
+}
+
+impl Default for &Namespace {
+    fn default() -> Self {
+        const ROOT: &Namespace = &Namespace::ROOT;
+        ROOT
+    }
+}
+
+impl Parse for Namespace {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let segments = QualifiedName::parse_quoted_or_unquoted(input)?.segments;
+        Ok(Namespace { segments })
+    }
+}
+
+impl Display for Namespace {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        for segment in self {
+            write!(f, "{}$", segment)?;
+        }
+        Ok(())
+    }
+}
+
+impl IdentFragment for Namespace {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        Display::fmt(self, f)
+    }
+}
+
+impl<'a> IntoIterator for &'a Namespace {
+    type Item = &'a Ident;
+    type IntoIter = Iter<'a, Ident>;
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+impl<'a> FromIterator<&'a Ident> for Namespace {
+    fn from_iter<I>(idents: I) -> Self
+    where
+        I: IntoIterator<Item = &'a Ident>,
+    {
+        let segments = idents.into_iter().cloned().collect();
+        Namespace { segments }
+    }
+}
diff --git a/src/syntax/parse.rs b/src/syntax/parse.rs
new file mode 100644 (file)
index 0000000..85a414c
--- /dev/null
@@ -0,0 +1,1391 @@
+use crate::syntax::attrs::OtherAttrs;
+use crate::syntax::discriminant::DiscriminantSet;
+use crate::syntax::file::{Item, ItemForeignMod};
+use crate::syntax::report::Errors;
+use crate::syntax::Atom::*;
+use crate::syntax::{
+    attrs, error, Api, Array, Derive, Doc, Enum, EnumRepr, ExternFn, ExternType, ForeignName, Impl,
+    Include, IncludeKind, Lang, Lifetimes, NamedType, Namespace, Pair, Ptr, Receiver, Ref,
+    Signature, SliceRef, Struct, Ty1, Type, TypeAlias, Var, Variant,
+};
+use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree};
+use quote::{format_ident, quote, quote_spanned};
+use std::mem;
+use syn::parse::{ParseStream, Parser};
+use syn::punctuated::Punctuated;
+use syn::{
+    Abi, Attribute, Error, Expr, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType,
+    GenericArgument, GenericParam, Generics, Ident, ItemEnum, ItemImpl, ItemStruct, Lit, LitStr,
+    Pat, PathArguments, Result, ReturnType, Signature as RustSignature, Token, TraitBound,
+    TraitBoundModifier, Type as RustType, TypeArray, TypeBareFn, TypeParamBound, TypePath, TypePtr,
+    TypeReference, Variant as RustVariant, Visibility,
+};
+
+pub mod kw {
+    syn::custom_keyword!(Pin);
+    syn::custom_keyword!(Result);
+}
+
+pub fn parse_items(
+    cx: &mut Errors,
+    items: Vec<Item>,
+    trusted: bool,
+    namespace: &Namespace,
+) -> Vec<Api> {
+    let mut apis = Vec::new();
+    for item in items {
+        match item {
+            Item::Struct(item) => match parse_struct(cx, item, namespace) {
+                Ok(strct) => apis.push(strct),
+                Err(err) => cx.push(err),
+            },
+            Item::Enum(item) => apis.push(parse_enum(cx, item, namespace)),
+            Item::ForeignMod(foreign_mod) => {
+                parse_foreign_mod(cx, foreign_mod, &mut apis, trusted, namespace)
+            }
+            Item::Impl(item) => match parse_impl(item) {
+                Ok(imp) => apis.push(imp),
+                Err(err) => cx.push(err),
+            },
+            Item::Use(item) => cx.error(item, error::USE_NOT_ALLOWED),
+            Item::Other(item) => cx.error(item, "unsupported item"),
+        }
+    }
+    apis
+}
+
+fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) -> Result<Api> {
+    let mut doc = Doc::new();
+    let mut derives = Vec::new();
+    let mut namespace = namespace.clone();
+    let mut cxx_name = None;
+    let mut rust_name = None;
+    let attrs = attrs::parse(
+        cx,
+        mem::take(&mut item.attrs),
+        attrs::Parser {
+            doc: Some(&mut doc),
+            derives: Some(&mut derives),
+            namespace: Some(&mut namespace),
+            cxx_name: Some(&mut cxx_name),
+            rust_name: Some(&mut rust_name),
+            ..Default::default()
+        },
+    );
+
+    let named_fields = match item.fields {
+        Fields::Named(fields) => fields,
+        Fields::Unit => return Err(Error::new_spanned(item, "unit structs are not supported")),
+        Fields::Unnamed(_) => {
+            return Err(Error::new_spanned(item, "tuple structs are not supported"));
+        }
+    };
+
+    let mut lifetimes = Punctuated::new();
+    let mut has_unsupported_generic_param = false;
+    for pair in item.generics.params.into_pairs() {
+        let (param, punct) = pair.into_tuple();
+        match param {
+            GenericParam::Lifetime(param) => {
+                if !param.bounds.is_empty() && !has_unsupported_generic_param {
+                    let msg = "lifetime parameter with bounds is not supported yet";
+                    cx.error(&param, msg);
+                    has_unsupported_generic_param = true;
+                }
+                lifetimes.push_value(param.lifetime);
+                if let Some(punct) = punct {
+                    lifetimes.push_punct(punct);
+                }
+            }
+            GenericParam::Type(param) => {
+                if !has_unsupported_generic_param {
+                    let msg = "struct with generic type parameter is not supported yet";
+                    cx.error(&param, msg);
+                    has_unsupported_generic_param = true;
+                }
+            }
+            GenericParam::Const(param) => {
+                if !has_unsupported_generic_param {
+                    let msg = "struct with const generic parameter is not supported yet";
+                    cx.error(&param, msg);
+                    has_unsupported_generic_param = true;
+                }
+            }
+        }
+    }
+
+    if let Some(where_clause) = &item.generics.where_clause {
+        cx.error(
+            where_clause,
+            "struct with where-clause is not supported yet",
+        );
+    }
+
+    let mut fields = Vec::new();
+    for field in named_fields.named {
+        let ident = field.ident.unwrap();
+        let mut doc = Doc::new();
+        let mut cxx_name = None;
+        let mut rust_name = None;
+        let attrs = attrs::parse(
+            cx,
+            field.attrs,
+            attrs::Parser {
+                doc: Some(&mut doc),
+                cxx_name: Some(&mut cxx_name),
+                rust_name: Some(&mut rust_name),
+                ..Default::default()
+            },
+        );
+        let ty = match parse_type(&field.ty) {
+            Ok(ty) => ty,
+            Err(err) => {
+                cx.push(err);
+                continue;
+            }
+        };
+        let visibility = visibility_pub(&field.vis, ident.span());
+        let name = pair(Namespace::default(), &ident, cxx_name, rust_name);
+        let colon_token = field.colon_token.unwrap();
+        fields.push(Var {
+            doc,
+            attrs,
+            visibility,
+            name,
+            colon_token,
+            ty,
+        });
+    }
+
+    let struct_token = item.struct_token;
+    let visibility = visibility_pub(&item.vis, struct_token.span);
+    let name = pair(namespace, &item.ident, cxx_name, rust_name);
+    let generics = Lifetimes {
+        lt_token: item.generics.lt_token,
+        lifetimes,
+        gt_token: item.generics.gt_token,
+    };
+    let brace_token = named_fields.brace_token;
+
+    Ok(Api::Struct(Struct {
+        doc,
+        derives,
+        attrs,
+        visibility,
+        struct_token,
+        name,
+        generics,
+        brace_token,
+        fields,
+    }))
+}
+
+fn parse_enum(cx: &mut Errors, item: ItemEnum, namespace: &Namespace) -> Api {
+    let mut doc = Doc::new();
+    let mut derives = Vec::new();
+    let mut repr = None;
+    let mut namespace = namespace.clone();
+    let mut cxx_name = None;
+    let mut rust_name = None;
+    let mut variants_from_header = None;
+    let attrs = attrs::parse(
+        cx,
+        item.attrs,
+        attrs::Parser {
+            doc: Some(&mut doc),
+            derives: Some(&mut derives),
+            repr: Some(&mut repr),
+            namespace: Some(&mut namespace),
+            cxx_name: Some(&mut cxx_name),
+            rust_name: Some(&mut rust_name),
+            variants_from_header: Some(&mut variants_from_header),
+            ..Default::default()
+        },
+    );
+
+    if !item.generics.params.is_empty() {
+        let vis = &item.vis;
+        let enum_token = item.enum_token;
+        let ident = &item.ident;
+        let generics = &item.generics;
+        let span = quote!(#vis #enum_token #ident #generics);
+        cx.error(span, "enum with generic parameters is not supported");
+    } else if let Some(where_clause) = &item.generics.where_clause {
+        cx.error(where_clause, "enum with where-clause is not supported");
+    }
+
+    let mut variants = Vec::new();
+    let mut discriminants = DiscriminantSet::new(repr);
+    for variant in item.variants {
+        match parse_variant(cx, variant, &mut discriminants) {
+            Ok(variant) => variants.push(variant),
+            Err(err) => cx.push(err),
+        }
+    }
+
+    let enum_token = item.enum_token;
+    let visibility = visibility_pub(&item.vis, enum_token.span);
+    let brace_token = item.brace_token;
+
+    let explicit_repr = repr.is_some();
+    let mut repr = U8;
+    match discriminants.inferred_repr() {
+        Ok(inferred) => repr = inferred,
+        Err(err) => {
+            let span = quote_spanned!(brace_token.span=> #enum_token {});
+            cx.error(span, err);
+            variants.clear();
+        }
+    }
+
+    let name = pair(namespace, &item.ident, cxx_name, rust_name);
+    let repr_ident = Ident::new(repr.as_ref(), Span::call_site());
+    let repr_type = Type::Ident(NamedType::new(repr_ident));
+    let repr = EnumRepr::Native {
+        atom: repr,
+        repr_type,
+    };
+    let generics = Lifetimes {
+        lt_token: None,
+        lifetimes: Punctuated::new(),
+        gt_token: None,
+    };
+    let variants_from_header_attr = variants_from_header;
+    let variants_from_header = variants_from_header_attr.is_some();
+
+    Api::Enum(Enum {
+        doc,
+        derives,
+        attrs,
+        visibility,
+        enum_token,
+        name,
+        generics,
+        brace_token,
+        variants,
+        variants_from_header,
+        variants_from_header_attr,
+        repr,
+        explicit_repr,
+    })
+}
+
+fn parse_variant(
+    cx: &mut Errors,
+    mut variant: RustVariant,
+    discriminants: &mut DiscriminantSet,
+) -> Result<Variant> {
+    let mut doc = Doc::new();
+    let mut cxx_name = None;
+    let mut rust_name = None;
+    let attrs = attrs::parse(
+        cx,
+        mem::take(&mut variant.attrs),
+        attrs::Parser {
+            doc: Some(&mut doc),
+            cxx_name: Some(&mut cxx_name),
+            rust_name: Some(&mut rust_name),
+            ..Default::default()
+        },
+    );
+
+    match variant.fields {
+        Fields::Unit => {}
+        _ => {
+            let msg = "enums with data are not supported yet";
+            return Err(Error::new_spanned(variant, msg));
+        }
+    }
+
+    let expr = variant.discriminant.as_ref().map(|(_, expr)| expr);
+    let try_discriminant = match &expr {
+        Some(lit) => discriminants.insert(lit),
+        None => discriminants.insert_next(),
+    };
+    let discriminant = match try_discriminant {
+        Ok(discriminant) => discriminant,
+        Err(err) => return Err(Error::new_spanned(variant, err)),
+    };
+
+    let name = pair(Namespace::ROOT, &variant.ident, cxx_name, rust_name);
+    let expr = variant.discriminant.map(|(_, expr)| expr);
+
+    Ok(Variant {
+        doc,
+        attrs,
+        name,
+        discriminant,
+        expr,
+    })
+}
+
+fn parse_foreign_mod(
+    cx: &mut Errors,
+    foreign_mod: ItemForeignMod,
+    out: &mut Vec<Api>,
+    trusted: bool,
+    namespace: &Namespace,
+) {
+    let lang = match parse_lang(&foreign_mod.abi) {
+        Ok(lang) => lang,
+        Err(err) => return cx.push(err),
+    };
+
+    match lang {
+        Lang::Rust => {
+            if foreign_mod.unsafety.is_some() {
+                let unsafety = foreign_mod.unsafety;
+                let abi = &foreign_mod.abi;
+                let span = quote!(#unsafety #abi);
+                cx.error(span, "extern \"Rust\" block does not need to be unsafe");
+            }
+        }
+        Lang::Cxx => {}
+    }
+
+    let trusted = trusted || foreign_mod.unsafety.is_some();
+
+    let mut namespace = namespace.clone();
+    attrs::parse(
+        cx,
+        foreign_mod.attrs,
+        attrs::Parser {
+            namespace: Some(&mut namespace),
+            ..Default::default()
+        },
+    );
+
+    let mut items = Vec::new();
+    for foreign in foreign_mod.items {
+        match foreign {
+            ForeignItem::Type(foreign) => {
+                let ety = parse_extern_type(cx, foreign, lang, trusted, &namespace);
+                items.push(ety);
+            }
+            ForeignItem::Fn(foreign) => {
+                match parse_extern_fn(cx, foreign, lang, trusted, &namespace) {
+                    Ok(efn) => items.push(efn),
+                    Err(err) => cx.push(err),
+                }
+            }
+            ForeignItem::Macro(foreign) if foreign.mac.path.is_ident("include") => {
+                match foreign.mac.parse_body_with(parse_include) {
+                    Ok(include) => items.push(Api::Include(include)),
+                    Err(err) => cx.push(err),
+                }
+            }
+            ForeignItem::Verbatim(tokens) => {
+                match parse_extern_verbatim(cx, tokens, lang, trusted, &namespace) {
+                    Ok(api) => items.push(api),
+                    Err(err) => cx.push(err),
+                }
+            }
+            _ => cx.error(foreign, "unsupported foreign item"),
+        }
+    }
+
+    if !trusted
+        && items.iter().any(|api| match api {
+            Api::CxxFunction(efn) => efn.unsafety.is_none(),
+            _ => false,
+        })
+    {
+        cx.error(
+            foreign_mod.abi,
+            "block must be declared `unsafe extern \"C++\"` if it contains any safe-to-call C++ functions",
+        );
+    }
+
+    let mut types = items.iter().filter_map(|item| match item {
+        Api::CxxType(ety) | Api::RustType(ety) => Some(&ety.name),
+        Api::TypeAlias(alias) => Some(&alias.name),
+        _ => None,
+    });
+    if let (Some(single_type), None) = (types.next(), types.next()) {
+        let single_type = single_type.clone();
+        for item in &mut items {
+            if let Api::CxxFunction(efn) | Api::RustFunction(efn) = item {
+                if let Some(receiver) = &mut efn.receiver {
+                    if receiver.ty.rust == "Self" {
+                        receiver.ty.rust = single_type.rust.clone();
+                    }
+                }
+            }
+        }
+    }
+
+    out.extend(items);
+}
+
+fn parse_lang(abi: &Abi) -> Result<Lang> {
+    let name = match &abi.name {
+        Some(name) => name,
+        None => {
+            return Err(Error::new_spanned(
+                abi,
+                "ABI name is required, extern \"C++\" or extern \"Rust\"",
+            ));
+        }
+    };
+
+    match name.value().as_str() {
+        "C++" => Ok(Lang::Cxx),
+        "Rust" => Ok(Lang::Rust),
+        _ => Err(Error::new_spanned(
+            abi,
+            "unrecognized ABI, requires either \"C++\" or \"Rust\"",
+        )),
+    }
+}
+
+fn parse_extern_type(
+    cx: &mut Errors,
+    foreign_type: ForeignItemType,
+    lang: Lang,
+    trusted: bool,
+    namespace: &Namespace,
+) -> Api {
+    let mut doc = Doc::new();
+    let mut derives = Vec::new();
+    let mut namespace = namespace.clone();
+    let mut cxx_name = None;
+    let mut rust_name = None;
+    let attrs = attrs::parse(
+        cx,
+        foreign_type.attrs,
+        attrs::Parser {
+            doc: Some(&mut doc),
+            derives: Some(&mut derives),
+            namespace: Some(&mut namespace),
+            cxx_name: Some(&mut cxx_name),
+            rust_name: Some(&mut rust_name),
+            ..Default::default()
+        },
+    );
+
+    let type_token = foreign_type.type_token;
+    let visibility = visibility_pub(&foreign_type.vis, type_token.span);
+    let name = pair(namespace, &foreign_type.ident, cxx_name, rust_name);
+    let generics = Lifetimes {
+        lt_token: None,
+        lifetimes: Punctuated::new(),
+        gt_token: None,
+    };
+    let colon_token = None;
+    let bounds = Vec::new();
+    let semi_token = foreign_type.semi_token;
+
+    (match lang {
+        Lang::Cxx => Api::CxxType,
+        Lang::Rust => Api::RustType,
+    })(ExternType {
+        lang,
+        doc,
+        derives,
+        attrs,
+        visibility,
+        type_token,
+        name,
+        generics,
+        colon_token,
+        bounds,
+        semi_token,
+        trusted,
+    })
+}
+
+fn parse_extern_fn(
+    cx: &mut Errors,
+    mut foreign_fn: ForeignItemFn,
+    lang: Lang,
+    trusted: bool,
+    namespace: &Namespace,
+) -> Result<Api> {
+    let mut doc = Doc::new();
+    let mut namespace = namespace.clone();
+    let mut cxx_name = None;
+    let mut rust_name = None;
+    let attrs = attrs::parse(
+        cx,
+        mem::take(&mut foreign_fn.attrs),
+        attrs::Parser {
+            doc: Some(&mut doc),
+            namespace: Some(&mut namespace),
+            cxx_name: Some(&mut cxx_name),
+            rust_name: Some(&mut rust_name),
+            ..Default::default()
+        },
+    );
+
+    let generics = &foreign_fn.sig.generics;
+    if generics.where_clause.is_some()
+        || generics.params.iter().any(|param| match param {
+            GenericParam::Lifetime(lifetime) => !lifetime.bounds.is_empty(),
+            GenericParam::Type(_) | GenericParam::Const(_) => true,
+        })
+    {
+        return Err(Error::new_spanned(
+            foreign_fn,
+            "extern function with generic parameters is not supported yet",
+        ));
+    }
+
+    if let Some(variadic) = &foreign_fn.sig.variadic {
+        return Err(Error::new_spanned(
+            variadic,
+            "variadic function is not supported yet",
+        ));
+    }
+
+    if foreign_fn.sig.asyncness.is_some() {
+        return Err(Error::new_spanned(
+            foreign_fn,
+            "async function is not directly supported yet, but see https://cxx.rs/async.html for a working approach, and https://github.com/pcwalton/cxx-async for some helpers; eventually what you wrote will work but it isn't integrated into the cxx::bridge macro yet",
+        ));
+    }
+
+    if foreign_fn.sig.constness.is_some() {
+        return Err(Error::new_spanned(
+            foreign_fn,
+            "const extern function is not supported",
+        ));
+    }
+
+    if let Some(abi) = &foreign_fn.sig.abi {
+        return Err(Error::new_spanned(
+            abi,
+            "explicit ABI on extern function is not supported",
+        ));
+    }
+
+    let mut receiver = None;
+    let mut args = Punctuated::new();
+    for arg in foreign_fn.sig.inputs.pairs() {
+        let (arg, comma) = arg.into_tuple();
+        match arg {
+            FnArg::Receiver(arg) => {
+                if let Some((ampersand, lifetime)) = &arg.reference {
+                    receiver = Some(Receiver {
+                        pinned: false,
+                        ampersand: *ampersand,
+                        lifetime: lifetime.clone(),
+                        mutable: arg.mutability.is_some(),
+                        var: arg.self_token,
+                        colon_token: Token![:](arg.self_token.span),
+                        ty: NamedType::new(Ident::new("Self", arg.self_token.span)),
+                        shorthand: true,
+                        pin_tokens: None,
+                        mutability: arg.mutability,
+                    });
+                    continue;
+                }
+                return Err(Error::new_spanned(arg, "unsupported signature"));
+            }
+            FnArg::Typed(arg) => {
+                let ident = match arg.pat.as_ref() {
+                    Pat::Ident(pat) => pat.ident.clone(),
+                    Pat::Wild(pat) => {
+                        Ident::new(&format!("arg{}", args.len()), pat.underscore_token.span)
+                    }
+                    _ => return Err(Error::new_spanned(arg, "unsupported signature")),
+                };
+                let ty = parse_type(&arg.ty)?;
+                if ident != "self" {
+                    let doc = Doc::new();
+                    let attrs = OtherAttrs::none();
+                    let visibility = Token![pub](ident.span());
+                    let name = pair(Namespace::default(), &ident, None, None);
+                    let colon_token = arg.colon_token;
+                    args.push_value(Var {
+                        doc,
+                        attrs,
+                        visibility,
+                        name,
+                        colon_token,
+                        ty,
+                    });
+                    if let Some(comma) = comma {
+                        args.push_punct(*comma);
+                    }
+                    continue;
+                }
+                if let Type::Ref(reference) = ty {
+                    if let Type::Ident(ident) = reference.inner {
+                        receiver = Some(Receiver {
+                            pinned: reference.pinned,
+                            ampersand: reference.ampersand,
+                            lifetime: reference.lifetime,
+                            mutable: reference.mutable,
+                            var: Token![self](ident.rust.span()),
+                            colon_token: arg.colon_token,
+                            ty: ident,
+                            shorthand: false,
+                            pin_tokens: reference.pin_tokens,
+                            mutability: reference.mutability,
+                        });
+                        continue;
+                    }
+                }
+                return Err(Error::new_spanned(arg, "unsupported method receiver"));
+            }
+        }
+    }
+
+    let mut throws_tokens = None;
+    let ret = parse_return_type(&foreign_fn.sig.output, &mut throws_tokens)?;
+    let throws = throws_tokens.is_some();
+    let unsafety = foreign_fn.sig.unsafety;
+    let fn_token = foreign_fn.sig.fn_token;
+    let inherited_span = unsafety.map_or(fn_token.span, |unsafety| unsafety.span);
+    let visibility = visibility_pub(&foreign_fn.vis, inherited_span);
+    let name = pair(namespace, &foreign_fn.sig.ident, cxx_name, rust_name);
+    let generics = generics.clone();
+    let paren_token = foreign_fn.sig.paren_token;
+    let semi_token = foreign_fn.semi_token;
+
+    Ok(match lang {
+        Lang::Cxx => Api::CxxFunction,
+        Lang::Rust => Api::RustFunction,
+    }(ExternFn {
+        lang,
+        doc,
+        attrs,
+        visibility,
+        name,
+        sig: Signature {
+            unsafety,
+            fn_token,
+            generics,
+            receiver,
+            args,
+            ret,
+            throws,
+            paren_token,
+            throws_tokens,
+        },
+        semi_token,
+        trusted,
+    }))
+}
+
+fn parse_extern_verbatim(
+    cx: &mut Errors,
+    tokens: TokenStream,
+    lang: Lang,
+    trusted: bool,
+    namespace: &Namespace,
+) -> Result<Api> {
+    |input: ParseStream| -> Result<Api> {
+        let attrs = input.call(Attribute::parse_outer)?;
+        let visibility: Visibility = input.parse()?;
+        if input.peek(Token![type]) {
+            parse_extern_verbatim_type(cx, attrs, visibility, input, lang, trusted, namespace)
+        } else if input.peek(Token![fn]) {
+            parse_extern_verbatim_fn(input)
+        } else {
+            let span = input.cursor().token_stream();
+            Err(Error::new_spanned(
+                span,
+                "unsupported foreign item, expected `type` or `fn`",
+            ))
+        }
+    }
+    .parse2(tokens)
+}
+
+fn parse_extern_verbatim_type(
+    cx: &mut Errors,
+    attrs: Vec<Attribute>,
+    visibility: Visibility,
+    input: ParseStream,
+    lang: Lang,
+    trusted: bool,
+    namespace: &Namespace,
+) -> Result<Api> {
+    let type_token: Token![type] = input.parse()?;
+    let ident: Ident = input.parse()?;
+    let generics: Generics = input.parse()?;
+    let mut lifetimes = Punctuated::new();
+    let mut has_unsupported_generic_param = false;
+    for pair in generics.params.into_pairs() {
+        let (param, punct) = pair.into_tuple();
+        match param {
+            GenericParam::Lifetime(param) => {
+                if !param.bounds.is_empty() && !has_unsupported_generic_param {
+                    let msg = "lifetime parameter with bounds is not supported yet";
+                    cx.error(&param, msg);
+                    has_unsupported_generic_param = true;
+                }
+                lifetimes.push_value(param.lifetime);
+                if let Some(punct) = punct {
+                    lifetimes.push_punct(punct);
+                }
+            }
+            GenericParam::Type(param) => {
+                if !has_unsupported_generic_param {
+                    let msg = "extern type with generic type parameter is not supported yet";
+                    cx.error(&param, msg);
+                    has_unsupported_generic_param = true;
+                }
+            }
+            GenericParam::Const(param) => {
+                if !has_unsupported_generic_param {
+                    let msg = "extern type with const generic parameter is not supported yet";
+                    cx.error(&param, msg);
+                    has_unsupported_generic_param = true;
+                }
+            }
+        }
+    }
+    let lifetimes = Lifetimes {
+        lt_token: generics.lt_token,
+        lifetimes,
+        gt_token: generics.gt_token,
+    };
+    let lookahead = input.lookahead1();
+    if lookahead.peek(Token![=]) {
+        // type Alias = crate::path::to::Type;
+        parse_type_alias(
+            cx, attrs, visibility, type_token, ident, lifetimes, input, lang, namespace,
+        )
+    } else if lookahead.peek(Token![:]) || lookahead.peek(Token![;]) {
+        // type Opaque: Bound2 + Bound2;
+        parse_extern_type_bounded(
+            cx, attrs, visibility, type_token, ident, lifetimes, input, lang, trusted, namespace,
+        )
+    } else {
+        Err(lookahead.error())
+    }
+}
+
+fn parse_extern_verbatim_fn(input: ParseStream) -> Result<Api> {
+    input.parse::<RustSignature>()?;
+    input.parse::<Token![;]>()?;
+    unreachable!()
+}
+
+fn parse_type_alias(
+    cx: &mut Errors,
+    attrs: Vec<Attribute>,
+    visibility: Visibility,
+    type_token: Token![type],
+    ident: Ident,
+    generics: Lifetimes,
+    input: ParseStream,
+    lang: Lang,
+    namespace: &Namespace,
+) -> Result<Api> {
+    let eq_token: Token![=] = input.parse()?;
+    let ty: RustType = input.parse()?;
+    let semi_token: Token![;] = input.parse()?;
+
+    let mut doc = Doc::new();
+    let mut derives = Vec::new();
+    let mut namespace = namespace.clone();
+    let mut cxx_name = None;
+    let mut rust_name = None;
+    let attrs = attrs::parse(
+        cx,
+        attrs,
+        attrs::Parser {
+            doc: Some(&mut doc),
+            derives: Some(&mut derives),
+            namespace: Some(&mut namespace),
+            cxx_name: Some(&mut cxx_name),
+            rust_name: Some(&mut rust_name),
+            ..Default::default()
+        },
+    );
+
+    if lang == Lang::Rust {
+        let span = quote!(#type_token #semi_token);
+        let msg = "type alias in extern \"Rust\" block is not supported";
+        return Err(Error::new_spanned(span, msg));
+    }
+
+    let visibility = visibility_pub(&visibility, type_token.span);
+    let name = pair(namespace, &ident, cxx_name, rust_name);
+
+    Ok(Api::TypeAlias(TypeAlias {
+        doc,
+        derives,
+        attrs,
+        visibility,
+        type_token,
+        name,
+        generics,
+        eq_token,
+        ty,
+        semi_token,
+    }))
+}
+
+fn parse_extern_type_bounded(
+    cx: &mut Errors,
+    attrs: Vec<Attribute>,
+    visibility: Visibility,
+    type_token: Token![type],
+    ident: Ident,
+    generics: Lifetimes,
+    input: ParseStream,
+    lang: Lang,
+    trusted: bool,
+    namespace: &Namespace,
+) -> Result<Api> {
+    let mut bounds = Vec::new();
+    let colon_token: Option<Token![:]> = input.parse()?;
+    if colon_token.is_some() {
+        loop {
+            match input.parse()? {
+                TypeParamBound::Trait(TraitBound {
+                    paren_token: None,
+                    modifier: TraitBoundModifier::None,
+                    lifetimes: None,
+                    path,
+                }) if if let Some(derive) = path.get_ident().and_then(Derive::from) {
+                    bounds.push(derive);
+                    true
+                } else {
+                    false
+                } => {}
+                bound @ TypeParamBound::Trait(_) | bound @ TypeParamBound::Lifetime(_) => {
+                    cx.error(bound, "unsupported trait");
+                }
+            }
+
+            let lookahead = input.lookahead1();
+            if lookahead.peek(Token![+]) {
+                input.parse::<Token![+]>()?;
+            } else if lookahead.peek(Token![;]) {
+                break;
+            } else {
+                return Err(lookahead.error());
+            }
+        }
+    }
+    let semi_token: Token![;] = input.parse()?;
+
+    let mut doc = Doc::new();
+    let mut derives = Vec::new();
+    let mut namespace = namespace.clone();
+    let mut cxx_name = None;
+    let mut rust_name = None;
+    let attrs = attrs::parse(
+        cx,
+        attrs,
+        attrs::Parser {
+            doc: Some(&mut doc),
+            derives: Some(&mut derives),
+            namespace: Some(&mut namespace),
+            cxx_name: Some(&mut cxx_name),
+            rust_name: Some(&mut rust_name),
+            ..Default::default()
+        },
+    );
+
+    let visibility = visibility_pub(&visibility, type_token.span);
+    let name = pair(namespace, &ident, cxx_name, rust_name);
+
+    Ok(match lang {
+        Lang::Cxx => Api::CxxType,
+        Lang::Rust => Api::RustType,
+    }(ExternType {
+        lang,
+        doc,
+        derives,
+        attrs,
+        visibility,
+        type_token,
+        name,
+        generics,
+        colon_token,
+        bounds,
+        semi_token,
+        trusted,
+    }))
+}
+
+fn parse_impl(imp: ItemImpl) -> Result<Api> {
+    let impl_token = imp.impl_token;
+
+    if !imp.items.is_empty() {
+        let mut span = Group::new(Delimiter::Brace, TokenStream::new());
+        span.set_span(imp.brace_token.span);
+        return Err(Error::new_spanned(span, "expected an empty impl block"));
+    }
+
+    if let Some((bang, path, for_token)) = &imp.trait_ {
+        let self_ty = &imp.self_ty;
+        let span = quote!(#bang #path #for_token #self_ty);
+        return Err(Error::new_spanned(
+            span,
+            "unexpected impl, expected something like `impl UniquePtr<T> {}`",
+        ));
+    }
+
+    if let Some(where_clause) = imp.generics.where_clause {
+        return Err(Error::new_spanned(
+            where_clause,
+            "where-clause on an impl is not supported yet",
+        ));
+    }
+    let mut impl_generics = Lifetimes {
+        lt_token: imp.generics.lt_token,
+        lifetimes: Punctuated::new(),
+        gt_token: imp.generics.gt_token,
+    };
+    for pair in imp.generics.params.into_pairs() {
+        let (param, punct) = pair.into_tuple();
+        match param {
+            GenericParam::Lifetime(def) if def.bounds.is_empty() => {
+                impl_generics.lifetimes.push_value(def.lifetime);
+                if let Some(punct) = punct {
+                    impl_generics.lifetimes.push_punct(punct);
+                }
+            }
+            _ => {
+                let span = quote!(#impl_token #impl_generics);
+                return Err(Error::new_spanned(
+                    span,
+                    "generic parameter on an impl is not supported yet",
+                ));
+            }
+        }
+    }
+
+    let mut negative_token = None;
+    let mut self_ty = *imp.self_ty;
+    if let RustType::Verbatim(ty) = &self_ty {
+        let mut iter = ty.clone().into_iter();
+        if let Some(TokenTree::Punct(punct)) = iter.next() {
+            if punct.as_char() == '!' {
+                let ty = iter.collect::<TokenStream>();
+                if !ty.is_empty() {
+                    negative_token = Some(Token![!](punct.span()));
+                    self_ty = syn::parse2(ty)?;
+                }
+            }
+        }
+    }
+
+    let ty = parse_type(&self_ty)?;
+    let ty_generics = match &ty {
+        Type::RustBox(ty)
+        | Type::RustVec(ty)
+        | Type::UniquePtr(ty)
+        | Type::SharedPtr(ty)
+        | Type::WeakPtr(ty)
+        | Type::CxxVector(ty) => match &ty.inner {
+            Type::Ident(ident) => ident.generics.clone(),
+            _ => Lifetimes::default(),
+        },
+        Type::Ident(_)
+        | Type::Ref(_)
+        | Type::Ptr(_)
+        | Type::Str(_)
+        | Type::Fn(_)
+        | Type::Void(_)
+        | Type::SliceRef(_)
+        | Type::Array(_) => Lifetimes::default(),
+    };
+
+    let negative = negative_token.is_some();
+    let brace_token = imp.brace_token;
+
+    Ok(Api::Impl(Impl {
+        impl_token,
+        impl_generics,
+        negative,
+        ty,
+        ty_generics,
+        brace_token,
+        negative_token,
+    }))
+}
+
+fn parse_include(input: ParseStream) -> Result<Include> {
+    if input.peek(LitStr) {
+        let lit: LitStr = input.parse()?;
+        let span = lit.span();
+        return Ok(Include {
+            path: lit.value(),
+            kind: IncludeKind::Quoted,
+            begin_span: span,
+            end_span: span,
+        });
+    }
+
+    if input.peek(Token![<]) {
+        let mut path = String::new();
+
+        let langle: Token![<] = input.parse()?;
+        while !input.is_empty() && !input.peek(Token![>]) {
+            let token: TokenTree = input.parse()?;
+            match token {
+                TokenTree::Ident(token) => path += &token.to_string(),
+                TokenTree::Literal(token)
+                    if token
+                        .to_string()
+                        .starts_with(|ch: char| ch.is_ascii_digit()) =>
+                {
+                    path += &token.to_string();
+                }
+                TokenTree::Punct(token) => path.push(token.as_char()),
+                _ => return Err(Error::new(token.span(), "unexpected token in include path")),
+            }
+        }
+        let rangle: Token![>] = input.parse()?;
+
+        return Ok(Include {
+            path,
+            kind: IncludeKind::Bracketed,
+            begin_span: langle.span,
+            end_span: rangle.span,
+        });
+    }
+
+    Err(input.error("expected \"quoted/path/to\" or <bracketed/path/to>"))
+}
+
+fn parse_type(ty: &RustType) -> Result<Type> {
+    match ty {
+        RustType::Reference(ty) => parse_type_reference(ty),
+        RustType::Ptr(ty) => parse_type_ptr(ty),
+        RustType::Path(ty) => parse_type_path(ty),
+        RustType::Array(ty) => parse_type_array(ty),
+        RustType::BareFn(ty) => parse_type_fn(ty),
+        RustType::Tuple(ty) if ty.elems.is_empty() => Ok(Type::Void(ty.paren_token.span)),
+        _ => Err(Error::new_spanned(ty, "unsupported type")),
+    }
+}
+
+fn parse_type_reference(ty: &TypeReference) -> Result<Type> {
+    let ampersand = ty.and_token;
+    let lifetime = ty.lifetime.clone();
+    let mutable = ty.mutability.is_some();
+    let mutability = ty.mutability;
+
+    if let RustType::Slice(slice) = ty.elem.as_ref() {
+        let inner = parse_type(&slice.elem)?;
+        let bracket = slice.bracket_token;
+        return Ok(Type::SliceRef(Box::new(SliceRef {
+            ampersand,
+            lifetime,
+            mutable,
+            bracket,
+            inner,
+            mutability,
+        })));
+    }
+
+    let inner = parse_type(&ty.elem)?;
+    let pinned = false;
+    let pin_tokens = None;
+
+    Ok(match &inner {
+        Type::Ident(ident) if ident.rust == "str" => {
+            if ty.mutability.is_some() {
+                return Err(Error::new_spanned(ty, "unsupported type"));
+            } else {
+                Type::Str
+            }
+        }
+        _ => Type::Ref,
+    }(Box::new(Ref {
+        pinned,
+        ampersand,
+        lifetime,
+        mutable,
+        inner,
+        pin_tokens,
+        mutability,
+    })))
+}
+
+fn parse_type_ptr(ty: &TypePtr) -> Result<Type> {
+    let star = ty.star_token;
+    let mutable = ty.mutability.is_some();
+    let constness = ty.const_token;
+    let mutability = ty.mutability;
+
+    let inner = parse_type(&ty.elem)?;
+
+    Ok(Type::Ptr(Box::new(Ptr {
+        star,
+        mutable,
+        inner,
+        mutability,
+        constness,
+    })))
+}
+
+fn parse_type_path(ty: &TypePath) -> Result<Type> {
+    let path = &ty.path;
+    if ty.qself.is_none() && path.leading_colon.is_none() && path.segments.len() == 1 {
+        let segment = &path.segments[0];
+        let ident = segment.ident.clone();
+        match &segment.arguments {
+            PathArguments::None => return Ok(Type::Ident(NamedType::new(ident))),
+            PathArguments::AngleBracketed(generic) => {
+                if ident == "UniquePtr" && generic.args.len() == 1 {
+                    if let GenericArgument::Type(arg) = &generic.args[0] {
+                        let inner = parse_type(arg)?;
+                        return Ok(Type::UniquePtr(Box::new(Ty1 {
+                            name: ident,
+                            langle: generic.lt_token,
+                            inner,
+                            rangle: generic.gt_token,
+                        })));
+                    }
+                } else if ident == "SharedPtr" && generic.args.len() == 1 {
+                    if let GenericArgument::Type(arg) = &generic.args[0] {
+                        let inner = parse_type(arg)?;
+                        return Ok(Type::SharedPtr(Box::new(Ty1 {
+                            name: ident,
+                            langle: generic.lt_token,
+                            inner,
+                            rangle: generic.gt_token,
+                        })));
+                    }
+                } else if ident == "WeakPtr" && generic.args.len() == 1 {
+                    if let GenericArgument::Type(arg) = &generic.args[0] {
+                        let inner = parse_type(arg)?;
+                        return Ok(Type::WeakPtr(Box::new(Ty1 {
+                            name: ident,
+                            langle: generic.lt_token,
+                            inner,
+                            rangle: generic.gt_token,
+                        })));
+                    }
+                } else if ident == "CxxVector" && generic.args.len() == 1 {
+                    if let GenericArgument::Type(arg) = &generic.args[0] {
+                        let inner = parse_type(arg)?;
+                        return Ok(Type::CxxVector(Box::new(Ty1 {
+                            name: ident,
+                            langle: generic.lt_token,
+                            inner,
+                            rangle: generic.gt_token,
+                        })));
+                    }
+                } else if ident == "Box" && generic.args.len() == 1 {
+                    if let GenericArgument::Type(arg) = &generic.args[0] {
+                        let inner = parse_type(arg)?;
+                        return Ok(Type::RustBox(Box::new(Ty1 {
+                            name: ident,
+                            langle: generic.lt_token,
+                            inner,
+                            rangle: generic.gt_token,
+                        })));
+                    }
+                } else if ident == "Vec" && generic.args.len() == 1 {
+                    if let GenericArgument::Type(arg) = &generic.args[0] {
+                        let inner = parse_type(arg)?;
+                        return Ok(Type::RustVec(Box::new(Ty1 {
+                            name: ident,
+                            langle: generic.lt_token,
+                            inner,
+                            rangle: generic.gt_token,
+                        })));
+                    }
+                } else if ident == "Pin" && generic.args.len() == 1 {
+                    if let GenericArgument::Type(arg) = &generic.args[0] {
+                        let inner = parse_type(arg)?;
+                        let pin_token = kw::Pin(ident.span());
+                        if let Type::Ref(mut inner) = inner {
+                            inner.pinned = true;
+                            inner.pin_tokens =
+                                Some((pin_token, generic.lt_token, generic.gt_token));
+                            return Ok(Type::Ref(inner));
+                        }
+                    }
+                } else {
+                    let mut lifetimes = Punctuated::new();
+                    let mut only_lifetimes = true;
+                    for pair in generic.args.pairs() {
+                        let (param, punct) = pair.into_tuple();
+                        if let GenericArgument::Lifetime(param) = param {
+                            lifetimes.push_value(param.clone());
+                            if let Some(punct) = punct {
+                                lifetimes.push_punct(*punct);
+                            }
+                        } else {
+                            only_lifetimes = false;
+                            break;
+                        }
+                    }
+                    if only_lifetimes {
+                        return Ok(Type::Ident(NamedType {
+                            rust: ident,
+                            generics: Lifetimes {
+                                lt_token: Some(generic.lt_token),
+                                lifetimes,
+                                gt_token: Some(generic.gt_token),
+                            },
+                        }));
+                    }
+                }
+            }
+            PathArguments::Parenthesized(_) => {}
+        }
+    }
+
+    Err(Error::new_spanned(ty, "unsupported type"))
+}
+
+fn parse_type_array(ty: &TypeArray) -> Result<Type> {
+    let inner = parse_type(&ty.elem)?;
+
+    let len_expr = if let Expr::Lit(lit) = &ty.len {
+        lit
+    } else {
+        let msg = "unsupported expression, array length must be an integer literal";
+        return Err(Error::new_spanned(&ty.len, msg));
+    };
+
+    let len_token = if let Lit::Int(int) = &len_expr.lit {
+        int.clone()
+    } else {
+        let msg = "array length must be an integer literal";
+        return Err(Error::new_spanned(len_expr, msg));
+    };
+
+    let len = len_token.base10_parse::<usize>()?;
+    if len == 0 {
+        let msg = "array with zero size is not supported";
+        return Err(Error::new_spanned(ty, msg));
+    }
+
+    let bracket = ty.bracket_token;
+    let semi_token = ty.semi_token;
+
+    Ok(Type::Array(Box::new(Array {
+        bracket,
+        inner,
+        semi_token,
+        len,
+        len_token,
+    })))
+}
+
+fn parse_type_fn(ty: &TypeBareFn) -> Result<Type> {
+    if ty.lifetimes.is_some() {
+        return Err(Error::new_spanned(
+            ty,
+            "function pointer with lifetime parameters is not supported yet",
+        ));
+    }
+
+    if ty.variadic.is_some() {
+        return Err(Error::new_spanned(
+            ty,
+            "variadic function pointer is not supported yet",
+        ));
+    }
+
+    let args = ty
+        .inputs
+        .iter()
+        .enumerate()
+        .map(|(i, arg)| {
+            let (ident, colon_token) = match &arg.name {
+                Some((ident, colon_token)) => (ident.clone(), *colon_token),
+                None => {
+                    let fn_span = ty.paren_token.span;
+                    let ident = format_ident!("arg{}", i, span = fn_span);
+                    let colon_token = Token![:](fn_span);
+                    (ident, colon_token)
+                }
+            };
+            let ty = parse_type(&arg.ty)?;
+            let doc = Doc::new();
+            let attrs = OtherAttrs::none();
+            let visibility = Token![pub](ident.span());
+            let name = pair(Namespace::default(), &ident, None, None);
+            Ok(Var {
+                doc,
+                attrs,
+                visibility,
+                name,
+                colon_token,
+                ty,
+            })
+        })
+        .collect::<Result<_>>()?;
+
+    let mut throws_tokens = None;
+    let ret = parse_return_type(&ty.output, &mut throws_tokens)?;
+    let throws = throws_tokens.is_some();
+
+    let unsafety = ty.unsafety;
+    let fn_token = ty.fn_token;
+    let generics = Generics::default();
+    let receiver = None;
+    let paren_token = ty.paren_token;
+
+    Ok(Type::Fn(Box::new(Signature {
+        unsafety,
+        fn_token,
+        generics,
+        receiver,
+        args,
+        ret,
+        throws,
+        paren_token,
+        throws_tokens,
+    })))
+}
+
+fn parse_return_type(
+    ty: &ReturnType,
+    throws_tokens: &mut Option<(kw::Result, Token![<], Token![>])>,
+) -> Result<Option<Type>> {
+    let mut ret = match ty {
+        ReturnType::Default => return Ok(None),
+        ReturnType::Type(_, ret) => ret.as_ref(),
+    };
+
+    if let RustType::Path(ty) = ret {
+        let path = &ty.path;
+        if ty.qself.is_none() && path.leading_colon.is_none() && path.segments.len() == 1 {
+            let segment = &path.segments[0];
+            let ident = segment.ident.clone();
+            if let PathArguments::AngleBracketed(generic) = &segment.arguments {
+                if ident == "Result" && generic.args.len() == 1 {
+                    if let GenericArgument::Type(arg) = &generic.args[0] {
+                        ret = arg;
+                        *throws_tokens =
+                            Some((kw::Result(ident.span()), generic.lt_token, generic.gt_token));
+                    }
+                }
+            }
+        }
+    }
+
+    match parse_type(ret)? {
+        Type::Void(_) => Ok(None),
+        ty => Ok(Some(ty)),
+    }
+}
+
+fn visibility_pub(vis: &Visibility, inherited: Span) -> Token![pub] {
+    Token![pub](match vis {
+        Visibility::Public(vis) => vis.pub_token.span,
+        Visibility::Crate(vis) => vis.crate_token.span,
+        Visibility::Restricted(vis) => vis.pub_token.span,
+        Visibility::Inherited => inherited,
+    })
+}
+
+fn pair(
+    namespace: Namespace,
+    default: &Ident,
+    cxx: Option<ForeignName>,
+    rust: Option<Ident>,
+) -> Pair {
+    Pair {
+        namespace,
+        cxx: cxx
+            .unwrap_or_else(|| ForeignName::parse(&default.to_string(), default.span()).unwrap()),
+        rust: rust.unwrap_or_else(|| default.clone()),
+    }
+}
diff --git a/src/syntax/pod.rs b/src/syntax/pod.rs
new file mode 100644 (file)
index 0000000..0bf152e
--- /dev/null
@@ -0,0 +1,36 @@
+use crate::syntax::atom::Atom::{self, *};
+use crate::syntax::{derive, Trait, Type, Types};
+
+impl<'a> Types<'a> {
+    pub fn is_guaranteed_pod(&self, ty: &Type) -> bool {
+        match ty {
+            Type::Ident(ident) => {
+                let ident = &ident.rust;
+                if let Some(atom) = Atom::from(ident) {
+                    match atom {
+                        Bool | Char | U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64
+                        | Isize | F32 | F64 => true,
+                        CxxString | RustString => false,
+                    }
+                } else if let Some(strct) = self.structs.get(ident) {
+                    derive::contains(&strct.derives, Trait::Copy)
+                        || strct
+                            .fields
+                            .iter()
+                            .all(|field| self.is_guaranteed_pod(&field.ty))
+                } else {
+                    self.enums.contains_key(ident)
+                }
+            }
+            Type::RustBox(_)
+            | Type::RustVec(_)
+            | Type::UniquePtr(_)
+            | Type::SharedPtr(_)
+            | Type::WeakPtr(_)
+            | Type::CxxVector(_)
+            | Type::Void(_) => false,
+            Type::Ref(_) | Type::Str(_) | Type::Fn(_) | Type::SliceRef(_) | Type::Ptr(_) => true,
+            Type::Array(array) => self.is_guaranteed_pod(&array.inner),
+        }
+    }
+}
diff --git a/src/syntax/qualified.rs b/src/syntax/qualified.rs
new file mode 100644 (file)
index 0000000..96f07c1
--- /dev/null
@@ -0,0 +1,41 @@
+use syn::ext::IdentExt;
+use syn::parse::{ParseStream, Result};
+use syn::{Ident, LitStr, Token};
+
+pub struct QualifiedName {
+    pub segments: Vec<Ident>,
+}
+
+impl QualifiedName {
+    pub fn parse_unquoted(input: ParseStream) -> Result<Self> {
+        let mut segments = Vec::new();
+        let mut trailing_punct = true;
+        let leading_colons: Option<Token![::]> = input.parse()?;
+        while trailing_punct && input.peek(Ident::peek_any) {
+            let ident = Ident::parse_any(input)?;
+            segments.push(ident);
+            let colons: Option<Token![::]> = input.parse()?;
+            trailing_punct = colons.is_some();
+        }
+        if segments.is_empty() && leading_colons.is_none() {
+            return Err(input.error("expected path"));
+        } else if trailing_punct {
+            return Err(input.error("expected path segment"));
+        }
+        Ok(QualifiedName { segments })
+    }
+
+    pub fn parse_quoted_or_unquoted(input: ParseStream) -> Result<Self> {
+        if input.peek(LitStr) {
+            let lit: LitStr = input.parse()?;
+            if lit.value().is_empty() {
+                let segments = Vec::new();
+                Ok(QualifiedName { segments })
+            } else {
+                lit.parse_with(Self::parse_unquoted)
+            }
+        } else {
+            Self::parse_unquoted(input)
+        }
+    }
+}
diff --git a/src/syntax/report.rs b/src/syntax/report.rs
new file mode 100644 (file)
index 0000000..d1d8bc9
--- /dev/null
@@ -0,0 +1,33 @@
+use quote::ToTokens;
+use std::fmt::Display;
+use syn::{Error, Result};
+
+pub struct Errors {
+    errors: Vec<Error>,
+}
+
+impl Errors {
+    pub fn new() -> Self {
+        Errors { errors: Vec::new() }
+    }
+
+    pub fn error(&mut self, sp: impl ToTokens, msg: impl Display) {
+        self.errors.push(Error::new_spanned(sp, msg));
+    }
+
+    pub fn push(&mut self, error: Error) {
+        self.errors.push(error);
+    }
+
+    pub fn propagate(&mut self) -> Result<()> {
+        let mut iter = self.errors.drain(..);
+        let mut all_errors = match iter.next() {
+            Some(err) => err,
+            None => return Ok(()),
+        };
+        for err in iter {
+            all_errors.combine(err);
+        }
+        Err(all_errors)
+    }
+}
diff --git a/src/syntax/resolve.rs b/src/syntax/resolve.rs
new file mode 100644 (file)
index 0000000..3a2635b
--- /dev/null
@@ -0,0 +1,46 @@
+use crate::syntax::instantiate::NamedImplKey;
+use crate::syntax::{Lifetimes, NamedType, Pair, Types};
+use proc_macro2::Ident;
+
+#[derive(Copy, Clone)]
+pub struct Resolution<'a> {
+    pub name: &'a Pair,
+    pub generics: &'a Lifetimes,
+}
+
+impl<'a> Types<'a> {
+    pub fn resolve(&self, ident: &impl UnresolvedName) -> Resolution<'a> {
+        let ident = ident.ident();
+        match self.try_resolve(ident) {
+            Some(resolution) => resolution,
+            None => panic!("Unable to resolve type `{}`", ident),
+        }
+    }
+
+    pub fn try_resolve(&self, ident: &impl UnresolvedName) -> Option<Resolution<'a>> {
+        let ident = ident.ident();
+        self.resolutions.get(ident).copied()
+    }
+}
+
+pub trait UnresolvedName {
+    fn ident(&self) -> &Ident;
+}
+
+impl UnresolvedName for Ident {
+    fn ident(&self) -> &Ident {
+        self
+    }
+}
+
+impl UnresolvedName for NamedType {
+    fn ident(&self) -> &Ident {
+        &self.rust
+    }
+}
+
+impl<'a> UnresolvedName for NamedImplKey<'a> {
+    fn ident(&self) -> &Ident {
+        self.rust
+    }
+}
diff --git a/src/syntax/set.rs b/src/syntax/set.rs
new file mode 100644 (file)
index 0000000..ca0c43e
--- /dev/null
@@ -0,0 +1,136 @@
+use std::fmt::{self, Debug};
+use std::slice;
+
+pub use self::ordered::OrderedSet;
+pub use self::unordered::UnorderedSet;
+
+mod ordered {
+    use super::{Iter, UnorderedSet};
+    use std::borrow::Borrow;
+    use std::hash::Hash;
+
+    pub struct OrderedSet<T> {
+        set: UnorderedSet<T>,
+        vec: Vec<T>,
+    }
+
+    impl<'a, T> OrderedSet<&'a T>
+    where
+        T: Hash + Eq,
+    {
+        pub fn new() -> Self {
+            OrderedSet {
+                set: UnorderedSet::new(),
+                vec: Vec::new(),
+            }
+        }
+
+        pub fn insert(&mut self, value: &'a T) -> bool {
+            let new = self.set.insert(value);
+            if new {
+                self.vec.push(value);
+            }
+            new
+        }
+
+        pub fn contains<Q>(&self, value: &Q) -> bool
+        where
+            &'a T: Borrow<Q>,
+            Q: ?Sized + Hash + Eq,
+        {
+            self.set.contains(value)
+        }
+
+        pub fn get<Q>(&self, value: &Q) -> Option<&'a T>
+        where
+            &'a T: Borrow<Q>,
+            Q: ?Sized + Hash + Eq,
+        {
+            self.set.get(value).copied()
+        }
+    }
+
+    impl<'a, T> OrderedSet<&'a T> {
+        pub fn is_empty(&self) -> bool {
+            self.vec.is_empty()
+        }
+
+        pub fn iter(&self) -> Iter<'_, 'a, T> {
+            Iter(self.vec.iter())
+        }
+    }
+
+    impl<'s, 'a, T> IntoIterator for &'s OrderedSet<&'a T> {
+        type Item = &'a T;
+        type IntoIter = Iter<'s, 'a, T>;
+        fn into_iter(self) -> Self::IntoIter {
+            self.iter()
+        }
+    }
+}
+
+mod unordered {
+    use std::borrow::Borrow;
+    use std::collections::HashSet;
+    use std::hash::Hash;
+
+    // Wrapper prohibits accidentally introducing iteration over the set, which
+    // could lead to nondeterministic generated code.
+    pub struct UnorderedSet<T>(HashSet<T>);
+
+    impl<T> UnorderedSet<T>
+    where
+        T: Hash + Eq,
+    {
+        pub fn new() -> Self {
+            UnorderedSet(HashSet::new())
+        }
+
+        pub fn insert(&mut self, value: T) -> bool {
+            self.0.insert(value)
+        }
+
+        pub fn contains<Q>(&self, value: &Q) -> bool
+        where
+            T: Borrow<Q>,
+            Q: ?Sized + Hash + Eq,
+        {
+            self.0.contains(value)
+        }
+
+        pub fn get<Q>(&self, value: &Q) -> Option<&T>
+        where
+            T: Borrow<Q>,
+            Q: ?Sized + Hash + Eq,
+        {
+            self.0.get(value)
+        }
+
+        pub fn retain(&mut self, f: impl FnMut(&T) -> bool) {
+            self.0.retain(f);
+        }
+    }
+}
+
+pub struct Iter<'s, 'a, T>(slice::Iter<'s, &'a T>);
+
+impl<'s, 'a, T> Iterator for Iter<'s, 'a, T> {
+    type Item = &'a T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.0.next().copied()
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.0.size_hint()
+    }
+}
+
+impl<'a, T> Debug for OrderedSet<&'a T>
+where
+    T: Debug,
+{
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.debug_set().entries(self).finish()
+    }
+}
diff --git a/src/syntax/symbol.rs b/src/syntax/symbol.rs
new file mode 100644 (file)
index 0000000..d5b9e36
--- /dev/null
@@ -0,0 +1,116 @@
+use crate::syntax::namespace::Namespace;
+use crate::syntax::{ForeignName, Pair};
+use proc_macro2::{Ident, TokenStream};
+use quote::ToTokens;
+use std::fmt::{self, Display, Write};
+
+// A mangled symbol consisting of segments separated by '$'.
+// For example: cxxbridge1$string$new
+pub struct Symbol(String);
+
+impl Display for Symbol {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        Display::fmt(&self.0, formatter)
+    }
+}
+
+impl ToTokens for Symbol {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        ToTokens::to_tokens(&self.0, tokens);
+    }
+}
+
+impl Symbol {
+    fn push(&mut self, segment: &dyn Display) {
+        let len_before = self.0.len();
+        if !self.0.is_empty() {
+            self.0.push('$');
+        }
+        self.0.write_fmt(format_args!("{}", segment)).unwrap();
+        assert!(self.0.len() > len_before);
+    }
+
+    pub fn from_idents<'a>(it: impl Iterator<Item = &'a dyn Segment>) -> Self {
+        let mut symbol = Symbol(String::new());
+        for segment in it {
+            segment.write(&mut symbol);
+        }
+        assert!(!symbol.0.is_empty());
+        symbol
+    }
+
+    /// For example, for taking a symbol and then making a new symbol
+    /// for a vec of that symbol.
+    pub fn prefix_with(&self, prefix: &str) -> Symbol {
+        Symbol(format!("{}{}", prefix, self))
+    }
+}
+
+pub trait Segment {
+    fn write(&self, symbol: &mut Symbol);
+}
+
+impl Segment for str {
+    fn write(&self, symbol: &mut Symbol) {
+        symbol.push(&self);
+    }
+}
+
+impl Segment for usize {
+    fn write(&self, symbol: &mut Symbol) {
+        symbol.push(&self);
+    }
+}
+
+impl Segment for Ident {
+    fn write(&self, symbol: &mut Symbol) {
+        symbol.push(&self);
+    }
+}
+
+impl Segment for Symbol {
+    fn write(&self, symbol: &mut Symbol) {
+        symbol.push(&self);
+    }
+}
+
+impl Segment for Namespace {
+    fn write(&self, symbol: &mut Symbol) {
+        for segment in self {
+            symbol.push(segment);
+        }
+    }
+}
+
+impl Segment for Pair {
+    fn write(&self, symbol: &mut Symbol) {
+        self.namespace.write(symbol);
+        self.cxx.write(symbol);
+    }
+}
+
+impl Segment for ForeignName {
+    fn write(&self, symbol: &mut Symbol) {
+        // TODO: support C++ names containing whitespace (`unsigned int`) or
+        // non-alphanumeric characters (`operator++`).
+        self.to_string().write(symbol);
+    }
+}
+
+impl<T> Segment for &'_ T
+where
+    T: ?Sized + Segment + Display,
+{
+    fn write(&self, symbol: &mut Symbol) {
+        (**self).write(symbol);
+    }
+}
+
+pub fn join(segments: &[&dyn Segment]) -> Symbol {
+    let mut symbol = Symbol(String::new());
+    for segment in segments {
+        segment.write(&mut symbol);
+    }
+    assert!(!symbol.0.is_empty());
+    symbol
+}
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
new file mode 100644 (file)
index 0000000..33f20fa
--- /dev/null
@@ -0,0 +1,369 @@
+use crate::syntax::atom::Atom::*;
+use crate::syntax::{
+    Array, Atom, Derive, Enum, EnumRepr, ExternFn, ExternType, Impl, Lifetimes, NamedType, Ptr,
+    Receiver, Ref, Signature, SliceRef, Struct, Ty1, Type, TypeAlias, Var,
+};
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::{quote_spanned, ToTokens};
+use syn::{token, Token};
+
+impl ToTokens for Type {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        match self {
+            Type::Ident(ident) => {
+                if ident.rust == Char {
+                    let span = ident.rust.span();
+                    tokens.extend(quote_spanned!(span=> ::std::os::raw::));
+                } else if ident.rust == CxxString {
+                    let span = ident.rust.span();
+                    tokens.extend(quote_spanned!(span=> ::cxx::));
+                }
+                ident.to_tokens(tokens);
+            }
+            Type::RustBox(ty)
+            | Type::UniquePtr(ty)
+            | Type::SharedPtr(ty)
+            | Type::WeakPtr(ty)
+            | Type::CxxVector(ty)
+            | Type::RustVec(ty) => ty.to_tokens(tokens),
+            Type::Ref(r) | Type::Str(r) => r.to_tokens(tokens),
+            Type::Ptr(p) => p.to_tokens(tokens),
+            Type::Array(a) => a.to_tokens(tokens),
+            Type::Fn(f) => f.to_tokens(tokens),
+            Type::Void(span) => tokens.extend(quote_spanned!(*span=> ())),
+            Type::SliceRef(r) => r.to_tokens(tokens),
+        }
+    }
+}
+
+impl ToTokens for Var {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Var {
+            doc: _,
+            attrs: _,
+            visibility: _,
+            name,
+            colon_token: _,
+            ty,
+        } = self;
+        name.rust.to_tokens(tokens);
+        Token![:](name.rust.span()).to_tokens(tokens);
+        ty.to_tokens(tokens);
+    }
+}
+
+impl ToTokens for Ty1 {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Ty1 {
+            name,
+            langle,
+            inner,
+            rangle,
+        } = self;
+        let span = name.span();
+        match name.to_string().as_str() {
+            "UniquePtr" | "SharedPtr" | "WeakPtr" | "CxxVector" => {
+                tokens.extend(quote_spanned!(span=> ::cxx::));
+            }
+            "Vec" => {
+                tokens.extend(quote_spanned!(span=> ::std::vec::));
+            }
+            _ => {}
+        }
+        name.to_tokens(tokens);
+        langle.to_tokens(tokens);
+        inner.to_tokens(tokens);
+        rangle.to_tokens(tokens);
+    }
+}
+
+impl ToTokens for Ref {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Ref {
+            pinned: _,
+            ampersand,
+            lifetime,
+            mutable: _,
+            inner,
+            pin_tokens,
+            mutability,
+        } = self;
+        if let Some((pin, langle, _rangle)) = pin_tokens {
+            tokens.extend(quote_spanned!(pin.span=> ::std::pin::Pin));
+            langle.to_tokens(tokens);
+        }
+        ampersand.to_tokens(tokens);
+        lifetime.to_tokens(tokens);
+        mutability.to_tokens(tokens);
+        inner.to_tokens(tokens);
+        if let Some((_pin, _langle, rangle)) = pin_tokens {
+            rangle.to_tokens(tokens);
+        }
+    }
+}
+
+impl ToTokens for Ptr {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Ptr {
+            star,
+            mutable: _,
+            inner,
+            mutability,
+            constness,
+        } = self;
+        star.to_tokens(tokens);
+        mutability.to_tokens(tokens);
+        constness.to_tokens(tokens);
+        inner.to_tokens(tokens);
+    }
+}
+
+impl ToTokens for SliceRef {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let SliceRef {
+            ampersand,
+            lifetime,
+            mutable: _,
+            bracket,
+            inner,
+            mutability,
+        } = self;
+        ampersand.to_tokens(tokens);
+        lifetime.to_tokens(tokens);
+        mutability.to_tokens(tokens);
+        bracket.surround(tokens, |tokens| {
+            inner.to_tokens(tokens);
+        });
+    }
+}
+
+impl ToTokens for Array {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Array {
+            bracket,
+            inner,
+            semi_token,
+            len: _,
+            len_token,
+        } = self;
+        bracket.surround(tokens, |tokens| {
+            inner.to_tokens(tokens);
+            semi_token.to_tokens(tokens);
+            len_token.to_tokens(tokens);
+        });
+    }
+}
+
+impl ToTokens for Atom {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        Ident::new(self.as_ref(), Span::call_site()).to_tokens(tokens);
+    }
+}
+
+impl ToTokens for Derive {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        Ident::new(self.what.as_ref(), self.span).to_tokens(tokens);
+    }
+}
+
+impl ToTokens for ExternType {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        // Notional token range for error reporting purposes.
+        self.type_token.to_tokens(tokens);
+        self.name.rust.to_tokens(tokens);
+        self.generics.to_tokens(tokens);
+    }
+}
+
+impl ToTokens for TypeAlias {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        // Notional token range for error reporting purposes.
+        self.type_token.to_tokens(tokens);
+        self.name.rust.to_tokens(tokens);
+        self.generics.to_tokens(tokens);
+    }
+}
+
+impl ToTokens for Struct {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        // Notional token range for error reporting purposes.
+        self.struct_token.to_tokens(tokens);
+        self.name.rust.to_tokens(tokens);
+        self.generics.to_tokens(tokens);
+    }
+}
+
+impl ToTokens for Enum {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        // Notional token range for error reporting purposes.
+        self.enum_token.to_tokens(tokens);
+        self.name.rust.to_tokens(tokens);
+        self.generics.to_tokens(tokens);
+    }
+}
+
+impl ToTokens for ExternFn {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        // Notional token range for error reporting purposes.
+        self.unsafety.to_tokens(tokens);
+        self.sig.fn_token.to_tokens(tokens);
+        self.semi_token.to_tokens(tokens);
+    }
+}
+
+impl ToTokens for Impl {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Impl {
+            impl_token,
+            impl_generics,
+            negative: _,
+            ty,
+            ty_generics: _,
+            brace_token,
+            negative_token,
+        } = self;
+        impl_token.to_tokens(tokens);
+        impl_generics.to_tokens(tokens);
+        negative_token.to_tokens(tokens);
+        ty.to_tokens(tokens);
+        brace_token.surround(tokens, |_tokens| {});
+    }
+}
+
+impl ToTokens for Lifetimes {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Lifetimes {
+            lt_token,
+            lifetimes,
+            gt_token,
+        } = self;
+        lt_token.to_tokens(tokens);
+        lifetimes.to_tokens(tokens);
+        gt_token.to_tokens(tokens);
+    }
+}
+
+impl ToTokens for Signature {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Signature {
+            unsafety: _,
+            fn_token,
+            generics: _,
+            receiver: _,
+            args,
+            ret,
+            throws: _,
+            paren_token,
+            throws_tokens,
+        } = self;
+        fn_token.to_tokens(tokens);
+        paren_token.surround(tokens, |tokens| {
+            args.to_tokens(tokens);
+        });
+        if let Some(ret) = ret {
+            Token![->](paren_token.span).to_tokens(tokens);
+            if let Some((result, langle, rangle)) = throws_tokens {
+                result.to_tokens(tokens);
+                langle.to_tokens(tokens);
+                ret.to_tokens(tokens);
+                rangle.to_tokens(tokens);
+            } else {
+                ret.to_tokens(tokens);
+            }
+        } else if let Some((result, langle, rangle)) = throws_tokens {
+            Token![->](paren_token.span).to_tokens(tokens);
+            result.to_tokens(tokens);
+            langle.to_tokens(tokens);
+            token::Paren(langle.span).surround(tokens, |_| ());
+            rangle.to_tokens(tokens);
+        }
+    }
+}
+
+impl ToTokens for EnumRepr {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        match self {
+            EnumRepr::Native { atom, repr_type: _ } => atom.to_tokens(tokens),
+            EnumRepr::Foreign { rust_type } => rust_type.to_tokens(tokens),
+        }
+    }
+}
+
+impl ToTokens for NamedType {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let NamedType { rust, generics } = self;
+        rust.to_tokens(tokens);
+        generics.to_tokens(tokens);
+    }
+}
+
+pub struct ReceiverType<'a>(&'a Receiver);
+pub struct ReceiverTypeSelf<'a>(&'a Receiver);
+
+impl Receiver {
+    // &TheType
+    pub fn ty(&self) -> ReceiverType {
+        ReceiverType(self)
+    }
+
+    // &Self
+    pub fn ty_self(&self) -> ReceiverTypeSelf {
+        ReceiverTypeSelf(self)
+    }
+}
+
+impl ToTokens for ReceiverType<'_> {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Receiver {
+            pinned: _,
+            ampersand,
+            lifetime,
+            mutable: _,
+            var: _,
+            colon_token: _,
+            ty,
+            shorthand: _,
+            pin_tokens,
+            mutability,
+        } = &self.0;
+        if let Some((pin, langle, _rangle)) = pin_tokens {
+            tokens.extend(quote_spanned!(pin.span=> ::std::pin::Pin));
+            langle.to_tokens(tokens);
+        }
+        ampersand.to_tokens(tokens);
+        lifetime.to_tokens(tokens);
+        mutability.to_tokens(tokens);
+        ty.to_tokens(tokens);
+        if let Some((_pin, _langle, rangle)) = pin_tokens {
+            rangle.to_tokens(tokens);
+        }
+    }
+}
+
+impl ToTokens for ReceiverTypeSelf<'_> {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Receiver {
+            pinned: _,
+            ampersand,
+            lifetime,
+            mutable: _,
+            var: _,
+            colon_token: _,
+            ty,
+            shorthand: _,
+            pin_tokens,
+            mutability,
+        } = &self.0;
+        if let Some((pin, langle, _rangle)) = pin_tokens {
+            tokens.extend(quote_spanned!(pin.span=> ::std::pin::Pin));
+            langle.to_tokens(tokens);
+        }
+        ampersand.to_tokens(tokens);
+        lifetime.to_tokens(tokens);
+        mutability.to_tokens(tokens);
+        Token![Self](ty.rust.span()).to_tokens(tokens);
+        if let Some((_pin, _langle, rangle)) = pin_tokens {
+            rangle.to_tokens(tokens);
+        }
+    }
+}
diff --git a/src/syntax/toposort.rs b/src/syntax/toposort.rs
new file mode 100644 (file)
index 0000000..8fe55b8
--- /dev/null
@@ -0,0 +1,51 @@
+use crate::syntax::map::{Entry, UnorderedMap as Map};
+use crate::syntax::report::Errors;
+use crate::syntax::{Api, Struct, Type, Types};
+
+enum Mark {
+    Visiting,
+    Visited,
+}
+
+pub fn sort<'a>(cx: &mut Errors, apis: &'a [Api], types: &Types<'a>) -> Vec<&'a Struct> {
+    let mut sorted = Vec::new();
+    let ref mut marks = Map::new();
+    for api in apis {
+        if let Api::Struct(strct) = api {
+            let _ = visit(cx, strct, &mut sorted, marks, types);
+        }
+    }
+    sorted
+}
+
+fn visit<'a>(
+    cx: &mut Errors,
+    strct: &'a Struct,
+    sorted: &mut Vec<&'a Struct>,
+    marks: &mut Map<*const Struct, Mark>,
+    types: &Types<'a>,
+) -> Result<(), ()> {
+    match marks.entry(strct) {
+        Entry::Occupied(entry) => match entry.get() {
+            Mark::Visiting => return Err(()), // not a DAG
+            Mark::Visited => return Ok(()),
+        },
+        Entry::Vacant(entry) => {
+            entry.insert(Mark::Visiting);
+        }
+    }
+    let mut result = Ok(());
+    for field in &strct.fields {
+        if let Type::Ident(ident) = &field.ty {
+            if let Some(inner) = types.structs.get(&ident.rust) {
+                if visit(cx, inner, sorted, marks, types).is_err() {
+                    cx.error(field, "unsupported cyclic data structure");
+                    result = Err(());
+                }
+            }
+        }
+    }
+    marks.insert(strct, Mark::Visited);
+    sorted.push(strct);
+    result
+}
diff --git a/src/syntax/trivial.rs b/src/syntax/trivial.rs
new file mode 100644 (file)
index 0000000..067e2d7
--- /dev/null
@@ -0,0 +1,312 @@
+use crate::syntax::map::UnorderedMap;
+use crate::syntax::set::{OrderedSet as Set, UnorderedSet};
+use crate::syntax::{Api, Enum, ExternFn, NamedType, Pair, Struct, Type};
+use proc_macro2::Ident;
+use std::fmt::{self, Display};
+
+#[derive(Copy, Clone)]
+pub enum TrivialReason<'a> {
+    StructField(&'a Struct),
+    FunctionArgument(&'a ExternFn),
+    FunctionReturn(&'a ExternFn),
+    BoxTarget,
+    VecElement,
+    SliceElement { mutable: bool },
+    UnpinnedMut(&'a ExternFn),
+}
+
+pub fn required_trivial_reasons<'a>(
+    apis: &'a [Api],
+    all: &Set<&'a Type>,
+    structs: &UnorderedMap<&'a Ident, &'a Struct>,
+    enums: &UnorderedMap<&'a Ident, &'a Enum>,
+    cxx: &UnorderedSet<&'a Ident>,
+) -> UnorderedMap<&'a Ident, Vec<TrivialReason<'a>>> {
+    let mut required_trivial = UnorderedMap::new();
+
+    let mut insist_extern_types_are_trivial = |ident: &'a NamedType, reason| {
+        if cxx.contains(&ident.rust)
+            && !structs.contains_key(&ident.rust)
+            && !enums.contains_key(&ident.rust)
+        {
+            required_trivial
+                .entry(&ident.rust)
+                .or_insert_with(Vec::new)
+                .push(reason);
+        }
+    };
+
+    for api in apis {
+        match api {
+            Api::Struct(strct) => {
+                for field in &strct.fields {
+                    if let Type::Ident(ident) = &field.ty {
+                        let reason = TrivialReason::StructField(strct);
+                        insist_extern_types_are_trivial(ident, reason);
+                    }
+                }
+            }
+            Api::CxxFunction(efn) | Api::RustFunction(efn) => {
+                if let Some(receiver) = &efn.receiver {
+                    if receiver.mutable && !receiver.pinned {
+                        let reason = TrivialReason::UnpinnedMut(efn);
+                        insist_extern_types_are_trivial(&receiver.ty, reason);
+                    }
+                }
+                for arg in &efn.args {
+                    match &arg.ty {
+                        Type::Ident(ident) => {
+                            let reason = TrivialReason::FunctionArgument(efn);
+                            insist_extern_types_are_trivial(ident, reason);
+                        }
+                        Type::Ref(ty) => {
+                            if ty.mutable && !ty.pinned {
+                                if let Type::Ident(ident) = &ty.inner {
+                                    let reason = TrivialReason::UnpinnedMut(efn);
+                                    insist_extern_types_are_trivial(ident, reason);
+                                }
+                            }
+                        }
+                        _ => {}
+                    }
+                }
+                if let Some(ret) = &efn.ret {
+                    match ret {
+                        Type::Ident(ident) => {
+                            let reason = TrivialReason::FunctionReturn(efn);
+                            insist_extern_types_are_trivial(ident, reason);
+                        }
+                        Type::Ref(ty) => {
+                            if ty.mutable && !ty.pinned {
+                                if let Type::Ident(ident) = &ty.inner {
+                                    let reason = TrivialReason::UnpinnedMut(efn);
+                                    insist_extern_types_are_trivial(ident, reason);
+                                }
+                            }
+                        }
+                        _ => {}
+                    }
+                }
+            }
+            _ => {}
+        }
+    }
+
+    for ty in all {
+        match ty {
+            Type::RustBox(ty) => {
+                if let Type::Ident(ident) = &ty.inner {
+                    let reason = TrivialReason::BoxTarget;
+                    insist_extern_types_are_trivial(ident, reason);
+                }
+            }
+            Type::RustVec(ty) => {
+                if let Type::Ident(ident) = &ty.inner {
+                    let reason = TrivialReason::VecElement;
+                    insist_extern_types_are_trivial(ident, reason);
+                }
+            }
+            Type::SliceRef(ty) => {
+                if let Type::Ident(ident) = &ty.inner {
+                    let reason = TrivialReason::SliceElement {
+                        mutable: ty.mutable,
+                    };
+                    insist_extern_types_are_trivial(ident, reason);
+                }
+            }
+            _ => {}
+        }
+    }
+
+    required_trivial
+}
+
+// Context:
+// "type {type} should be trivially move constructible and trivially destructible in C++ to be used as {what} in Rust"
+// "needs a cxx::ExternType impl in order to be used as {what}"
+pub fn as_what<'a>(name: &'a Pair, reasons: &'a [TrivialReason]) -> impl Display + 'a {
+    struct Description<'a> {
+        name: &'a Pair,
+        reasons: &'a [TrivialReason<'a>],
+    }
+
+    impl<'a> Display for Description<'a> {
+        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+            let mut field_of = Set::new();
+            let mut argument_of = Set::new();
+            let mut return_of = Set::new();
+            let mut box_target = false;
+            let mut vec_element = false;
+            let mut slice_shared_element = false;
+            let mut slice_mut_element = false;
+            let mut unpinned_mut = Set::new();
+
+            for reason in self.reasons {
+                match reason {
+                    TrivialReason::StructField(strct) => {
+                        field_of.insert(&strct.name.rust);
+                    }
+                    TrivialReason::FunctionArgument(efn) => {
+                        argument_of.insert(&efn.name.rust);
+                    }
+                    TrivialReason::FunctionReturn(efn) => {
+                        return_of.insert(&efn.name.rust);
+                    }
+                    TrivialReason::BoxTarget => box_target = true,
+                    TrivialReason::VecElement => vec_element = true,
+                    TrivialReason::SliceElement { mutable } => {
+                        if *mutable {
+                            slice_mut_element = true;
+                        } else {
+                            slice_shared_element = true;
+                        }
+                    }
+                    TrivialReason::UnpinnedMut(efn) => {
+                        unpinned_mut.insert(&efn.name.rust);
+                    }
+                }
+            }
+
+            let mut clauses = Vec::new();
+            if !field_of.is_empty() {
+                clauses.push(Clause::Set {
+                    article: "a",
+                    desc: "field of",
+                    set: &field_of,
+                });
+            }
+            if !argument_of.is_empty() {
+                clauses.push(Clause::Set {
+                    article: "an",
+                    desc: "argument of",
+                    set: &argument_of,
+                });
+            }
+            if !return_of.is_empty() {
+                clauses.push(Clause::Set {
+                    article: "a",
+                    desc: "return value of",
+                    set: &return_of,
+                });
+            }
+            if box_target {
+                clauses.push(Clause::Ty1 {
+                    article: "type",
+                    desc: "Box",
+                    param: self.name,
+                });
+            }
+            if vec_element {
+                clauses.push(Clause::Ty1 {
+                    article: "a",
+                    desc: "vector element in Vec",
+                    param: self.name,
+                });
+            }
+            if slice_shared_element || slice_mut_element {
+                clauses.push(Clause::Slice {
+                    article: "a",
+                    desc: "slice element in",
+                    shared: slice_shared_element,
+                    mutable: slice_mut_element,
+                    param: self.name,
+                });
+            }
+            if !unpinned_mut.is_empty() {
+                clauses.push(Clause::Set {
+                    article: "a",
+                    desc: "non-pinned mutable reference in signature of",
+                    set: &unpinned_mut,
+                });
+            }
+
+            for (i, clause) in clauses.iter().enumerate() {
+                if i == 0 {
+                    write!(f, "{} ", clause.article())?;
+                } else if i + 1 < clauses.len() {
+                    write!(f, ", ")?;
+                } else {
+                    write!(f, " or ")?;
+                }
+                clause.fmt(f)?;
+            }
+
+            Ok(())
+        }
+    }
+
+    enum Clause<'a> {
+        Set {
+            article: &'a str,
+            desc: &'a str,
+            set: &'a Set<&'a Ident>,
+        },
+        Ty1 {
+            article: &'a str,
+            desc: &'a str,
+            param: &'a Pair,
+        },
+        Slice {
+            article: &'a str,
+            desc: &'a str,
+            shared: bool,
+            mutable: bool,
+            param: &'a Pair,
+        },
+    }
+
+    impl<'a> Clause<'a> {
+        fn article(&self) -> &'a str {
+            match self {
+                Clause::Set { article, .. }
+                | Clause::Ty1 { article, .. }
+                | Clause::Slice { article, .. } => article,
+            }
+        }
+
+        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+            match self {
+                Clause::Set {
+                    article: _,
+                    desc,
+                    set,
+                } => {
+                    write!(f, "{} ", desc)?;
+                    for (i, ident) in set.iter().take(3).enumerate() {
+                        if i > 0 {
+                            write!(f, ", ")?;
+                        }
+                        write!(f, "`{}`", ident)?;
+                    }
+                    Ok(())
+                }
+                Clause::Ty1 {
+                    article: _,
+                    desc,
+                    param,
+                } => write!(f, "{}<{}>", desc, param.rust),
+                Clause::Slice {
+                    article: _,
+                    desc,
+                    shared,
+                    mutable,
+                    param,
+                } => {
+                    write!(f, "{} ", desc)?;
+                    if *shared {
+                        write!(f, "&[{}]", param.rust)?;
+                    }
+                    if *shared && *mutable {
+                        write!(f, " and ")?;
+                    }
+                    if *mutable {
+                        write!(f, "&mut [{}]", param.rust)?;
+                    }
+                    Ok(())
+                }
+            }
+        }
+    }
+
+    Description { name, reasons }
+}
diff --git a/src/syntax/types.rs b/src/syntax/types.rs
new file mode 100644 (file)
index 0000000..c54682b
--- /dev/null
@@ -0,0 +1,284 @@
+use crate::syntax::improper::ImproperCtype;
+use crate::syntax::instantiate::ImplKey;
+use crate::syntax::map::{OrderedMap, UnorderedMap};
+use crate::syntax::report::Errors;
+use crate::syntax::resolve::Resolution;
+use crate::syntax::set::{OrderedSet, UnorderedSet};
+use crate::syntax::trivial::{self, TrivialReason};
+use crate::syntax::visit::{self, Visit};
+use crate::syntax::{
+    toposort, Api, Atom, Enum, EnumRepr, ExternType, Impl, Lifetimes, Pair, Struct, Type, TypeAlias,
+};
+use proc_macro2::Ident;
+use quote::ToTokens;
+
+pub struct Types<'a> {
+    pub all: OrderedSet<&'a Type>,
+    pub structs: UnorderedMap<&'a Ident, &'a Struct>,
+    pub enums: UnorderedMap<&'a Ident, &'a Enum>,
+    pub cxx: UnorderedSet<&'a Ident>,
+    pub rust: UnorderedSet<&'a Ident>,
+    pub aliases: UnorderedMap<&'a Ident, &'a TypeAlias>,
+    pub untrusted: UnorderedMap<&'a Ident, &'a ExternType>,
+    pub required_trivial: UnorderedMap<&'a Ident, Vec<TrivialReason<'a>>>,
+    pub impls: OrderedMap<ImplKey<'a>, Option<&'a Impl>>,
+    pub resolutions: UnorderedMap<&'a Ident, Resolution<'a>>,
+    pub struct_improper_ctypes: UnorderedSet<&'a Ident>,
+    pub toposorted_structs: Vec<&'a Struct>,
+}
+
+impl<'a> Types<'a> {
+    pub fn collect(cx: &mut Errors, apis: &'a [Api]) -> Self {
+        let mut all = OrderedSet::new();
+        let mut structs = UnorderedMap::new();
+        let mut enums = UnorderedMap::new();
+        let mut cxx = UnorderedSet::new();
+        let mut rust = UnorderedSet::new();
+        let mut aliases = UnorderedMap::new();
+        let mut untrusted = UnorderedMap::new();
+        let mut impls = OrderedMap::new();
+        let mut resolutions = UnorderedMap::new();
+        let struct_improper_ctypes = UnorderedSet::new();
+        let toposorted_structs = Vec::new();
+
+        fn visit<'a>(all: &mut OrderedSet<&'a Type>, ty: &'a Type) {
+            struct CollectTypes<'s, 'a>(&'s mut OrderedSet<&'a Type>);
+
+            impl<'s, 'a> Visit<'a> for CollectTypes<'s, 'a> {
+                fn visit_type(&mut self, ty: &'a Type) {
+                    self.0.insert(ty);
+                    visit::visit_type(self, ty);
+                }
+            }
+
+            CollectTypes(all).visit_type(ty);
+        }
+
+        let mut add_resolution = |name: &'a Pair, generics: &'a Lifetimes| {
+            resolutions.insert(&name.rust, Resolution { name, generics });
+        };
+
+        let mut type_names = UnorderedSet::new();
+        let mut function_names = UnorderedSet::new();
+        for api in apis {
+            // The same identifier is permitted to be declared as both a shared
+            // enum and extern C++ type, or shared struct and extern C++ type.
+            // That indicates to not emit the C++ enum/struct definition because
+            // it's defined by the included headers already.
+            //
+            // All other cases of duplicate identifiers are reported as an error.
+            match api {
+                Api::Include(_) => {}
+                Api::Struct(strct) => {
+                    let ident = &strct.name.rust;
+                    if !type_names.insert(ident)
+                        && (!cxx.contains(ident)
+                            || structs.contains_key(ident)
+                            || enums.contains_key(ident))
+                    {
+                        // If already declared as a struct or enum, or if
+                        // colliding with something other than an extern C++
+                        // type, then error.
+                        duplicate_name(cx, strct, ident);
+                    }
+                    structs.insert(&strct.name.rust, strct);
+                    for field in &strct.fields {
+                        visit(&mut all, &field.ty);
+                    }
+                    add_resolution(&strct.name, &strct.generics);
+                }
+                Api::Enum(enm) => {
+                    match &enm.repr {
+                        EnumRepr::Native { atom: _, repr_type } => {
+                            all.insert(repr_type);
+                        }
+                        EnumRepr::Foreign { rust_type: _ } => {}
+                    }
+                    let ident = &enm.name.rust;
+                    if !type_names.insert(ident)
+                        && (!cxx.contains(ident)
+                            || structs.contains_key(ident)
+                            || enums.contains_key(ident))
+                    {
+                        // If already declared as a struct or enum, or if
+                        // colliding with something other than an extern C++
+                        // type, then error.
+                        duplicate_name(cx, enm, ident);
+                    }
+                    enums.insert(ident, enm);
+                    if enm.variants_from_header {
+                        // #![variants_from_header] enums are implicitly extern
+                        // C++ type.
+                        cxx.insert(&enm.name.rust);
+                    }
+                    add_resolution(&enm.name, &enm.generics);
+                }
+                Api::CxxType(ety) => {
+                    let ident = &ety.name.rust;
+                    if !type_names.insert(ident)
+                        && (cxx.contains(ident)
+                            || !structs.contains_key(ident) && !enums.contains_key(ident))
+                    {
+                        // If already declared as an extern C++ type, or if
+                        // colliding with something which is neither struct nor
+                        // enum, then error.
+                        duplicate_name(cx, ety, ident);
+                    }
+                    cxx.insert(ident);
+                    if !ety.trusted {
+                        untrusted.insert(ident, ety);
+                    }
+                    add_resolution(&ety.name, &ety.generics);
+                }
+                Api::RustType(ety) => {
+                    let ident = &ety.name.rust;
+                    if !type_names.insert(ident) {
+                        duplicate_name(cx, ety, ident);
+                    }
+                    rust.insert(ident);
+                    add_resolution(&ety.name, &ety.generics);
+                }
+                Api::CxxFunction(efn) | Api::RustFunction(efn) => {
+                    // Note: duplication of the C++ name is fine because C++ has
+                    // function overloading.
+                    if !function_names.insert((&efn.receiver, &efn.name.rust)) {
+                        duplicate_name(cx, efn, &efn.name.rust);
+                    }
+                    for arg in &efn.args {
+                        visit(&mut all, &arg.ty);
+                    }
+                    if let Some(ret) = &efn.ret {
+                        visit(&mut all, ret);
+                    }
+                }
+                Api::TypeAlias(alias) => {
+                    let ident = &alias.name.rust;
+                    if !type_names.insert(ident) {
+                        duplicate_name(cx, alias, ident);
+                    }
+                    cxx.insert(ident);
+                    aliases.insert(ident, alias);
+                    add_resolution(&alias.name, &alias.generics);
+                }
+                Api::Impl(imp) => {
+                    visit(&mut all, &imp.ty);
+                    if let Some(key) = imp.ty.impl_key() {
+                        impls.insert(key, Some(imp));
+                    }
+                }
+            }
+        }
+
+        for ty in &all {
+            let impl_key = match ty.impl_key() {
+                Some(impl_key) => impl_key,
+                None => continue,
+            };
+            let implicit_impl = match impl_key {
+                ImplKey::RustBox(ident)
+                | ImplKey::RustVec(ident)
+                | ImplKey::UniquePtr(ident)
+                | ImplKey::SharedPtr(ident)
+                | ImplKey::WeakPtr(ident)
+                | ImplKey::CxxVector(ident) => {
+                    Atom::from(ident.rust).is_none() && !aliases.contains_key(ident.rust)
+                }
+            };
+            if implicit_impl && !impls.contains_key(&impl_key) {
+                impls.insert(impl_key, None);
+            }
+        }
+
+        // All these APIs may contain types passed by value. We need to ensure
+        // we check that this is permissible. We do this _after_ scanning all
+        // the APIs above, in case some function or struct references a type
+        // which is declared subsequently.
+        let required_trivial =
+            trivial::required_trivial_reasons(apis, &all, &structs, &enums, &cxx);
+
+        let mut types = Types {
+            all,
+            structs,
+            enums,
+            cxx,
+            rust,
+            aliases,
+            untrusted,
+            required_trivial,
+            impls,
+            resolutions,
+            struct_improper_ctypes,
+            toposorted_structs,
+        };
+
+        types.toposorted_structs = toposort::sort(cx, apis, &types);
+
+        let mut unresolved_structs = types.structs.keys();
+        let mut new_information = true;
+        while new_information {
+            new_information = false;
+            unresolved_structs.retain(|ident| {
+                let mut retain = false;
+                for var in &types.structs[ident].fields {
+                    if match types.determine_improper_ctype(&var.ty) {
+                        ImproperCtype::Depends(inner) => {
+                            retain = true;
+                            types.struct_improper_ctypes.contains(inner)
+                        }
+                        ImproperCtype::Definite(improper) => improper,
+                    } {
+                        types.struct_improper_ctypes.insert(ident);
+                        new_information = true;
+                        return false;
+                    }
+                }
+                // If all fields definite false, remove from unresolved_structs.
+                retain
+            });
+        }
+
+        types
+    }
+
+    pub fn needs_indirect_abi(&self, ty: &Type) -> bool {
+        match ty {
+            Type::RustBox(_) | Type::UniquePtr(_) => false,
+            Type::Array(_) => true,
+            _ => !self.is_guaranteed_pod(ty),
+        }
+    }
+
+    // Types that trigger rustc's default #[warn(improper_ctypes)] lint, even if
+    // they may be otherwise unproblematic to mention in an extern signature.
+    // For example in a signature like `extern "C" fn(*const String)`, rustc
+    // refuses to believe that C could know how to supply us with a pointer to a
+    // Rust String, even though C could easily have obtained that pointer
+    // legitimately from a Rust call.
+    pub fn is_considered_improper_ctype(&self, ty: &Type) -> bool {
+        match self.determine_improper_ctype(ty) {
+            ImproperCtype::Definite(improper) => improper,
+            ImproperCtype::Depends(ident) => self.struct_improper_ctypes.contains(ident),
+        }
+    }
+
+    // Types which we need to assume could possibly exist by value on the Rust
+    // side.
+    pub fn is_maybe_trivial(&self, ty: &Ident) -> bool {
+        self.structs.contains_key(ty)
+            || self.enums.contains_key(ty)
+            || self.aliases.contains_key(ty)
+    }
+}
+
+impl<'t, 'a> IntoIterator for &'t Types<'a> {
+    type Item = &'a Type;
+    type IntoIter = crate::syntax::set::Iter<'t, 'a, Type>;
+    fn into_iter(self) -> Self::IntoIter {
+        self.all.into_iter()
+    }
+}
+
+fn duplicate_name(cx: &mut Errors, sp: impl ToTokens, ident: &Ident) {
+    let msg = format!("the name `{}` is defined multiple times", ident);
+    cx.error(sp, msg);
+}
diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs
new file mode 100644 (file)
index 0000000..2f31378
--- /dev/null
@@ -0,0 +1,34 @@
+use crate::syntax::Type;
+
+pub trait Visit<'a> {
+    fn visit_type(&mut self, ty: &'a Type) {
+        visit_type(self, ty);
+    }
+}
+
+pub fn visit_type<'a, V>(visitor: &mut V, ty: &'a Type)
+where
+    V: Visit<'a> + ?Sized,
+{
+    match ty {
+        Type::Ident(_) | Type::Str(_) | Type::Void(_) => {}
+        Type::RustBox(ty)
+        | Type::UniquePtr(ty)
+        | Type::SharedPtr(ty)
+        | Type::WeakPtr(ty)
+        | Type::CxxVector(ty)
+        | Type::RustVec(ty) => visitor.visit_type(&ty.inner),
+        Type::Ref(r) => visitor.visit_type(&r.inner),
+        Type::Ptr(p) => visitor.visit_type(&p.inner),
+        Type::Array(a) => visitor.visit_type(&a.inner),
+        Type::SliceRef(s) => visitor.visit_type(&s.inner),
+        Type::Fn(fun) => {
+            if let Some(ret) = &fun.ret {
+                visitor.visit_type(ret);
+            }
+            for arg in &fun.args {
+                visitor.visit_type(&arg.ty);
+            }
+        }
+    }
+}
diff --git a/src/test.rs b/src/test.rs
new file mode 100644 (file)
index 0000000..d94f867
--- /dev/null
@@ -0,0 +1,58 @@
+const EXPECTED: &str = "\
+cxxbridge $VERSION
+David Tolnay <dtolnay@gmail.com>
+https://github.com/dtolnay/cxx
+
+USAGE:
+    cxxbridge <input>.rs              Emit .cc file for bridge to stdout
+    cxxbridge <input>.rs --header     Emit .h file for bridge to stdout
+    cxxbridge --header                Emit \"rust/cxx.h\" header to stdout
+
+ARGS:
+    <input>
+            Input Rust source file containing #[cxx::bridge].
+
+OPTIONS:
+        --cxx-impl-annotations <annotation>
+            Optional annotation for implementations of C++ function wrappers
+            that may be exposed to Rust. You may for example need to provide
+            __declspec(dllexport) or __attribute__((visibility(\"default\")))
+            if Rust code from one shared object or executable depends on
+            these C++ functions in another.
+
+    -h, --help
+            Print help information.
+
+        --header
+            Emit header with declarations only. Optional if using `-o` with
+            a path ending in `.h`.
+
+    -i, --include <include>
+            Any additional headers to #include. The cxxbridge tool does not
+            parse or even require the given paths to exist; they simply go
+            into the generated C++ code as #include lines.
+
+    -o, --output <output>
+            Path of file to write as output. Output goes to stdout if -o is
+            not specified.
+
+    -V, --version
+            Print version information.
+";
+
+#[test]
+fn test_help() {
+    let mut app = super::app();
+    let mut out = Vec::new();
+    app.write_long_help(&mut out).unwrap();
+    let help = String::from_utf8(out).unwrap();
+    let version = option_env!("CARGO_PKG_VERSION").unwrap_or_default();
+    let expected = EXPECTED.replace("$VERSION", version);
+    assert_eq!(help, expected);
+}
+
+#[test]
+fn test_cli() {
+    let app = super::app();
+    app.debug_assert();
+}